haroon_dev #206

Merged
Haroon6138 merged 11 commits from haroon_dev into master 2 days ago

@ -34,7 +34,8 @@ android {
defaultConfig {
applicationId = "com.cloudsolutions.HMGPatientApp"
// minSdk = 24
minSdk = 26
// minSdk = 26
minSdk = 29
targetSdk = 35
compileSdk = 36
// targetSdk = flutter.targetSdkVersion

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 475 KiB

After

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

@ -301,7 +301,7 @@
"vitalSignsSubTitle": "التقارير",
"myMedical": "نشط",
"myMedicalSubtitle": "الأدوية",
"myDoctor": "طبيبي",
"myDoctor": "أطبائي",
"myDoctorSubtitle": "القائمة",
"eye": "العين",
"eyeSubtitle": "القياس",
@ -1618,5 +1618,35 @@
"sortByLocation": "الترتيب حسب الموقع",
"timeForFirstReminder": "وقت التذكير الأول",
"reminderRemovalNote": "يمكنك إزالتها من التقويم الخاص بك لاحقاً عن طريق إيقاف تشغيل التذكير",
"communicationLanguage": "لغة التواصل"
"communicationLanguage": "لغة التواصل",
"cmcServiceHeader": "فحص صحي شامل: تشخيص متقدم، معلومات صحية مفصلة",
"cmcServiceDescription": "احصل على معلومات تفصيلية عن صحتك من خلال خدمات التشخيص المتقدمة لدينا. افهم جسمك بشكل أفضل لمستقبل صحي.",
"eReferralServiceHeader": "نظام الإحالة الإلكترونية في مستشفى حبيب: تبسيط عملية إحالة المرضى",
"eReferralServiceDescription": "نُسهّل عملية نقل المرضى بسلاسة إلى مستشفى حبيب من خلال نظام الإحالة الإلكترونية الآمن لدينا. نضمن استمرارية الرعاية لكل مريض.",
"bloodDonationServiceHeader": "تبرع بالدم، أنقذ الأرواح. تبرعك يُحدث فرقاً.",
"bloodDonationServiceDescription": "تبرّع بالدم، وأنقذ الأرواح. تبرعك يبعث الأمل. انضم لحملة التبرع بالدم وكن شريان حياة للمحتاجين. كل قطرة تُحدث فرقًا!",
"healthTrackersServiceHeader": "تتبّع مؤشراتك الحيوية بسهولة ويسر",
"healthTrackersServiceDescription": "أدخل بياناتك لمراقبة معدل ضربات القلب وضغط الدم بشكل مستمر، بالإضافة إلى ملخصات دقيقة لأنشطتك اليومية. ابقَ على اطلاع وحسّن صحتك بسهولة.",
"waterConsumptionServiceHeader": "حافظ على رطوبتك، حافظ على صحتك. تتبع كمية الماء التي تشربها يومياً بكل سهولة.",
"waterConsumptionServiceDescription": "أروِ عطشك، وتابع صحتك. راقب كمية الماء التي تتناولها يومياً بكل سهولة باستخدام تطبيقنا سهل الاستخدام، مما يضمن لك الترطيب الأمثل والصحة الجيدة.",
"smartWatchServiceHeader": "قم بمزامنة ساعتك الذكية مع تطبيقات الصحة",
"smartWatchServiceDescription": "قم بتوصيل ساعتك الذكية بسلاسة بتطبيقنا الصحي لتتبع البيانات بسهولة والحصول على رؤى شخصية.",
"liveChatServiceHeader": "مساعدة الخبراء على مدار الساعة طوال أيام الأسبوع\n\nمساعدة",
"liveChatServiceDescription": "هل تحتاج إلى مساعدة؟ تتيح لك خدمة الدردشة المباشرة لدينا التواصل مع فريق دعم الخبراء للإجابة على أي أسئلة لديك حول الميزات أو الإعدادات أو استكشاف الأخطاء وإصلاحها.",
"emergencyServiceHeader": "تسجيل الوصول إلى قسم الطوارئ، أسرع من أي وقت مضى. اتصل بالإسعاف / فريق الاستجابة السريعة على الفور",
"emergencyServiceDescription": "هل تواجه حالة طبية طارئة؟ سيارات الإسعاف وفرق الاستجابة السريعة لدينا جاهزة على مدار الساعة. بالإضافة إلى ذلك، يمكنك تسجيل دخولك إلى قسم الطوارئ بسرعة لتلقي رعاية أسرع.",
"homeHealthCareServiceHeader": "صحتك، في أبهى صورها. رعاية فائقة الجودة، تصلك إلى عتبة دارك.",
"homeHealthCareServiceDescription": "نقدم لكم رعاية صحية عالية الجودة تصلكم إلى عتبة منزلكم. ممرضات ذوات خبرة يقدمون رعاية حانية في راحة منزلكم.",
"profileOnlyText": "الملف الشخصي",
"information": "معلومة",
"noFavouriteDoctors": "ليس لديك أي قائمة مفضلة حتى الآن",
"addDoctors": "إضافة الأطباء",
"favouriteList": "قائمة المفضلة",
"later": "لاحقاً",
"cancelAppointmentConfirmMessage": "هل أنت متأكد من رغبتك في إلغاء هذا الموعد؟",
"acknowledged": "معترف به",
"searchLabResults": "بحث نتائج المختبر",
"callForAssistance": "اتصل للحصول على المساعدة الفورية"
}

@ -300,7 +300,7 @@
"vitalSignsSubTitle": "Reports",
"myMedical": "Active",
"myMedicalSubtitle": "Medications",
"myDoctor": "My Doctor",
"myDoctor": "My Doctors",
"myDoctorSubtitle": "List",
"eye": "Eye",
"eyeSubtitle": "Measurement",
@ -1611,5 +1611,33 @@
"sortByLocation": "Sort by location",
"sortByNearestLocation": "Sort by nearest to your location",
"giveLocationPermissionForNearestList": "Please grant location permission from app settings to see the nearest locations.",
"communicationLanguage": "Communication Language"
"communicationLanguage": "Communication Language",
"cmcServiceHeader": "Complete Health Checkup: Advanced diagnostics, Detailed Health insights",
"cmcServiceDescription": "Get detailed insights into your health with our advanced diagnostics. Understand your body better for a healthier future.",
"eReferralServiceHeader": "HMG Hospital E-Referral: Streamlined patient referrals",
"eReferralServiceDescription": "Facilitate seamless patient transfers to HMG with our secure e-referral system. Ensure continuity of care for every patient.",
"bloodDonationServiceHeader": "Give Blood, Save Lives. Your donation makes a difference.",
"bloodDonationServiceDescription": "Donate blood, empower lives. Your contribution creates hope. Join our blood drive and be a lifeline for those in need. Every drop counts!",
"healthTrackersServiceHeader": "Track Your Vitals with Ease and effortlessly ",
"healthTrackersServiceDescription": "Input your metrics for continuous heart rate monitoring, blood pressure and precise daily activity summaries. Stay informed and optimize your well-being with ease.",
"waterConsumptionServiceHeader": "Stay Hydrated, Stay Healthy. Track your daily water intake with ease.",
"waterConsumptionServiceDescription": "Quench your thirst, track your health. Effortlessly monitor your daily water intake with our user-friendly app, ensuring optimal hydration and well-being.",
"smartWatchServiceHeader": "Sync Your Smartwatch with Health Apps",
"smartWatchServiceDescription": "Seamlessly connect your smartwatch to our health app for effortless data tracking and personalized insights.",
"liveChatServiceHeader": "24/7 Expert\nAssistance",
"liveChatServiceDescription": "Need help ? Our live chat connects you with expert support for any questions about features, settings, or troubleshooting.",
"emergencyServiceHeader": "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly",
"emergencyServiceDescription": "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.",
"homeHealthCareServiceHeader": "Your Health, Elevated. Premium care, delivered to your doorstep.",
"homeHealthCareServiceDescription": "We bring quality healthcare to your doorstep. Experienced nurses providing compassionate care in the comfort of your home.",
"profileOnlyText": "Profile",
"information": "Information",
"noFavouriteDoctors": "You don't have any favourite list yet",
"addDoctors": "Add Doctors",
"favouriteList": "Favourite List",
"later": "Later",
"cancelAppointmentConfirmMessage": "Are you sure you want to cancel this appointment?",
"acknowledged": "Acknowledged",
"searchLabResults": "Search lab results",
"callForAssistance": "Call for immediate assistance"
}

@ -200,7 +200,7 @@ class ApiClientImp implements ApiClient {
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 1231755;
// body['PatientID'] = 1018977;
// body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0;
// body['SessionID'] = "45786230487560q";

@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.preProd;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT

@ -366,6 +366,10 @@ class AppAssets {
static const String emergencyService = '$pngBasePath/emergency_services_image.png';
static const String cmcService = '$pngBasePath/cmc_services_image.png';
static const String eReferralService = '$pngBasePath/ereferral_services_image.png';
static const String carParkingService = '$pngBasePath/Carparking_services_image.png';
static const String smartWatchService = '$pngBasePath/smartwatch_services_image.png';
static const String healthTrackersService = '$pngBasePath/healthtrackers_services_image.png';
static const String livechatService = '$pngBasePath/livechat_services_image.png';
static const String maleImg = '$pngBasePath/male_img.png';
static const String femaleImg = '$pngBasePath/female_img.png';

@ -15,37 +15,37 @@ class LoadingUtils {
static bool get isLoading => _isLoadingVisible;
static showFullScreenLoader({bool barrierDismissible = true, isSuccessDialog = false, String loadingText = "Loading, Please wait..."}) {
if (!_isLoadingVisible) {
_isLoadingVisible = true;
final context = _navigationService.navigatorKey.currentContext;
log("got the context in showFullScreenLoading");
if (context == null) return;
showDialog(
barrierDismissible: barrierDismissible,
context: context,
barrierColor: AppColors.blackColor.withOpacity(0.5),
useRootNavigator: false,
useSafeArea: false,
builder: (BuildContext context) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Material(
child: Center(
child: isSuccessDialog ? Utils.getSuccessWidget(loadingText: loadingText) : Utils.getLoadingWidget(loadingText: loadingText),
).paddingSymmetrical(24.w, 0),
),
);
}).then((value) {
_isLoadingVisible = false;
});
}
}
// static showFullScreenLoader({bool barrierDismissible = true, isSuccessDialog = false, String loadingText = "Loading, Please wait..."}) {
// if (!_isLoadingVisible) {
// _isLoadingVisible = true;
// final context = _navigationService.navigatorKey.currentContext;
// log("got the context in showFullScreenLoading");
// if (context == null) return;
//
// showDialog(
// barrierDismissible: barrierDismissible,
// context: context,
// barrierColor: AppColors.blackColor.withOpacity(0.5),
// useRootNavigator: false,
// useSafeArea: false,
// builder: (BuildContext context) {
// return Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.whiteColor,
// borderRadius: 20.h,
// hasShadow: false,
// ),
// child: Material(
// child: Center(
// child: isSuccessDialog ? Utils.getSuccessWidget(loadingText: loadingText) : Utils.getLoadingWidget(loadingText: loadingText),
// ).paddingSymmetrical(24.w, 0),
// ),
// );
// }).then((value) {
// _isLoadingVisible = false;
// });
// }
// }
static hideFullScreenLoader() {
if (!_isLoadingVisible) return;

@ -768,6 +768,7 @@ class Utils {
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 5.w,
children: [
Image.asset(AppAssets.mada, width: 35.h, height: 35.h),
Image.asset(

@ -73,13 +73,14 @@ extension EmailValidator on String {
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow,
double letterSpacing = 0}) =>
double letterSpacing = 0, bool isEnglishOnly = false}) =>
Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines,
overflow: textOverflow,
style: TextStyle(
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
fontSize: 9.f,
fontStyle: fontStyle ?? FontStyle.normal,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
@ -89,7 +90,7 @@ extension EmailValidator on String {
decorationColor: color ?? AppColors.blackColor),
);
Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0}) => Text(
Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0, bool isEnglishOnly = false,}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
@ -100,6 +101,7 @@ extension EmailValidator on String {
color: color ?? AppColors.blackColor,
letterSpacing: letterSpacing,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins',
),
);
@ -301,10 +303,11 @@ extension EmailValidator on String {
height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal),
);
Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text(
Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing, bool isEnglishOnly = false,}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
fontFamily: (isEnglishOnly ? "Poppins" : getIt.get<AppState>().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),
height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.f, letterSpacing: letterSpacing ?? -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
);

@ -862,17 +862,17 @@ class AuthenticationViewModel extends ChangeNotifier {
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();
LoaderBottomSheet.hideLoader();
_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 {
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
if (apiResponse.data["MessageStatus"] == 1) {
LoadingUtils.showFullScreenLoader(isSuccessDialog: true, loadingText: "Your medical file has been created successfully. \nPlease proceed to login.");
LoaderBottomSheet.showLoader(loadingText: "Your medical file has been created successfully. \nPlease proceed to login.");
//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();
LoaderBottomSheet.hideLoader();
// _navigationService.pushAndReplace(AppRoutes.loginScreen);
_navigationService.pushAndRemoveUntil(CustomPageRoute(page: LandingNavigation()), (r) => false);
_navigationService.push(CustomPageRoute(page: LoginScreen()));

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.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/location_util.dart';
@ -11,6 +12,7 @@ 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';
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_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/LaserCategoryType.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart';
@ -139,6 +141,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool applyFilters = false;
bool isWaitingAppointmentAvailable = false;
bool isPatientRescheduleAppointment = false;
bool isWaitingAppointmentSelected = false;
int waitingAppointmentProjectID = 0;
@ -157,7 +160,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
LaserCategoryType(11, 'retouch'),
];
List<LaserCategoryType> maleLaserCategory = [
LaserCategoryType(1, 'body'),
LaserCategoryType(1, 'bodyString'),
LaserCategoryType(2, 'face'),
LaserCategoryType(11, 'retouch'),
];
@ -289,6 +292,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
isContinueDentalPlan = false;
isChiefComplaintsListLoading = true;
isWaitingAppointmentSelected = false;
isPatientRescheduleAppointment = false;
bodyTypes = [maleLaserCategory, femaleLaserCategory];
// getLocation();
notifyListeners();
@ -427,6 +431,11 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
setIsPatientRescheduleAppointment(bool isPatientRescheduleAppointment) {
this.isPatientRescheduleAppointment = isPatientRescheduleAppointment;
notifyListeners();
}
void onTabChanged(int index) {
calculationID = null;
isGetDocForHealthCal = false;
@ -652,12 +661,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
// Check if doctor is favorite after getting profile
checkIsFavouriteDoctor(
patientID: _appState.getAuthenticatedUser()!.patientId!,
projectID: doctorsProfileResponseModel.projectID ?? 0,
clinicID: doctorsProfileResponseModel.clinicID ?? 0,
doctorID: doctorsProfileResponseModel.doctorID ?? 0,
);
if(_appState.isAuthenticated) {
checkIsFavouriteDoctor(
patientID: _appState.getAuthenticatedUser()!.patientId!,
projectID: doctorsProfileResponseModel.projectID ?? 0,
clinicID: doctorsProfileResponseModel.clinicID ?? 0,
doctorID: doctorsProfileResponseModel.doctorID ?? 0,
);
}
if (onSuccess != null) {
onSuccess(apiResponse);
@ -852,64 +863,114 @@ class BookAppointmentsViewModel extends ChangeNotifier {
print(failure);
onError!(failure.message);
},
(apiResponse) {
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
// onError!(apiResponse);
LoadingUtils.hideFullScreenLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: apiResponse.data["ErrorEndUserMessage"],
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();
},
onConfirmTap: () async {
navigationService.pop();
PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel = PatientAppointmentHistoryResponseModel(
appointmentNo: apiResponse.data["SameClinicApptList"][0]['AppointmentNo'],
clinicID: apiResponse.data["SameClinicApptList"][0]['ClinicID'],
projectID: apiResponse.data["SameClinicApptList"][0]['ProjectID'],
endDate: apiResponse.data["SameClinicApptList"][0]['EndTime'],
startTime: apiResponse.data["SameClinicApptList"][0]['StartTime'],
doctorID: apiResponse.data["SameClinicApptList"][0]['DoctorID'],
isLiveCareAppointment: apiResponse.data["SameClinicApptList"][0]['IsLiveCareAppointment'],
originalClinicID: 0,
originalProjectID: 0,
appointmentDate: apiResponse.data["SameClinicApptList"][0]['AppointmentDate'],
);
LoaderBottomSheet.showLoader(
loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!),
);
await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async {
if (isPatientRescheduleAppointment) {
PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel = PatientAppointmentHistoryResponseModel(
appointmentNo: apiResponse.data["SameClinicApptList"][0]['AppointmentNo'],
clinicID: apiResponse.data["SameClinicApptList"][0]['ClinicID'],
projectID: apiResponse.data["SameClinicApptList"][0]['ProjectID'],
endDate: apiResponse.data["SameClinicApptList"][0]['EndTime'],
startTime: apiResponse.data["SameClinicApptList"][0]['StartTime'],
doctorID: apiResponse.data["SameClinicApptList"][0]['DoctorID'],
isLiveCareAppointment: apiResponse.data["SameClinicApptList"][0]['IsLiveCareAppointment'],
originalClinicID: 0,
originalProjectID: 0,
appointmentDate: apiResponse.data["SameClinicApptList"][0]['AppointmentDate'],
);
await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async {
// LoaderBottomSheet.hideLoader();
Future.delayed(Duration(milliseconds: 50)).then((value) async {});
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingYourAppointment.tr());
await insertSpecificAppointment(
onError: (err) {},
onSuccess: (apiResp) async {
LoaderBottomSheet.hideLoader();
Future.delayed(Duration(milliseconds: 50)).then((value) async {});
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr());
await insertSpecificAppointment(
onError: (err) {},
onSuccess: (apiResp) async {
LoadingUtils.hideFullScreenLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr());
await Future.delayed(Duration(milliseconds: 4000)).then((value) {
LoadingUtils.hideFullScreenLoader();
Navigator.pushAndRemoveUntil(
navigationService.navigatorKey.currentContext!,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
showCommonBottomSheetWithoutHeight(
GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h),
callBackFunc: () {
setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(
navigationService.navigatorKey.currentContext!,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
});
});
});
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
loadingText: apiResponse.data["ErrorEndUserMessage"],
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();
},
onConfirmTap: () async {
navigationService.pop();
PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel = PatientAppointmentHistoryResponseModel(
appointmentNo: apiResponse.data["SameClinicApptList"][0]['AppointmentNo'],
clinicID: apiResponse.data["SameClinicApptList"][0]['ClinicID'],
projectID: apiResponse.data["SameClinicApptList"][0]['ProjectID'],
endDate: apiResponse.data["SameClinicApptList"][0]['EndTime'],
startTime: apiResponse.data["SameClinicApptList"][0]['StartTime'],
doctorID: apiResponse.data["SameClinicApptList"][0]['DoctorID'],
isLiveCareAppointment: apiResponse.data["SameClinicApptList"][0]['IsLiveCareAppointment'],
originalClinicID: 0,
originalProjectID: 0,
appointmentDate: apiResponse.data["SameClinicApptList"][0]['AppointmentDate'],
);
LoaderBottomSheet.showLoader(
loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!),
);
await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async {
LoaderBottomSheet.hideLoader();
Future.delayed(Duration(milliseconds: 50)).then((value) async {});
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingYourAppointment.tr());
await insertSpecificAppointment(
onError: (err) {},
onSuccess: (apiResp) async {
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
showCommonBottomSheetWithoutHeight(
GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h),
callBackFunc: () {
setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(
navigationService.navigatorKey.currentContext!,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
});
});
});
});
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data == null || apiResponse.data!.isEmpty) {
onError!("No free slots available".tr());
@ -946,7 +1007,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// onError!(apiResponse);
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
@ -982,22 +1043,28 @@ class BookAppointmentsViewModel extends ChangeNotifier {
await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async {
navigationService.pop();
Future.delayed(Duration(milliseconds: 50)).then((value) async {});
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr());
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingYourAppointment.tr());
await insertSpecificAppointment(
onError: (err) {},
onSuccess: (apiResp) async {
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr());
await Future.delayed(Duration(milliseconds: 4000)).then((value) {
LoadingUtils.hideFullScreenLoader();
Navigator.pushAndRemoveUntil(
navigationService.navigatorKey.currentContext!,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
showCommonBottomSheetWithoutHeight(
GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h),
callBackFunc: () {
setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(
navigationService.navigatorKey.currentContext!,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
});
});
});
@ -1491,7 +1558,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,

@ -61,4 +61,14 @@ class LaserBodyPart {
data['CategoryNameN'] = this.categoryNameN;
return data;
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is LaserBodyPart &&
runtimeType == other.runtimeType &&
id == other.id;
@override
int get hashCode => id.hashCode;
}

@ -919,8 +919,6 @@ class HmgServicesRepoImp implements HmgServicesRepo {
@override
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign() async {
Map<String, dynamic> requestBody = {
};
try {

@ -4,6 +4,7 @@ import 'dart:core';
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/common_models/data_points.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
@ -71,6 +72,9 @@ class LabViewModel extends ChangeNotifier {
List<TestDetails> uniqueTestsList = [];
List<String> indexedCharacterForUniqueTest = [];
List<TestDetails> filteredUniqueTestsList = [];
List<String> filteredIndexedCharacterForUniqueTest = [];
double maxY = 0.0;
double minY = double.infinity;
double maxX = double.infinity;
@ -84,6 +88,9 @@ class LabViewModel extends ChangeNotifier {
LabOrderResponseByAi? labOrderResponseByAi;
LabOrdersResponseByAi? labOrdersResponseByAi;
bool isLabAIAnalysisNeedsToBeShown = true;
bool isLabResultsHistoryShowMore = false;
LabViewModel({required this.labRepo, required this.errorHandlerService, required this.navigationService});
initLabProvider() {
@ -93,6 +100,8 @@ class LabViewModel extends ChangeNotifier {
labOrderTests.clear();
isLabOrdersLoading = true;
isLabResultsLoading = true;
isLabAIAnalysisNeedsToBeShown = true;
isLabResultsHistoryShowMore = false;
patientLabOrdersByClinic.clear();
patientLabOrdersByHospital.clear();
patientLabOrdersViewList.clear();
@ -111,6 +120,16 @@ class LabViewModel extends ChangeNotifier {
notifyListeners();
}
setIsLabAIAnalysisNeedsToBeShown(bool isLabAIAnalysisNeedsToBeShown) {
this.isLabAIAnalysisNeedsToBeShown = isLabAIAnalysisNeedsToBeShown;
notifyListeners();
}
setIsLabResultsHistoryShowMore() {
isLabResultsHistoryShowMore = !isLabResultsHistoryShowMore;
notifyListeners();
}
void setIsSortByClinic(bool value) {
isSortByClinic = value;
patientLabOrdersViewList = isSortByClinic ? patientLabOrdersByClinic : patientLabOrdersByHospital;
@ -218,7 +237,8 @@ class LabViewModel extends ChangeNotifier {
final clinicMap = <String, List<PatientLabOrdersResponseModel>>{};
final hospitalMap = <String, List<PatientLabOrdersResponseModel>>{};
if (query.isEmpty) {
// filteredLabOrders = List.from(patientLabOrders); // reset
filteredLabOrders = List.from(patientLabOrders); // reset
filteredUniqueTestsList = List.from(uniqueTestsList);
for (var order in patientLabOrders) {
final clinicKey = (order.clinicDescription ?? 'Unknown').trim();
clinicMap.putIfAbsent(clinicKey, () => []).add(order);
@ -234,7 +254,10 @@ class LabViewModel extends ChangeNotifier {
final descriptions = order.testDetails?.map((d) => d.description?.toLowerCase()).toList() ?? [];
return descriptions.any((desc) => desc != null && desc.contains(query.toLowerCase()));
}).toList();
// patientLabOrders = filteredLabOrders;
filteredUniqueTestsList = uniqueTestsList.where((test) {
final desc = test.description?.toLowerCase() ?? '';
return desc.contains(query.toLowerCase());
}).toList();
for (var order in filteredLabOrders) {
final clinicKey = (order.clinicDescription ?? 'Unknown').trim();
clinicMap.putIfAbsent(clinicKey, () => []).add(order);
@ -246,6 +269,14 @@ class LabViewModel extends ChangeNotifier {
patientLabOrdersByHospital = hospitalMap.values.toList();
patientLabOrdersViewList = isSortByClinic ? patientLabOrdersByClinic : patientLabOrdersByHospital;
}
// Rebuild filtered indexed characters
filteredIndexedCharacterForUniqueTest = [];
for (var test in filteredUniqueTestsList) {
String label = test.description ?? "";
if (label.isEmpty) continue;
if (filteredIndexedCharacterForUniqueTest.contains(label[0].toLowerCase())) continue;
filteredIndexedCharacterForUniqueTest.add(label[0].toLowerCase());
}
notifyListeners();
}
@ -280,6 +311,10 @@ class LabViewModel extends ChangeNotifier {
for (var element in uniqueTests) {
labOrderTests.add(element.description ?? "");
}
// Initialize filtered lists with full data
filteredUniqueTestsList = List.from(uniqueTestsList);
filteredIndexedCharacterForUniqueTest = List.from(indexedCharacterForUniqueTest);
}
Future<void> getLabResultsByAppointmentNo(
@ -396,7 +431,7 @@ class LabViewModel extends ChangeNotifier {
double lowRefenceValue = double.infinity;
String? flagForLowReferenceRange;
sortedResult.take(3).toList().reversed.forEach((element) {
sortedResult.toList().reversed.forEach((element) {
try {
var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
var resultValue = double.parse(element.resultValue!);
@ -787,7 +822,13 @@ class LabViewModel extends ChangeNotifier {
Future<void> getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder, required String loadingText}) async {
// LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic. \nPlease be patient, This might take some time.");
LoadingUtils.showFullScreenLoader(loadingText: loadingText);
// LoadingUtils.showFullScreenLoader(loadingText: loadingText);
LoaderBottomSheet.showLoader(
loadingText: loadingText,
showCloseButton: true,
onCloseTap: () {
setIsLabAIAnalysisNeedsToBeShown(false);
});
List<Map<String, dynamic>> results = [];
Map<String, dynamic> orderData = {"order_date": labOrder.orderDate ?? "", "clinic": labOrder.clinicDescription ?? "", "doctor": labOrder.doctorName ?? "", "results": []};
List<Map<String, dynamic>> testResults = [];
@ -810,16 +851,20 @@ class LabViewModel extends ChangeNotifier {
result.fold(
(failure) async {
LoadingUtils.hideFullScreenLoader();
await errorHandlerService.handleError(failure: failure);
if (isLabAIAnalysisNeedsToBeShown) {
await errorHandlerService.handleError(failure: failure);
}
},
(apiResponse) {
LoadingUtils.hideFullScreenLoader();
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
labOrdersResponseByAi = apiResponse.data;
navigationService.push(
MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()),
);
if (isLabAIAnalysisNeedsToBeShown) {
labOrdersResponseByAi = apiResponse.data;
navigationService.push(
MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()),
);
}
}
},
);
@ -828,7 +873,12 @@ class LabViewModel extends ChangeNotifier {
}
Future<void> getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult, required String loadingText}) async {
LoaderBottomSheet.showLoader(loadingText: loadingText);
LoaderBottomSheet.showLoader(
loadingText: loadingText,
showCloseButton: true,
onCloseTap: () {
setIsLabAIAnalysisNeedsToBeShown(false);
});
List<Map<String, dynamic>> results = [];
results.add({
"Description": recentLabResult.description ?? '',
@ -838,19 +888,24 @@ class LabViewModel extends ChangeNotifier {
"ReferanceRange": recentLabResult.referanceRange ?? '',
});
var payload = {"patient_id": currentlySelectedPatientOrder!.patientID, "language_id": langId, "lab_results": results};
// var payload = {"patient_id": currentlySelectedPatientOrder!.patientID, "language_id": langId, "lab_results": results};
var payload = {"patient_id": getIt.get<AppState>().getAuthenticatedUser()!.patientId, "language_id": langId, "lab_results": results};
final result = await labRepo.getPatientAiOverViewLabOrder(payload);
result.fold(
(failure) async {
LoaderBottomSheet.hideLoader();
await errorHandlerService.handleError(failure: failure);
if (isLabAIAnalysisNeedsToBeShown) {
await errorHandlerService.handleError(failure: failure);
}
},
(apiResponse) {
LoaderBottomSheet.hideLoader();
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
labOrderResponseByAi = apiResponse.data;
notifyListeners();
if (isLabAIAnalysisNeedsToBeShown) {
labOrderResponseByAi = apiResponse.data;
notifyListeners();
}
}
},
);

@ -87,6 +87,8 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
hmcCount = registeredDoctorMap.hmcSize;
hmgCount = registeredDoctorMap.hmgSize;
hospitalList!.sort((a, b) => num.parse(a.distanceInKMs!).compareTo(num.parse(b.distanceInKMs!)));
getDisplayList();
}

@ -513,7 +513,10 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
try {
final list = response['PatientDoctorAppointmentResultList'];
final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientAppointmentHistoryResponseModel>();
List<PatientAppointmentHistoryResponseModel> appointmentsList =
list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientAppointmentHistoryResponseModel>();
// appointmentsList.removeWhere((element) => element.isActiveDoctorProfile == false);
apiResponse = GenericApiModel<List<PatientAppointmentHistoryResponseModel>>(
messageStatus: messageStatus,

@ -94,6 +94,10 @@ class MyAppointmentsViewModel extends ChangeNotifier {
selectedTabIndex = index;
start = null;
end = null;
// if (index == 0) {
// filteredAppointmentList.clear();
// filteredAppointmentList.addAll(patientAppointmentsHistoryList);
// }
notifyListeners();
}

@ -12,6 +12,10 @@ abstract class NotificationsRepo {
required int pagingSize,
required int currentPage,
});
Future<Either<Failure, GenericApiModel<List<NotificationResponseModel>>>> markAsRead({
required int notificationID,
});
}
class NotificationsRepoImp implements NotificationsRepo {
@ -75,4 +79,38 @@ class NotificationsRepoImp implements NotificationsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<NotificationResponseModel>>>> markAsRead({required int notificationID}) async {
Map<String, dynamic> mapDevice = {"NotificationPoolID": notificationID};
try {
GenericApiModel<List<NotificationResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
PUSH_NOTIFICATION_SET_MESSAGES_FROM_POOL_AS_READ,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<List<NotificationResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: [],
);
} 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()));
}
}
}

@ -7,13 +7,15 @@ class NotificationsViewModel extends ChangeNotifier {
bool isNotificationsLoading = false;
bool hasMoreNotifications = true;
int unreadNotificationsCount = 0;
NotificationsRepo notificationsRepo;
ErrorHandlerService errorHandlerService;
List<NotificationResponseModel> notificationsList = [];
int currentPage = 0;
int pagingSize = 14;
int pagingSize = 15;
int notificationStatusID = 2; // Default to status 2 (e.g., unread)
NotificationsViewModel({
@ -46,6 +48,7 @@ class NotificationsViewModel extends ChangeNotifier {
Function(String)? onError,
}) async {
isNotificationsLoading = true;
unreadNotificationsCount = 0;
notifyListeners();
final result = await notificationsRepo.getAllNotifications(
@ -78,6 +81,11 @@ class NotificationsViewModel extends ChangeNotifier {
}
notificationsList.addAll(newNotifications);
for (var notification in notificationsList) {
if (notification.isRead == false) {
unreadNotificationsCount++;
}
}
currentPage++;
notifyListeners();
@ -89,6 +97,21 @@ class NotificationsViewModel extends ChangeNotifier {
);
}
Future<void> markAsRead({
int notificationID = 0,
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
final result = await notificationsRepo.markAsRead(notificationID: notificationID);
result.fold(
(failure) async {},
(apiResponse) {
refreshNotifications();
},
);
}
Future<void> refreshNotifications({
Function(dynamic)? onSuccess,
Function(String)? onError,

@ -1597,6 +1597,7 @@ abstract class LocaleKeys {
static const timeFor = 'timeFor';
static const hmgPolicies = 'hmgPolicies';
static const darkMode = 'darkMode';
static const featureComingSoonDescription = 'featureComingSoonDescription';
static const generateAiAnalysisResult = 'generateAiAnalysisResult';
static const ratings = 'ratings';
static const hmgPharmacyText = 'hmgPharmacyText';
@ -1611,6 +1612,33 @@ abstract class LocaleKeys {
static const timeForFirstReminder = 'timeForFirstReminder';
static const reminderRemovalNote = 'reminderRemovalNote';
static const communicationLanguage = 'communicationLanguage';
static const featureComingSoonDescription = 'featureComingSoonDescription';
static const cmcServiceHeader = 'cmcServiceHeader';
static const cmcServiceDescription = 'cmcServiceDescription';
static const eReferralServiceHeader = 'eReferralServiceHeader';
static const eReferralServiceDescription = 'eReferralServiceDescription';
static const bloodDonationServiceHeader = 'bloodDonationServiceHeader';
static const bloodDonationServiceDescription = 'bloodDonationServiceDescription';
static const healthTrackersServiceHeader = 'healthTrackersServiceHeader';
static const healthTrackersServiceDescription = 'healthTrackersServiceDescription';
static const waterConsumptionServiceHeader = 'waterConsumptionServiceHeader';
static const waterConsumptionServiceDescription = 'waterConsumptionServiceDescription';
static const smartWatchServiceHeader = 'smartWatchServiceHeader';
static const smartWatchServiceDescription = 'smartWatchServiceDescription';
static const liveChatServiceHeader = 'liveChatServiceHeader';
static const liveChatServiceDescription = 'liveChatServiceDescription';
static const emergencyServiceHeader = 'emergencyServiceHeader';
static const emergencyServiceDescription = 'emergencyServiceDescription';
static const homeHealthCareServiceHeader = 'homeHealthCareServiceHeader';
static const homeHealthCareServiceDescription = 'homeHealthCareServiceDescription';
static const profileOnlyText = 'profileOnlyText';
static const information = 'information';
static const noFavouriteDoctors = 'noFavouriteDoctors';
static const addDoctors = 'addDoctors';
static const favouriteList = 'favouriteList';
static const later = 'later';
static const cancelAppointmentConfirmMessage = 'cancelAppointmentConfirmMessage';
static const acknowledged = 'acknowledged';
static const searchLabResults = 'searchLabResults';
static const callForAssistance = 'callForAssistance';
}

@ -36,7 +36,9 @@ import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart
import 'package:hmg_patient_app_new/presentation/ask_doctor/doctor_response_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -73,10 +75,10 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
void initState() {
scheduleMicrotask(() async {
if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
CalenderUtilsNew calendarUtils = await CalenderUtilsNew.instance;
var doesExist = await calendarUtils.checkIfEventExist("${widget.patientAppointmentHistoryResponseModel.appointmentNo}");
myAppointmentsViewModel.setAppointmentReminder(doesExist, widget.patientAppointmentHistoryResponseModel);
setState(() {});
// CalenderUtilsNew calendarUtils = await CalenderUtilsNew.instance;
// var doesExist = await calendarUtils.checkIfEventExist("${widget.patientAppointmentHistoryResponseModel.appointmentNo}");
// myAppointmentsViewModel.setAppointmentReminder(doesExist, widget.patientAppointmentHistoryResponseModel);
// setState(() {});
}
});
@ -148,41 +150,57 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
});
},
onCancelTap: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr(context: context));
await myAppointmentsViewModel.cancelAppointment(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentCancelledSuccessfully.tr(context: context)),
callBackFunc: () {
Navigator.of(context).pop();
},
title: "",
isCloseButtonVisible: true,
isDismissible: false,
isFullScreen: false,
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
title: "",
isCloseButtonVisible: true,
isDismissible: false,
isFullScreen: false,
);
});
var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", );
setState(() {
myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
});
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.cancelAppointmentConfirmMessage.tr(context: context),
isShowActionButtons: true,
onCancelTap: () {
Navigator.of(context).pop();
},
onConfirmTap: () async {
Navigator.of(context).pop();
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr(context: context));
myAppointmentsViewModel.onTabChange(0);
myAppointmentsViewModel.updateListWRTTab(0);
await myAppointmentsViewModel.cancelAppointment(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) async {
LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheet(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentCancelledSuccessfully.tr(context: context)),
callBackFunc: (str) {
Navigator.of(context).pop();
},
title: "",
isCloseButtonVisible: false,
isDismissible: false,
isFullScreen: false,
isAutoDismiss: true,
height: ResponsiveExtension.screenHeight * 0.3,
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
title: "", isCloseButtonVisible: true, isDismissible: false, isFullScreen: false);
});
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
// var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", );
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// });
},
onRescheduleTap: () async {
openDoctorScheduleCalendar();
@ -229,16 +247,22 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
// myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheetWithoutHeight(
title: "",
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)),
callBackFunc: () {
Navigator.of(context).pop();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
isCloseButtonVisible: false,
isAutoDismiss: true
);
});
},
@ -987,12 +1011,13 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL,
doctorTitle: widget.patientAppointmentHistoryResponseModel.doctorTitle,
name: widget.patientAppointmentHistoryResponseModel.doctorNameObj,
nationalityFlagURL: "https://hmgwebservices.com/Images/flag/SYR.png",
nationalityFlagURL: "",
speciality: [],
clinicName: widget.patientAppointmentHistoryResponseModel.clinicName,
projectName: widget.patientAppointmentHistoryResponseModel.projectName,
);
bookAppointmentsViewModel.setSelectedDoctor(doctor);
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(true);
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingDoctorSchedulePleaseWait.tr(context: context));
await bookAppointmentsViewModel.getDoctorFreeSlots(
isBookingForLiveCare: false,
@ -1036,17 +1061,19 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
Navigator.of(context).pop();
},
title: "",
height: ResponsiveExtension.screenHeight * 0.3,
isCloseButtonVisible: true,
isDismissible: false,
isFullScreen: false,
}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false,
isSuccessDialog: true);
});
// LoaderBottomSheet.hideLoader();
case 15:
break;
getIt.get<DialogService>().showCommonBottomSheetWithoutH(
message: LocaleKeys.upcomingPaymentPending.tr(context: context),
label: LocaleKeys.notice.tr(),
onOkPressed: () {},
okLabel: "confirm",
cancelLabel: LocaleKeys.acknowledged.tr(context: context),
isConfirmButton: true,
);
case 20:
myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true);
Navigator.of(context).push(

@ -55,36 +55,40 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.appointmentsList.tr(context: context),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 16.h),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.completed.tr(context: context)),
],
onTabChange: (index) {
setState(() {
expandedIndex = null;
});
myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index);
context.read<DateRangeSelectorRangeViewModel>().flush();
},
).paddingSymmetrical(24.h, 0.h),
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM);
}),
],
body: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return CollapsingListView(
title: LocaleKeys.appointmentsList.tr(context: context),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 16.h),
CustomTabBar(
initialIndex: myAppointmentsVM.selectedTabIndex,
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.completed.tr(context: context)),
],
onTabChange: (index) {
setState(() {
expandedIndex = null;
});
myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index);
context.read<DateRangeSelectorRangeViewModel>().flush();
},
).paddingSymmetrical(24.h, 0.h),
// Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
// return
getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM),
// }),
],
),
),
),
),
);
}),
);
}
@ -144,7 +148,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
? myAppointmentsVM.patientAppointmentsViewList.length
: 1,
itemBuilder: (context, index) {
final isExpanded = expandedIndex == index;
final isExpanded = myAppointmentsVM.selectedTabIndex == 1 ? true : expandedIndex == index;
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),

@ -218,8 +218,17 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(labelText: "${group.length} ${'doctors'}"),
Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
AppCustomChipWidget(
richText: Row(
children: [
"${group.length} ".toText10(isEnglishOnly: true),
"${LocaleKeys.doctors.tr(context: context)} ".toText10()
],
),
// labelText: "${group.length} ${'doctors'}",
isEnglishOnly: true,
),
Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
],
),
SizedBox(height: 8.h),
@ -289,10 +298,10 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
flex: 2,
child: CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 1.0) : AppColors.primaryRedColor,
iconSize: 16.h,
text: LocaleKeys.viewProfile.tr(context: context),
onPressed: () async {
onPressed: doctor?.isActiveDoctorProfile == false ? () {} : () async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
clinicID: doctor?.clinicID ?? 0,
projectID: doctor?.projectID ?? 0,
@ -323,9 +332,9 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
);
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
backgroundColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : AppColors.secondaryLightRedColor,
borderColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 0.01) : AppColors.secondaryLightRedColor,
textColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 1.0) : AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,

@ -148,8 +148,8 @@ class AppointmentCard extends StatelessWidget {
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow),
SizedBox(height: 2.h),
(isFoldable || isTablet)
? "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor)
: "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor),
? "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true)
: "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
],
),
).circle(100).toShimmer2(isShow: isLoading),
@ -456,7 +456,7 @@ class AppointmentCard extends StatelessWidget {
doctorImageURL: patientAppointmentHistoryResponseModel.doctorImageURL,
doctorTitle: patientAppointmentHistoryResponseModel.doctorTitle,
name: patientAppointmentHistoryResponseModel.doctorNameObj,
nationalityFlagURL: 'https://hmgwebservices.com/Images/flag/SYR.png',
nationalityFlagURL: '',
speciality: [],
clinicName: patientAppointmentHistoryResponseModel.clinicName,
projectName: patientAppointmentHistoryResponseModel.projectName,

@ -1,19 +1,27 @@
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/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/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/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'dart:ui' as ui;
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class AppointmentDoctorCard extends StatelessWidget {
const AppointmentDoctorCard(
{super.key, required this.patientAppointmentHistoryResponseModel, required this.onRescheduleTap, required this.onCancelTap, required this.onAskDoctorTap, this.renderWidgetForERDisplay = false});
@ -66,7 +74,7 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow),
SizedBox(height: 2.h),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}".toText11(isBold: true, color: AppColors.textColor),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
],
),
).circle(100),
@ -75,6 +83,7 @@ class AppointmentDoctorCard extends StatelessWidget {
),
SizedBox(width: 16.w),
Expanded(
flex: 9,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -120,6 +129,39 @@ class AppointmentDoctorCard extends StatelessWidget {
],
),
),
Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
selectedDoctor.nationalityFlagURL = "";
selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
getIt.get<BookAppointmentsViewModel>().setSelectedDoctor(selectedDoctor);
LoaderBottomSheet.showLoader();
await getIt.get<BookAppointmentsViewModel>().getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
})
],
),
SizedBox(height: 8.h),

@ -84,7 +84,7 @@ class _SavedLogin extends State<SavedLogin> {
SizedBox(height: 16.h),
appState.getSelectDeviceByImeiRespModelElement != null
? appState.getSelectDeviceByImeiRespModelElement!.name!.toCamelCase
.toText26(isBold: true, height: 26 / 36, color: AppColors.textColor)
.toText26(isBold: true, height: 26 / 36, color: AppColors.textColor, isEnglishOnly: true)
: SizedBox(),
SizedBox(height: 24.h),
Container(
@ -206,7 +206,7 @@ class _SavedLogin extends State<SavedLogin> {
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.textColor,
textColor: Colors.white,
icon: AppAssets.sms),
),
Row(

@ -19,6 +19,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca
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/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.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/doctor_profile_page.dart';
@ -119,18 +120,30 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
if (appState.isAuthenticated) ...[
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return myAppointmentsVM.isPatientMyDoctorsLoading
? Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
? SizedBox(
height: 110.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 5,
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.w, right: 24.w),
itemBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 64.w,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.r),
SizedBox(height: 8.h),
("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
],
("Dr. John").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
],
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.h),
),
)
: myAppointmentsVM.patientMyDoctorsList.isEmpty
? SizedBox()
@ -138,7 +151,25 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.isAuthenticated) ...[],
LocaleKeys.recentVisits.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.recentVisits.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
Row(
children: [
LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyDoctorsPage(),
),
);
}),
],
),
SizedBox(height: 16.h),
SizedBox(
height: 110.h,
@ -264,7 +295,46 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
// Show empty state or actual list
return myAppointmentsVM.patientFavouriteDoctorsList.isEmpty
? SizedBox()
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
LocaleKeys.favouriteList.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
padding: EdgeInsets.all(16.h),
child: Column(
children: [
Center(
child: LocaleKeys.noFavouriteDoctors.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
),
SizedBox(height: 12.h),
CustomButton(
text: LocaleKeys.add.tr(context: context),
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: SearchDoctorByName()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
height: 40.h,
),
],
),
).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 24.h),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -659,7 +729,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
return SizedBox.shrink();
}
void _handleSortByLocationToggle(bool value, AppointmentViaRegionViewmodel regionVM) {
void _handleSortByLocationToggle(bool value, AppointmentViaRegionViewmodel regionVM) {
if (value) {
final locationUtils = getIt.get<LocationUtils>();
locationUtils.getLocation(

@ -33,17 +33,16 @@ class DoctorProfilePage extends StatelessWidget {
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.doctorProfile.tr(),
trailing: Consumer<BookAppointmentsViewModel>(
title: LocaleKeys.profileOnlyText.tr(),
trailing: appState.isAuthenticated ? Consumer<BookAppointmentsViewModel>(
builder: (context, viewModel, child) {
return SizedBox(
width: 24.h,
height: 24.h,
child: Utils.buildSvgWithAssets(
icon: viewModel.isFavouriteDoctor ? AppAssets.bookmark_filled_icon : AppAssets.bookmark_icon,
width: 24.h,
height: 24.h,
iconColor: viewModel.isFavouriteDoctor ? AppColors.primaryRedColor : AppColors.textColor,
child: Icon(
viewModel.isFavouriteDoctor ? Icons.bookmark : Icons.bookmark_border_outlined,
size: 36.h,
color: viewModel.isFavouriteDoctor ? AppColors.primaryRedColor : AppColors.textColor,
).onPress(() async {
viewModel.toggleFavouriteDoctor();
await viewModel.insertFavouriteDoctor(
@ -68,7 +67,7 @@ class DoctorProfilePage extends StatelessWidget {
}),
);
},
),
) : SizedBox.shrink(),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -179,9 +178,10 @@ class DoctorProfilePage extends StatelessWidget {
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
LocaleKeys.docInfo.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor),
LocaleKeys.information.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor),
SizedBox(height: 6.h),
bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo!.toText12(fontWeight: FontWeight.w600, color: AppColors.greyTextColor),
(bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "").trim().toText12(fontWeight: FontWeight.w600, color: AppColors.greyTextColor),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.h, 0.h),
),
@ -199,7 +199,12 @@ class DoctorProfilePage extends StatelessWidget {
bookAppointmentsViewModel.selectedDoctor.speciality = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty;
bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty;
bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName;
bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName;
bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL;
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL;
bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription;
bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName;
LoaderBottomSheet.showLoader();
bookAppointmentsViewModel.isLiveCareSchedule
? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots(

@ -84,7 +84,7 @@ class LaserAppointment extends StatelessWidget {
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, LocaleKeys.malE.tr()),
CustomTabBarModel(null, "Female"),
CustomTabBarModel(null, LocaleKeys.femaleGender.tr()),
],
onTabChange: (index) {
var viewmodel = context.read<BookAppointmentsViewModel>();
@ -93,6 +93,7 @@ class LaserAppointment extends StatelessWidget {
viewmodel.getLaserClinic();
},
),
SizedBox(height: 8.h),
Selector<BookAppointmentsViewModel, int>(
selector: (_, vm) => vm.selectedBodyTypeIndex,
builder: (_, bodyType, __) {
@ -110,6 +111,7 @@ class LaserAppointment extends StatelessWidget {
});
},
),
SizedBox(height: 8.h),
Selector<BookAppointmentsViewModel, List<LaserBodyPart>>(selector: (_, vm) => vm.laserBodyPartsList, builder:(_, parts,__){
return BodyPartsListing(
parts: parts,

@ -1,9 +1,12 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/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/book_appointments/models/laser_body_parts_data.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/laser_body_parts.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:provider/provider.dart';
@ -70,7 +73,7 @@ class BodyPartsListing extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 97 / 97,
aspectRatio: 130 / 120,
child: FittedBox(
fit: BoxFit.fitWidth,
child: Stack(
@ -93,12 +96,12 @@ class BodyPartsListing extends StatelessWidget {
Container(
width: 18.h,
height: 18.h,
child: Icon(Icons.done,
color: Colors.white, size: 12.h),
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
borderRadius: BorderRadius.circular(30.h),
),
child: Icon(Icons.done,
color: Colors.white, size: 12.h),
),
],
),
@ -106,17 +109,19 @@ class BodyPartsListing extends StatelessWidget {
),
SizedBox(height: 6.h),
Expanded(
child: Text(
context
.read<BookAppointmentsViewModel>()
.getLaserProcedureNameWRTLanguage(parts[index]),
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
color: Color(0xff2B353E),
letterSpacing: -0.48,
child: Center(
child: Column(
children: [
context.read<BookAppointmentsViewModel>().getLaserProcedureNameWRTLanguage(parts[index]).toText12(isBold: true, color: AppColors.textColor, isCenter: true, maxLine: 2),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
"${LocaleKeys.mins.tr()}: ".toText12(isBold: true, color: AppColors.textColor, isCenter: true, maxLine: 2),
parts[index].timeDuration!.toText12(isBold: true, color: AppColors.textColor, isCenter: true, maxLine: 2),
],
),
],
),
maxLines: 1,
),
),
],

@ -90,12 +90,12 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
.toString()
.toText16(isBold: true, maxlines: 1),
SizedBox(width: 12.w),
Image.network(
(bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty )? Image.network(
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h,
height: 15.h,
fit: BoxFit.cover,
),
) : SizedBox.shrink(),
],
),
SizedBox(height: 2.h),
@ -316,34 +316,42 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
}
void initiateBookAppointment() async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr(context: context));
// LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
LoaderBottomSheet.showLoader(loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
if (bookAppointmentsViewModel.isLiveCareSchedule) {
await bookAppointmentsViewModel.insertSpecificAppointmentForLiveCare(onError: (err) {
print(err.data["ErrorEndUserMessage"]);
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
}, onSuccess: (apiResp) async {
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr());
await Future.delayed(Duration(milliseconds: 4000)).then((value) {
LoadingUtils.hideFullScreenLoader();
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr());
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()),
callBackFunc: () {
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
});
});
} else {
//TODO: Add patient Derma package check API Here
await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) {
// print(err.data["ErrorEndUserMessage"]);
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
@ -357,19 +365,25 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
isCloseButtonVisible: true,
);
}, onSuccess: (apiResp) async {
LoadingUtils.hideFullScreenLoader();
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr());
await Future.delayed(Duration(milliseconds: 4000)).then((value) {
LoadingUtils.hideFullScreenLoader();
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h),
callBackFunc: () {
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
});
});
}

@ -1132,6 +1132,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? "";
}
if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) {
bookAppointmentsViewModel.setProjectID(id);
if (appState.isAuthenticated) {
initDentalAppointment();
return SizedBox.shrink();

@ -76,7 +76,7 @@ class DoctorCard extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false),
SizedBox(height: 2.h),
"${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
"${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
],
),
).circle(100).toShimmer2(isShow: isLoading),
@ -134,6 +134,7 @@ class DoctorCard extends StatelessWidget {
// richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot))
// .toText10(isEnglishOnly: true, color: AppColors.textColor),
// backgroundColor: AppColors.successColor,
isEnglishOnly: true,
textColor: AppColors.textColor,
).toShimmer2(isShow: isLoading)
: SizedBox.shrink()

@ -20,7 +20,7 @@ class DoctorRatingDetails extends StatelessWidget {
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.ceilToDouble().toString().toText44(isBold: true, isEnglishOnly: true),
bookAppointmentsVM.doctorsProfileResponseModel.decimalDoctorRate!.toString().toText44(isBold: true, isEnglishOnly: true),
SizedBox(height: 4.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,

@ -385,7 +385,7 @@ class _CmcSelectionReviewPageState extends State<CmcSelectionReviewPage> {
isCloseButtonVisible: false,
isDismissible: false,
isFullScreen: false,
);
isAutoDismiss: true);
}
void _handleConfirm() {

@ -39,8 +39,8 @@ class ContactUs extends StatelessWidget {
checkInOptionCard(
AppAssets.call_fill,
LocaleKeys.callNow.tr(),
// LocaleKeys.viewNearestHMGLocationsviewNearestHMGLocations.tr(),
"Call for immediate assistance",
LocaleKeys.callForAssistance.tr(),
// "Call for immediate assistance",
).onPress(() {
launchUrl(Uri.parse("tel://" + "+966 92 006 6666"));
}),
@ -83,6 +83,8 @@ class ContactUs extends StatelessWidget {
LocaleKeys.liveChatWithHMG.tr(),
).onPress(() {
if (getIt.get<AppState>().isAuthenticated) {
Navigator.of(context).pop();
contactUsViewModel.getLiveChatProjectsList();
Navigator.of(context).push(
CustomPageRoute(
page: LiveChatPage(),
@ -93,9 +95,9 @@ class ContactUs extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.liveChat.tr(),
serviceHeader: "HMG Hospital E-Referral: Streamlined patient referrals".needTranslation,
serviceDescription: "Facilitate seamless patient transfers to HMG with our secure e-referral system. Ensure continuity of care for every patient.".needTranslation,
serviceImage: AppAssets.eReferralService),
serviceHeader: LocaleKeys.liveChatServiceHeader.tr(),
serviceDescription: LocaleKeys.liveChatServiceDescription.tr(),
serviceImage: AppAssets.livechatService),
),
);
}

@ -121,7 +121,9 @@ class LiveChatPage extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM")
("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}"
// "\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM"
)
.toText14(isBold: true, color: contactUsVM.selectedLiveChatProjectIndex == index ? Colors.white : AppColors.textColor),
Transform.flip(
flipX: getIt.get<AppState>().isArabic(),

@ -21,6 +21,7 @@ class RrtRequestTypeSelect extends StatelessWidget {
return Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.rapidResponseTeam.tr(context: context).toText20(color: AppColors.textColor, isBold: true),
SizedBox(
@ -32,10 +33,9 @@ class RrtRequestTypeSelect extends StatelessWidget {
borderRadius: 20.r,
),
child: OptionSelection(context)),
SizedBox(
height: 24.h,
),
SizedBox(height: 16.h),
termsAndCondition(context, emergencyServicesVM),
SizedBox(height: 16.h),
],
).paddingSymmetrical(24.w, 0.h),
bottomPriceContent(context, emergencyServicesVM)
@ -122,15 +122,7 @@ class RrtRequestTypeSelect extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 6.w,
children: [
Image.asset(AppAssets.mada, width: 24.h, height: 24.h),
Image.asset(AppAssets.visa, width: 24.h, height: 24.h),
Image.asset(AppAssets.mastercard, width: 24.h, height: 24.h),
Image.asset(AppAssets.applePay, width: 24.h, height: 24.h),
],
),
Utils.getPaymentMethods(),
Column(
children: [
Divider(
@ -149,7 +141,9 @@ class RrtRequestTypeSelect extends StatelessWidget {
CustomButton(text: LocaleKeys.next.tr(), onPressed: () {
Navigator.pop(context);
emergencyServicesVM.openRRT();
})
},
isDisabled: !emergencyServicesVM.agreedToTermsAndCondition,
)
],
).paddingAll(24.h),
),
@ -236,7 +230,6 @@ class RrtRequestTypeSelect extends StatelessWidget {
value: emergencyServicesVM.agreedToTermsAndCondition,
checkColor: AppColors.whiteColor,
fillColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
print("the state is ${states}");
if (states.contains(WidgetState.selected)) {
return AppColors.errorColor;
}

@ -92,7 +92,10 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
),
),
SizedBox(width: 12.h),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h),
),
],
).onPress(() {
if(appState.isAuthenticated) {
@ -158,8 +161,8 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.emergencyServices.tr(),
serviceHeader: "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly".needTranslation,
serviceDescription: "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.".needTranslation,
serviceHeader: LocaleKeys.emergencyServiceHeader.tr(),
serviceDescription: LocaleKeys.emergencyServiceDescription.tr(),
serviceImage: AppAssets.emergencyService),
),
);
@ -188,7 +191,10 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
),
),
SizedBox(width: 12.h),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h),
),
],
).onPress(() {
context.read<EmergencyServicesViewModel>().navigateToNearestERPage();
@ -216,7 +222,7 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
),
),
SizedBox(width: 12.h),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h)),
],
).onPress(() {
if(appState.isAuthenticated) {
@ -308,8 +314,8 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.emergencyServices.tr(),
serviceHeader: "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly".needTranslation,
serviceDescription: "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.".needTranslation,
serviceHeader: LocaleKeys.emergencyServiceHeader.tr(),
serviceDescription: LocaleKeys.emergencyServiceDescription.tr(),
serviceImage: AppAssets.emergencyService),
),
);
@ -338,7 +344,7 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
),
),
SizedBox(width: 12.h),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h)),
],
).onPress(() {
if(appState.isAuthenticated) {
@ -408,8 +414,8 @@ class _EmergencyServicesPageState extends State<EmergencyServicesPage> {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.emergencyServices.tr(),
serviceHeader: "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly".needTranslation,
serviceDescription: "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.".needTranslation,
serviceHeader: LocaleKeys.emergencyServiceHeader.tr(),
serviceDescription: LocaleKeys.emergencyServiceDescription.tr(),
serviceImage: AppAssets.emergencyService),
),
);

@ -320,6 +320,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
},
isFullScreen: false,
isCloseButtonVisible: true,
isAutoDismiss: true
);
},
onError: (err) {

@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart';
@ -20,6 +21,7 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_mode
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart';
@ -80,7 +82,11 @@ class ServicesPage extends StatelessWidget {
AppAssets.appointment_calendar_icon,
bgColor: AppColors.bookAppointment,
true,
route: AppRoutes.bookAppointmentPage,
route: null,
onTap: () {
getIt.get<BookAppointmentsViewModel>().onTabChanged(0);
Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage()));
}
),
HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) {
@ -90,8 +96,8 @@ class ServicesPage extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.comprehensiveMedicalCheckup.tr(),
serviceHeader: "Complete Health Checkup: Advanced diagnostics, Detailed Health insights".needTranslation,
serviceDescription: "Get detailed insights into your health with our advanced diagnostics. Understand your body better for a healthier future.".needTranslation,
serviceHeader: LocaleKeys.cmcServiceHeader.tr(),
serviceDescription: LocaleKeys.cmcServiceDescription.tr(),
serviceImage: AppAssets.cmcService),
),
);
@ -150,8 +156,8 @@ class ServicesPage extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.eReferralServices.tr(),
serviceHeader: "HMG Hospital E-Referral: Streamlined patient referrals".needTranslation,
serviceDescription: "Facilitate seamless patient transfers to HMG with our secure e-referral system. Ensure continuity of care for every patient.".needTranslation,
serviceHeader: LocaleKeys.eReferralServiceHeader.tr(),
serviceDescription: LocaleKeys.eReferralServiceDescription.tr(),
serviceImage: AppAssets.eReferralService),
),
);
@ -177,8 +183,8 @@ class ServicesPage extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.bloodDonation.tr(),
serviceHeader: "Give Blood, Save Lives. Your donation makes a difference.".needTranslation,
serviceDescription: "Donate blood, empower lives. Your contribution creates hope. Join our blood drive and be a lifeline for those in need. Every drop counts!".needTranslation,
serviceHeader: LocaleKeys.bloodDonationServiceHeader.tr(),
serviceDescription: LocaleKeys.bloodDonationServiceDescription.tr(),
serviceImage: AppAssets.bloodDonationService),
),
);
@ -270,9 +276,9 @@ class ServicesPage extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.healthTrackers.tr(),
serviceHeader: "HMG Hospital E-Referral: Streamlined patient referrals".needTranslation,
serviceDescription: "Facilitate seamless patient transfers to HMG with our secure e-referral system. Ensure continuity of care for every patient.".needTranslation,
serviceImage: AppAssets.eReferralService),
serviceHeader: LocaleKeys.healthTrackersServiceHeader.tr(),
serviceDescription: LocaleKeys.healthTrackersServiceDescription.tr(),
serviceImage: AppAssets.healthTrackersService),
),
);
}
@ -311,8 +317,8 @@ class ServicesPage extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.waterConsumption.tr(),
serviceHeader: "Stay Hydrated, Stay Healthy. Track your daily water intake with ease.".needTranslation,
serviceDescription: "Quench your thirst, track your health. Effortlessly monitor your daily water intake with our user-friendly app, ensuring optimal hydration and well-being.".needTranslation,
serviceHeader: LocaleKeys.waterConsumptionServiceHeader.tr(),
serviceDescription: LocaleKeys.waterConsumptionServiceDescription.tr(),
serviceImage: AppAssets.waterConsumptionService),
),
);
@ -353,9 +359,9 @@ class ServicesPage extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.smartWatches.tr(),
serviceHeader: "HMG Hospital E-Referral: Streamlined patient referrals".needTranslation,
serviceDescription: "Facilitate seamless patient transfers to HMG with our secure e-referral system. Ensure continuity of care for every patient.".needTranslation,
serviceImage: AppAssets.eReferralService),
serviceHeader: LocaleKeys.smartWatchServiceHeader.tr(),
serviceDescription: LocaleKeys.smartWatchServiceDescription.tr(),
serviceImage: AppAssets.smartWatchService),
),
);
}

@ -1,6 +1,8 @@
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/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/data/service_card_data.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -123,8 +125,8 @@ class LandingPageData {
ServiceCardData(
serviceName: "my_doctors",
icon: AppAssets.my_doctors_icon,
title: "My",
subtitle: "Doctors",
title: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctor : "My",
subtitle: getIt.get<AppState>().isArabic() ? "" : "Doctors",
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,

@ -133,22 +133,22 @@ class _LandingPageState extends State<LandingPage> {
// Commented as per new requirement to remove rating popup from the app
// if(!appState.isRatedVisible) {
// appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) {
// if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) {
// appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!,
// onSuccess: ((response) {
// appointmentRatingViewModel.setClinicOrDoctor(false);
// appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation);
// appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation);
// openLastRating();
// appState.setRatedVisible(true);
// }),
// );
// }
// },
// );
// }
if(!appState.isRatedVisible) {
appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) {
if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) {
appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!,
onSuccess: ((response) {
appointmentRatingViewModel.setClinicOrDoctor(false);
appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context));
appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context));
openLastRating();
appState.setRatedVisible(true);
}),
);
}
},
);
}
}
});
super.initState();
@ -173,7 +173,7 @@ class _LandingPageState extends State<LandingPage> {
children: [
SingleChildScrollView(
padding: EdgeInsets.only(
top: (!insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
? (MediaQuery.paddingOf(context).top + 70.h)
: kToolbarHeight + 0.h,
bottom: 24),
@ -365,6 +365,8 @@ class _LandingPageState extends State<LandingPage> {
),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
myAppointmentsViewModel.onTabChange(0);
myAppointmentsViewModel.updateListWRTTab(0);
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
}),
Consumer3<MyAppointmentsViewModel, ImmediateLiveCareViewModel, TodoSectionViewModel>(
@ -727,7 +729,7 @@ class _LandingPageState extends State<LandingPage> {
],
),
),
(!insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
(appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
? Container(
height: MediaQuery.paddingOf(context).top + 50.h,
decoration: ShapeDecoration(

@ -63,6 +63,7 @@ class ServiceInfoPage extends StatelessWidget {
child: Image.asset(
serviceImage,
fit: BoxFit.fitHeight,
height: 480.h,
width: 520.w,
),
),

@ -148,8 +148,8 @@ class LargeServiceCard extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.homeHealthCare.tr(),
serviceHeader: "Your Health, Elevated. Premium care, delivered to your doorstep.",
serviceDescription: "We bring quality healthcare to your doorstep. Experienced nurses providing compassionate care in the comfort of your home.",
serviceHeader: LocaleKeys.homeHealthCareServiceHeader.tr(),
serviceDescription: LocaleKeys.homeHealthCareServiceDescription.tr(),
serviceImage: AppAssets.homeHealthCareService),
),
);
@ -295,8 +295,8 @@ class FadedLargeServiceCard extends StatelessWidget {
CustomPageRoute(
page: ServiceInfoPage(
serviceName: LocaleKeys.homeHealthCare.tr(),
serviceHeader: "Your Health, Elevated. Premium care, delivered to your doorstep.".needTranslation,
serviceDescription: "We bring quality healthcare to your doorstep. Experienced nurses providing compassionate care in the comfort of your home.".needTranslation,
serviceHeader: LocaleKeys.homeHealthCareServiceHeader.tr(),
serviceDescription: LocaleKeys.homeHealthCareServiceDescription.tr(),
serviceImage: AppAssets.homeHealthCareService),
),
);

@ -1,6 +1,7 @@
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/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@ -42,7 +43,11 @@ class WelcomeWidget extends StatelessWidget {
children: [
Icon(Icons.menu, color: AppColors.textColor).onPress(() {
getIt.get<ProfileSettingsViewModel>().getProfileSettings();
Navigator.of(context).push(springPageRoute(ProfileSettings()));
Navigator.of(context).push(
CustomPageRoute(
page: ProfileSettings(),
),
);
}),
Image.asset(imageUrl, width: 40, height: 40),
Column(
@ -58,7 +63,17 @@ class WelcomeWidget extends StatelessWidget {
children: [
Flexible(child: name.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, height: 1, isEnglishOnly: true)),
// Icon(Icons.keyboard_arrow_down, size: 20, color: AppColors.greyTextColor),
Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, height: 22.h, width: 22.w)
Transform.flip(
flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight,
iconColor: AppColors.blackColor,
width: 22.w,
height: 22.h,
fit: BoxFit.contain,
)
),
// Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, height: 22.h, width: 22.w)
],
),
],

@ -74,6 +74,7 @@ class _InsuranceHomePageState extends State<InsuranceHomePage> {
return Column(
children: [
PatientInsuranceCard(
isShowButtons: index == 0,
insuranceCardDetailsModel: insuranceVM.patientInsuranceList[index],
isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))),
SizedBox(

@ -130,6 +130,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget {
},
isFullScreen: false,
isCloseButtonVisible: false,
isAutoDismiss: true
);
// Future.delayed(Duration(milliseconds: 2000)).then((value) async {
// Navigator.pop(context);

@ -20,10 +20,11 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class PatientInsuranceCard extends StatelessWidget {
PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired});
PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired, this.isShowButtons = true});
PatientInsuranceDetailsResponseModel insuranceCardDetailsModel;
bool isInsuranceExpired = false;
bool isShowButtons = true;
late InsuranceViewModel insuranceViewModel;
late AppState appState;
@ -60,7 +61,7 @@ class PatientInsuranceCard extends StatelessWidget {
),
],
),
AppCustomChipWidget(
isShowButtons ? AppCustomChipWidget(
icon: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!)
? AppAssets.cancel_circle_icon
: insuranceViewModel.isInsuranceActive
@ -120,7 +121,7 @@ class PatientInsuranceCard extends StatelessWidget {
? AppColors.successColor.withOpacity(0.1)
: AppColors.warningColorYellow.withOpacity(0.1),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceViewModel.isInsuranceActive ? 6.w : 0.w),
).toShimmer2(isShow: insuranceViewModel.isInsuranceLoading),
).toShimmer2(isShow: insuranceViewModel.isInsuranceLoading) : SizedBox.shrink(),
// AppCustomChipWidget(
// icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon,
// labelText: isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context),
@ -168,7 +169,7 @@ class PatientInsuranceCard extends StatelessWidget {
),
SizedBox(height: 10.h),
isInsuranceExpired
? CustomButton(
? isShowButtons ? CustomButton(
icon: AppAssets.update_insurance_card_icon,
iconColor: AppColors.warningColorYellow,
iconSize: 15.h,
@ -194,7 +195,7 @@ class PatientInsuranceCard extends StatelessWidget {
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
)
) : SizedBox.shrink()
: Container(),
],
),

@ -45,41 +45,55 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
void initState() {
super.initState();
scheduleMicrotask((){
for(var char in widget.alpahbetsAvailable){
data[char] = widget.details.where((element)=>element.description?.toLowerCase().startsWith(char.toLowerCase()) == true).toList();
}
setState((){});
_rebuildData();
});
itemPositionsListener.itemPositions.addListener((){
itemPositionsListener.itemPositions.addListener(_onPositionChanged);
}
@override
void didUpdateWidget(covariant AlphabeticScroll oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.details != widget.details || oldWidget.alpahbetsAvailable != widget.alpahbetsAvailable) {
_rebuildData();
}
}
final positions = itemPositionsListener.itemPositions.value;
void _rebuildData() {
data.clear();
for (var char in widget.alpahbetsAvailable) {
data[char] = widget.details
.where((element) => element.description?.toLowerCase().startsWith(char.toLowerCase()) == true)
.toList();
}
if (_activeIndex >= widget.alpahbetsAvailable.length) {
_activeIndex = 0;
}
setState(() {});
}
if (positions.isEmpty) return;
void _onPositionChanged() {
final positions = itemPositionsListener.itemPositions.value;
// Get FIRST visible item (top-most)
final firstVisible = positions
.where((p) => p.itemTrailingEdge > 0) // visible
.reduce((min, p) =>
p.itemLeadingEdge < min.itemLeadingEdge ? p : min);
if (positions.isEmpty) return;
if(_activeIndex == firstVisible.index) return ;
setState(() {
_activeIndex = firstVisible.index;
});
// Get FIRST visible item (top-most)
final firstVisible = positions
.where((p) => p.itemTrailingEdge > 0) // visible
.reduce((min, p) =>
p.itemLeadingEdge < min.itemLeadingEdge ? p : min);
print("Active index = $_activeIndex");
if(_activeIndex == firstVisible.index) return ;
setState(() {
_activeIndex = firstVisible.index;
});
print("Active index = $_activeIndex");
}
@override
void dispose() {
itemPositionsListener.itemPositions.removeListener((){
});
itemPositionsListener.itemPositions.removeListener(_onPositionChanged);
super.dispose();
}
void _scrollToLetter(String letter) async {
@ -145,7 +159,6 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
crossAxisAlignment: CrossAxisAlignment.center,
children:List.generate(widget.alpahbetsAvailable.length, (i) {
final isActive = (i == _activeIndex);
return GestureDetector(
onTap: () {
setState(() => _activeIndex = i);
@ -161,7 +174,7 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
child: Opacity(
opacity: isActive ? 1.0 : 0.5,
child: widget.alpahbetsAvailable[i].toUpperCase().toText14(
color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor
color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor, isEnglishOnly: true
),
),
);

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'dart:ui' as ui;
class LabOrderByTest extends StatelessWidget {
final VoidCallback onTap;
@ -36,7 +37,7 @@ class LabOrderByTest extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
'${tests!.description}'.toText16(isBold: true),
'${tests!.description}'.toText16(isBold: true, isEnglishOnly: true),
SizedBox(height: 4.h),
(appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(fontWeight: FontWeight.w500),
SizedBox(height: 8.h),
@ -44,7 +45,13 @@ class LabOrderByTest extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
richText: '${"${LocaleKeys.lastTested.tr(context: context)}:"} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(fontWeight: FontWeight.w500),
richText: Row(
children: [
"${LocaleKeys.lastTested.tr(context: context)}: ".toText12(fontWeight: FontWeight.w500),
Directionality(
textDirection: ui.TextDirection.ltr,child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false).toText12(fontWeight: FontWeight.w500, isEnglishOnly: true))
],
),
backgroundColor: AppColors.greyLightColor,
textColor: AppColors.textColor,
),

File diff suppressed because one or more lines are too long

@ -27,7 +27,7 @@ class LabResultList extends StatelessWidget {
shrinkWrap: true,itemCount: list.length,itemBuilder: (____, index) {
var labItem = list[index];
return LabOrderResultItem(onTap: () {
model.getPatientLabResult(model.currentlySelectedPatientOrder!, labItem.description ?? "", labItem.testShortDescription!, labItem.uOM ?? "");
model.getPatientLabResult(model.currentlySelectedPatientOrder!, labItem.description ?? "", labItem.testShortDescription ?? "", labItem.uOM ?? "");
},
tests: labItem,
index: index,

@ -44,7 +44,7 @@ class LabOrderResultItem extends StatelessWidget {
),
// (tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
((tests!.testShortDescription != null && tests!.testShortDescription!.isNotEmpty) ? tests!.testShortDescription : tests!.packageShortDescription)!
(((tests!.testShortDescription != null && tests!.testShortDescription!.isNotEmpty) ? tests!.testShortDescription : tests!.packageShortDescription) ?? "")
.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 12.h),
Directionality(

@ -23,6 +23,7 @@ import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart';
import 'package:provider/provider.dart' show Consumer, Provider, ReadContext, Selector;
import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight;
import 'dart:ui' as ui;
class LabResultDetails extends StatelessWidget {
final LabResult recentLabResult;
@ -63,6 +64,30 @@ class LabResultDetails extends StatelessWidget {
LabNameAndStatus(context),
getLabDescription(context),
LabGraph(context),
Container(
padding: EdgeInsets.symmetric(horizontal: 24.h, vertical: 16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.history.tr(context: context).toText16(isBold: true, color: AppColors.textColor),
(labVM.isLabResultsHistoryShowMore ? "Show Less" : "Show More").toText14(weight: FontWeight.w500, color: AppColors.primaryRedColor, isUnderLine: true).onPress(() {
labVM.setIsLabResultsHistoryShowMore();
}),
],
),
SizedBox(height: 24.h,),
labHistoryList(labVM),
],
),
),
Selector<LabViewModel, LabOrderResponseByAi?>(
selector: (_, model) => model.labOrderResponseByAi,
builder: (_, aiData, __) {
@ -170,7 +195,7 @@ class LabResultDetails extends StatelessWidget {
],
),
SizedBox(height: 4.h),
("${LocaleKeys.resultOf.tr(context: context)} ${recentLabResult.verifiedOn ?? ""}").toText11(weight: FontWeight.w500, color: AppColors.greyTextColor),
("${LocaleKeys.resultOf.tr(context: context)} ${recentLabResult.verifiedOn ?? ""}").toText11(weight: FontWeight.w500, color: AppColors.greyTextColor, isEnglishOnly: true),
],
),
Row(
@ -232,24 +257,25 @@ class LabResultDetails extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
labmodel.isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context),
style: TextStyle(
fontSize: 16.f,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
(labmodel.isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context)).toText16(isBold: true, color: AppColors.textColor),
// Text(
// labmodel.isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context),
// style: TextStyle(
// fontSize: 16.f,
// fontFamily: 'Poppins',
// fontWeight: FontWeight.w600,
// color: AppColors.textColor,
// ),
// ),
Row(
spacing: 16.h,
children: [
//todo handle when the graph icon is being displayed
Utils.buildSvgWithAssets(icon: labmodel.isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph, width: 24.h, height: 24.h).onPress(() {
if (labmodel.shouldShowGraph) {
labmodel.alterGraphVisibility();
}
}),
// Utils.buildSvgWithAssets(icon: labmodel.isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph, width: 24.h, height: 24.h).onPress(() {
// if (labmodel.shouldShowGraph) {
// labmodel.alterGraphVisibility();
// }
// }),
Utils.buildSvgWithAssets(icon: AppAssets.ic_date_filter, width: 24, height: 24).onPress(() {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.setTheDateRange.tr(context: context),
@ -311,6 +337,7 @@ class LabResultDetails extends StatelessWidget {
minY: labmodel.minY,
maxX: labmodel.filteredGraphValues.length.toDouble() - .75,
horizontalInterval: .1,
isRTL: getIt.get<AppState>().isArabic(),
getDrawingHorizontalLine: (value) {
value = double.parse(value.toStringAsFixed(1));
if (value == labmodel.highRefrenceValue || value == labmodel.lowRefenceValue) {
@ -392,10 +419,11 @@ class LabResultDetails extends StatelessWidget {
Widget labHistoryList(LabViewModel labmodel) {
return SizedBox(
height: labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues.length * 64 : 180.h,
height: labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues.length * 64 : (!labmodel.isLabResultsHistoryShowMore ? 180.h : 220.h),
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: labmodel.filteredGraphValues.length,
// itemCount: labmodel.isLabResultsHistoryShowMore ? (labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues : 3) : labmodel.filteredGraphValues.length,
itemCount: !labmodel.isLabResultsHistoryShowMore ? (labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues.length : 3) : labmodel.filteredGraphValues.length,
itemBuilder: (context, index) {
var data = labmodel.filteredGraphValues.reversed.toList()[index];
return LabHistoryItem(

@ -78,8 +78,9 @@ class _SearchLabResultsContentState extends State<SearchLabResultsContent> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextInputWidget(
labelText: "Search lab results",
hintText: "Type test name",
fontFamily: "Poppins",
labelText: LocaleKeys.searchLabResults.tr(context: context),
hintText: "",
controller: searchEditingController,
isEnable: true,
prefix: null,
@ -93,7 +94,7 @@ class _SearchLabResultsContentState extends State<SearchLabResultsContent> {
),
SizedBox(height: ResponsiveExtension(20).h),
if (filteredSuggestions.isNotEmpty) ...[
"Suggestions".toText16(isBold: true),
"Suggestions".toText16(isBold: true, isEnglishOnly: true),
],
],
),
@ -147,6 +148,7 @@ class SuggestionChip extends StatelessWidget {
// color: isSelected ? AppColors.textColor : Colors.black87,
color: AppColors.textColor,
fontWeight: FontWeight.w500,
isEnglishOnly: true
),
),
);

@ -546,7 +546,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
doctorImageURL: patientAppointmentHistoryResponseModel.doctorImageURL,
doctorTitle: patientAppointmentHistoryResponseModel.doctorTitle,
name: patientAppointmentHistoryResponseModel.doctorNameObj,
nationalityFlagURL: "https://hmgwebservices.com/Images/flag/SYR.png",
nationalityFlagURL: "",
speciality: [],
clinicName: patientAppointmentHistoryResponseModel.clinicName,
projectName: patientAppointmentHistoryResponseModel.projectName,
@ -1566,7 +1566,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
label: LocaleKeys.weight.tr(context: context),
value: vitalSign.weightKg?.toString() ?? '--',
unit: 'kg',
status: vitalSign.weightKg != null ? "Normal" : null,
status: vitalSign.weightKg != null ? _getWeightStatus(vitalSign.bodyMassIndex) : null,
onTap: onTap,
),
),
@ -1593,6 +1593,11 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
return VitalSignUiModel.bmiStatus(bmi);
}
String? _getWeightStatus(dynamic bmi) {
if (bmi == null) return 'Normal';
return VitalSignUiModel.bmiStatus(bmi);
}
String? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) {
return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic);
}

@ -419,6 +419,7 @@ class _MedicalReportsPageState extends State<MedicalReportsPage> {
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
isAutoDismiss: true
);
}
}

@ -56,6 +56,7 @@ class _MyInvoicesDetailsPageState extends State<MyInvoicesDetailsPage> {
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
isAutoDismiss: true
);
},
onError: (err) {

@ -17,144 +17,114 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
class NotificationsListPage extends StatelessWidget {
class NotificationsListPage extends StatefulWidget {
const NotificationsListPage({super.key});
@override
State<NotificationsListPage> createState() => _NotificationsListPageState();
}
class _NotificationsListPageState extends State<NotificationsListPage> {
final GlobalKey _bottomKey = GlobalKey();
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final ctx = _bottomKey.currentContext;
if (ctx != null) {
Scrollable.ensureVisible(
ctx,
duration: const Duration(milliseconds: 400),
curve: Curves.easeOut,
);
}
});
}
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: LocaleKeys.notification.tr(context: context),
child: SingleChildScrollView(
child: Consumer<NotificationsViewModel>(builder: (context, notificationsVM, child) {
return Container(
margin: EdgeInsets.symmetric(vertical: 24.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: ListView.builder(
itemCount: notificationsVM.isNotificationsLoading ? 4 : notificationsVM.notificationsList.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return notificationsVM.isNotificationsLoading
? LabResultItemView(
child: Consumer<NotificationsViewModel>(builder: (context, notificationsVM, child) {
return Column(
children: [
Container(
margin: EdgeInsets.symmetric(vertical: 24.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: ListView.builder(
itemCount: notificationsVM.isNotificationsLoading
? 4
: notificationsVM.notificationsList.length + (notificationsVM.hasMoreNotifications ? 1 : 0),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
if (notificationsVM.isNotificationsLoading) {
return LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationDetailsPage(
notification: notificationsVM.notificationsList[index],
),
),
);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
// Message row with red dot for unread
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: notificationsVM.notificationsList[index].message!.toText16(
isBold: (notificationsVM.notificationsList[index].isRead == false),
weight: (notificationsVM.notificationsList[index].isRead == false)
? FontWeight.w600
: FontWeight.w400,
),
);
}
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: GestureDetector(
onTap: () {
notificationsVM.markAsRead(notificationID: notificationsVM.notificationsList[index].id ?? 0);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationDetailsPage(
notification: notificationsVM.notificationsList[index],
),
SizedBox(width: 8.w),
// Red dot for unread notifications ONLY
if (notificationsVM.notificationsList[index].isRead == false)
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
],
),
SizedBox(height: 12.h),
// First row: Time and Date chips with arrow
Row(
),
);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Time chip with clock icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.access_time, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
_formatTime(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
SizedBox(height: 16.h),
// Message row with red dot for unread
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: notificationsVM.notificationsList[index].message!.toText16(
isBold: (notificationsVM.notificationsList[index].isRead == false),
weight: (notificationsVM.notificationsList[index].isRead == false)
? FontWeight.w600
: FontWeight.w400,
),
],
),
),
SizedBox(width: 8.w),
// Date chip with calendar icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
_formatDate(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
SizedBox(width: 8.w),
// Red dot for unread notifications ONLY
if (notificationsVM.notificationsList[index].isRead == false)
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
],
),
),
Spacer(),
// Arrow icon
Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
width: 16.w,
height: 16.h,
iconColor: AppColors.greyTextColor,
],
),
],
),
// Second row: Contains Image chip (if MessageType is "image")
if (notificationsVM.notificationsList[index].messageType != null &&
notificationsVM.notificationsList[index].messageType!.toLowerCase() == "image")
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Row(
SizedBox(height: 12.h),
// First row: Time and Date chips with arrow
Row(
children: [
// Time chip with clock icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
@ -164,31 +134,100 @@ class NotificationsListPage extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.image_outlined, size: 12.w, color: AppColors.textColor),
Icon(Icons.access_time, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
'Contains Image'.toText10(
_formatTime(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
],
),
),
SizedBox(width: 8.w),
// Date chip with calendar icon
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
_formatDate(notificationsVM.notificationsList[index].isSentOn).toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
],
),
),
Spacer(),
// Arrow icon
Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
width: 16.w,
height: 16.h,
iconColor: AppColors.greyTextColor,
),
],
),
),
SizedBox(height: 16.h),
1.divider,
],
// Second row: Contains Image chip (if MessageType is "image")
if (notificationsVM.notificationsList[index].messageType != null &&
notificationsVM.notificationsList[index].messageType!.toLowerCase() == "image")
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: AppColors.greyColor,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.image_outlined, size: 12.w, color: AppColors.textColor),
SizedBox(width: 4.w),
'Contains Image'.toText10(
weight: FontWeight.w500,
color: AppColors.textColor,
),
],
),
),
],
),
),
SizedBox(height: 16.h),
1.divider,
],
),
),
),
),
),
),
),
);
}).paddingSymmetrical(16.w, 0.h),
).paddingSymmetrical(24.w, 0.h);
);
}).paddingSymmetrical(16.w, 0.h),
).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 16.h),
SizedBox(
key: _bottomKey,
child: "Show more notifications".toText16(
color: AppColors.primaryRedColor,
isBold: true,
isUnderLine: true
),
).onPress(() async {
await notificationsVM.loadMoreNotifications();
_scrollToBottom();
}),
SizedBox(height: 24.h),
],
);
}),
),
);
}

@ -1,7 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/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';
@ -103,6 +106,18 @@ class _ParkingPageState extends State<ParkingPage> {
child: Padding(
padding: EdgeInsets.all(16.h), child: LocaleKeys.parkingDescription.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColor)),
).paddingOnly(top: 16, bottom: 16),
ClipRRect(
borderRadius: BorderRadius.circular(24.r),
child: Transform.flip(
flipX: getIt.get<AppState>().isArabic(),
child: Image.asset(
AppAssets.carParkingService,
fit: BoxFit.fitHeight,
height: 480.h,
width: 520.w,
),
),
),
],
),
),

@ -135,6 +135,7 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget {
isCloseButtonVisible: true,
isDismissible: false,
isFullScreen: false,
isAutoDismiss: true
);
},
onError: (err) {

@ -72,7 +72,7 @@ class _PreferredLanguageWidgetState extends State<PreferredLanguageWidget> {
callBackFunc: () async {
Navigator.of(context).pop();
profileSettingsViewModel.getProfileSettings();
}, isFullScreen: false);
}, isFullScreen: false, isAutoDismiss: true);
},
onError: (error) {
LoaderBottomSheet.hideLoader();

@ -96,7 +96,7 @@ class _UpdateEmailDialogState extends State<UpdateEmailDialog> {
callBackFunc: () async {
Navigator.of(context).pop();
profileSettingsViewModel!.getProfileSettings();
}, isFullScreen: false);
}, isFullScreen: false, isAutoDismiss: true);
},
onError: (error) {
LoaderBottomSheet.hideLoader();

@ -96,7 +96,7 @@ class _UpdateEmergencyContactDialogState extends State<UpdateEmergencyContactDia
callBackFunc: () async {
Navigator.of(context).pop();
profileSettingsViewModel!.getProfileSettings();
}, isFullScreen: false);
}, isFullScreen: false, isAutoDismiss: true);
},
onError: (error) {
LoaderBottomSheet.hideLoader();

@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointme
import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class RateAppointmentDoctor extends StatefulWidget {
@ -25,7 +26,7 @@ class RateAppointmentDoctor extends StatefulWidget {
class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
final formKey = GlobalKey<FormState>();
String note = "";
int rating = 5;
int rating = 0;
// ProjectViewModel? projectViewModel;
AppointmentRatingViewModel? appointmentRatingViewModel;
@ -48,16 +49,16 @@ class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
return Selector<AppointmentRatingViewModel, bool>(
selector: (_, vm) => vm.isRateClinic,
builder: (context, isRateClinic, child) => isRateClinic
? RateAppointmentClinic(doctorNote: note, doctorRate: rating,)
: SizedBox(
height: sheetHeight,
builder: (context, isRateClinic, child) =>
// isRateClinic ? RateAppointmentClinic(doctorNote: note, doctorRate: rating,)
// :
SizedBox(
height: sheetHeight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Scrollable main content
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 0.0, left: 0, right: 0),
child: Column(
@ -144,7 +145,7 @@ class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
decoration: InputDecoration.collapsed(
hintText: LocaleKeys.notes.tr(context: context),
hintStyle: TextStyle(
fontSize: 16,
fontSize: 16.f,
fontWeight: FontWeight.w600,
color: Color(0xff2B353E),
letterSpacing: -0.64,
@ -162,7 +163,6 @@ class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
),
),
// Bottom action buttons pinned to bottom
SafeArea(
top: false,
@ -172,7 +172,7 @@ class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
children: [
Expanded(
child: CustomButton(
text: "Later",
text: LocaleKeys.later.tr(context: context),
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
@ -184,12 +184,18 @@ class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
SizedBox(width: 10),
Expanded(
child: CustomButton(
text: LocaleKeys.next.tr(context: context),
onPressed: () {
text: LocaleKeys.submit.tr(context: context),
onPressed: () async {
// Set up clinic rating and show clinic rating view
appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),);
appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),);
appointmentRatingViewModel!.setClinicOrDoctor(true);
// appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),);
// appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),);
// appointmentRatingViewModel!.setClinicOrDoctor(true);
LoaderBottomSheet.showLoader();
await appointmentRatingViewModel!.submitDoctorRating(docRate: rating, docNote: note);
// await appointmentRatingViewModel!.submitClinicRating(clinicRate: rating, clinicNote: note);
LoaderBottomSheet.hideLoader();
Navigator.pop(context);
setState(() {});
},

@ -36,32 +36,26 @@ class BuildDoctorRow extends StatelessWidget {
SizedBox(height: 8.h),
isForClinic ? Wrap(
direction: Axis.horizontal,
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText:
appointmentDetails!.clinicName.toString(),
),
AppCustomChipWidget(
icon: AppAssets.ic_date_filter,
labelText:
DateUtil.formatDateToDate(DateUtil.convertStringToDate(appointmentDetails!.appointmentDate), false),
),
AppCustomChipWidget(
icon: AppAssets.appointment_time_icon,
labelText:
appointmentDetails!.startTime.substring(0, appointmentDetails!.startTime.length - 3),
),
]
) : Wrap(
direction: Axis.horizontal,
@ -69,26 +63,26 @@ class BuildDoctorRow extends StatelessWidget {
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText:
appointmentDetails!.projectName.toString(),
),
AppCustomChipWidget(
labelText:
appointmentDetails!.clinicName.toString(),
)
]
)
],
),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -8.w, end: 6.w),
icon: AppAssets.ic_date_filter,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(appointmentDetails!.appointmentDate), false),
),
],
),
],
),
),
),
],
));
),
);
}
}

@ -37,6 +37,8 @@ class PossibleConditionsPage extends StatelessWidget {
late BookAppointmentsViewModel bookAppointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
late AppState appState;
Widget _buildLoadingShimmer() {
return ListView.separated(
shrinkWrap: true,
@ -223,6 +225,7 @@ class PossibleConditionsPage extends StatelessWidget {
symptomsCheckerViewModel = context.read<SymptomsCheckerViewModel>();
bookAppointmentsViewModel = context.read<BookAppointmentsViewModel>();
regionalViewModel = context.read<AppointmentViaRegionViewmodel>();
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
@ -248,7 +251,7 @@ class PossibleConditionsPage extends StatelessWidget {
// Get selected symptoms names for display
final symptoms = symptomsCheckerViewModel
.getAllSelectedSymptoms()
.map((s) => s.commonName ?? s.name ?? '')
.map((s) => (appState.isArabic() ? s.commonNameAr ?? s.nameAr ?? '' : s.commonName ?? s.name ?? ''))
.where((name) => name.isNotEmpty)
.take(3)
.toList();
@ -376,7 +379,6 @@ class PossibleConditionsPage extends StatelessWidget {
},
);
} else {
final appState = getIt.get<AppState>();
appState.resetLocation();
regionVM.setSortByLocation(false);
_refreshHospitalListAfterApi(regionVM);

@ -491,7 +491,7 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
child: Utils.getSuccessWidget(loadingText: "${LocaleKeys.paymentCompletedSuccessfully.tr(context: context)}, ${LocaleKeys.hereIsYourInvoiceNumber.tr(context: context)}$invoiceNo"),
isCloseButtonVisible: true,
isDismissible: true,
isFullScreen: false, callBackFunc: () {
isFullScreen: false, isAutoDismiss: true, callBackFunc: () {
// Pop until we reach the LandingPage
todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true);
Navigator.pushAndRemoveUntil(

@ -89,6 +89,21 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
),
_whatIsThisResultCard(context),
_historyCard(context, history: history),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.history.tr(context: context).toText16(isBold: true, color: AppColors.textColor),
SizedBox(height: 16.h),
_buildHistoryList(context, history),
],
).paddingSymmetrical(16.h, 16.h),
),
],
).paddingAll(24.h),
);
@ -264,35 +279,27 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context),
style: TextStyle(
fontSize: 16,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
LocaleKeys.historyFlowchart.tr(context: context).toText16(isBold: true, color: AppColors.textColor),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 24.h,
height: 24.h,
alignment: Alignment.center,
child: InkWell(
onTap: () {
setState(() {
_isGraphVisible = !_isGraphVisible;
});
},
child: Utils.buildSvgWithAssets(
icon: _isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph,
width: 24.h,
height: 24.h,
),
),
),
// Container(
// width: 24.h,
// height: 24.h,
// alignment: Alignment.center,
// child: InkWell(
// onTap: () {
// setState(() {
// _isGraphVisible = !_isGraphVisible;
// });
// },
// child: Utils.buildSvgWithAssets(
// icon: _isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph,
// width: 24.h,
// height: 24.h,
// ),
// ),
// ),
// SizedBox(width: 16.h),
// Container(
// width: 24.h,
@ -663,7 +670,8 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
case VitalSignMetric.height:
return null;
case VitalSignMetric.weight:
return latest.weightKg != null ? 'Normal' : null;
// return latest.weightKg != null ? 'Normal' : null;
return VitalSignUiModel.bmiStatus(latest.bodyMassIndex);
case VitalSignMetric.temperature:
return null;
case VitalSignMetric.heartRate:

@ -138,11 +138,13 @@ class _VitalSignPageState extends State<VitalSignPage> {
label: LocaleKeys.weight.tr(context: context),
value: latestVitalSign?.weightKg?.toString() ?? '--',
unit: 'kg',
status: (latestVitalSign?.weightKg != null) ? 'Normal' : null,
status: (latestVitalSign?.weightKg != null)
? VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex)
: null,
onTap: () => _openDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.weight,
title: LocaleKeys.height.tr(context: context),
title: LocaleKeys.weight.tr(context: context),
icon: AppAssets.weightVital,
unit: 'kg',
),

@ -23,7 +23,7 @@ abstract class DialogService {
Future<void> showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showSuccessBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed});
@ -119,7 +119,7 @@ class DialogServiceImp implements DialogService {
}
@override
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) async {
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(
@ -132,6 +132,7 @@ class DialogServiceImp implements DialogService {
cancelLabel: cancelLabel,
onOkPressed: onOkPressed,
onCancelPressed: onCancelPressed,
isConfirmButton: isConfirmButton
),
callBackFunc: () {},
);
@ -239,7 +240,7 @@ class DialogServiceImp implements DialogService {
}
}
Widget exceptionBottomSheetWidget({required BuildContext context, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) {
Widget exceptionBottomSheetWidget({required BuildContext context, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed}) {
return Column(
children: [
(message).toText16(isBold: false, color: AppColors.textColor),
@ -267,7 +268,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin
text: okLabel ?? LocaleKeys.confirm.tr(context: context),
onPressed: onOkPressed,
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
borderColor: AppColors.successColor,
textColor: Colors.white,
icon: AppAssets.confirm,
),
@ -284,8 +285,9 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin
context.pop();
}
: onCancelPressed,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
// backgroundColor: AppColors.primaryRedColor,
backgroundColor: isConfirmButton ? AppColors.successColor : AppColors.primaryRedColor,
borderColor: isConfirmButton ? AppColors.successColor : AppColors.primaryRedColor,
textColor: Colors.white,
icon: AppAssets.confirm,
),

@ -57,9 +57,9 @@ class _AppLanguageChangeState extends State<AppLanguageChange> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: Column(
children: [
languageItem("English", "en"),
1.divider,
languageItem("العربية", "ar"),
1.divider,
languageItem("English", "en"),
],
),
),

@ -116,6 +116,7 @@ void showCommonBottomSheet(BuildContext context,
Function(String?)? callBackFunc,
String? title,
required double height,
bool isAutoDismiss = false,
bool isCloseButtonVisible = true,
bool isFullScreen = true,
bool isDismissible = true,
@ -131,6 +132,14 @@ void showCommonBottomSheet(BuildContext context,
isDismissible: isDismissible,
backgroundColor: isSuccessDialog ? AppColors.whiteColor : AppColors.scaffoldBgColor,
builder: (BuildContext context) {
if (isAutoDismiss) {
Future.delayed(Duration(seconds: 2), () {
Navigator.of(context).pop();
if (callBackFunc != null) {
callBackFunc(null);
}
});
}
return Container(
height: height,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 24.h),
@ -211,9 +220,11 @@ void showCommonBottomSheetWithoutHeight(
required Widget child,
VoidCallback? callBackFunc,
String title = "",
bool isConfirmButton = false,
bool isCloseButtonVisible = true,
bool isFullScreen = true,
bool isDismissible = true,
bool isAutoDismiss = false,
Widget? titleWidget,
bool useSafeArea = false,
bool hasBottomPadding = true,
@ -237,6 +248,14 @@ void showCommonBottomSheetWithoutHeight(
backgroundColor: resolvedBgColor,
useSafeArea: useSafeArea,
builder: (BuildContext context) {
if (isAutoDismiss) {
Future.delayed(Duration(seconds: 2), () {
Navigator.of(context).pop();
if (callBackFunc != null) {
callBackFunc();
}
});
}
return SafeArea(
top: false,
left: false,

@ -48,6 +48,14 @@ class CustomTabBarState extends State<CustomTabBar> {
super.initState();
}
@override
void didUpdateWidget(covariant CustomTabBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialIndex != widget.initialIndex) {
selectedIndex = widget.initialIndex;
}
}
@override
void dispose() {
super.dispose();

@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
/// - [showBottomTitleDates]: Whether to show bottom axis labels.
/// - [isFullScreeGraph]: Whether the graph is fullscreen.
/// - [makeGraphBasedOnActualValue]: Use `actualValue` for plotting.
/// - [isRTL]: Whether the graph should render in right-to-left direction.
///
/// Example usage:
/// ```dart
@ -70,6 +71,7 @@ class CustomGraph extends StatelessWidget {
final bool showLinePoints;
final double? cutOffY;
final RangeAnnotations? rangeAnnotations;
final bool isRTL;
///creates the left label and provide it to the chart as it will be used by other part of the application so the label will be different for every chart
final Widget Function(double) leftLabelFormatter;
@ -113,7 +115,8 @@ class CustomGraph extends StatelessWidget {
this.showShadow = false,
this.showLinePoints = false,
this.cutOffY = 0,
this.rangeAnnotations});
this.rangeAnnotations,
this.isRTL = false});
@override
Widget build(BuildContext context) {
@ -122,6 +125,12 @@ class CustomGraph extends StatelessWidget {
final resolvedGraphShadowColor = graphShadowColor ?? AppColors.graphGridColor;
final resolvedGraphGridColor = graphGridColor ?? AppColors.graphGridColor;
// For RTL, reverse the data points so the graph reads right-to-left
final displayDataPoints = isRTL ? dataPoints.reversed.toList() : dataPoints;
final displaySecondaryDataPoints = (isRTL && secondaryDataPoints != null)
? secondaryDataPoints!.reversed.toList()
: secondaryDataPoints;
return Material(
color: AppColors.whiteColor,
child: SizedBox(
@ -176,10 +185,10 @@ class CustomGraph extends StatelessWidget {
// Show tooltip for each touched line
return touchedSpots.map((spot) {
// Determine which dataset this spot belongs to
final isSecondary = secondaryDataPoints != null && spot.barIndex == 1;
final isSecondary = displaySecondaryDataPoints != null && spot.barIndex == 1;
final dataPoint = isSecondary
? secondaryDataPoints![spot.x.toInt()]
: dataPoints[spot.x.toInt()];
? displaySecondaryDataPoints[spot.x.toInt()]
: displayDataPoints[spot.x.toInt()];
return LineTooltipItem(
'${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""} - ${dataPoint.displayTime}',
@ -192,9 +201,19 @@ class CustomGraph extends StatelessWidget {
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
showTitles: !isRTL,
reservedSize: leftLabelReservedSize ?? 80,
interval: leftLabelInterval ?? .1,
getTitlesWidget: (value, _) {
return leftLabelFormatter(value);
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: isRTL,
reservedSize: leftLabelReservedSize ?? 80,
interval: leftLabelInterval ?? .1, // Let fl_chart handle it
interval: leftLabelInterval ?? .1,
getTitlesWidget: (value, _) {
return leftLabelFormatter(value);
},
@ -206,30 +225,28 @@ class CustomGraph extends StatelessWidget {
showTitles: showBottomTitleDates,
reservedSize: bottomLabelReservedSize ?? 20,
getTitlesWidget: (value, _) {
return bottomLabelFormatter(value, dataPoints);
return bottomLabelFormatter(value, displayDataPoints);
},
interval: 1, // ensures 1:1 mapping with spots
),
),
topTitles: AxisTitles(),
rightTitles: AxisTitles(),
),
borderData: FlBorderData(
show: true,
border: const Border(
border: Border(
bottom: BorderSide.none,
left: BorderSide(color: Colors.grey, width: .5),
right: BorderSide.none,
left: isRTL ? BorderSide.none : const BorderSide(color: Colors.grey, width: .5),
right: isRTL ? const BorderSide(color: Colors.grey, width: .5) : BorderSide.none,
top: BorderSide.none,
),
),
lineBarsData: _buildColoredLineSegments(dataPoints, showLinePoints, resolvedGraphColor, resolvedGraphShadowColor),
lineBarsData: _buildColoredLineSegments(displayDataPoints, showLinePoints, resolvedGraphColor, resolvedGraphShadowColor,
secondaryPoints: displaySecondaryDataPoints),
gridData: FlGridData(
show: showGridLines ?? true,
drawVerticalLine: false,
horizontalInterval: horizontalInterval,
// checkToShowHorizontalLine: (value) =>
// value >= 0 && value <= 100,
getDrawingHorizontalLine: getDrawingHorizontalLine ??
(value) {
return FlLine(
@ -245,7 +262,8 @@ class CustomGraph extends StatelessWidget {
);
}
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints, bool showLinePoints, Color resolvedGraphColor, Color resolvedGraphShadowColor) {
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints, bool showLinePoints, Color resolvedGraphColor, Color resolvedGraphShadowColor,
{List<DataPoint>? secondaryPoints}) {
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
double value = (makeGraphBasedOnActualValue) ? double.tryParse(entry.value.actualValue) ?? 0.0 : entry.value.value;
return FlSpot(entry.key.toDouble(), value);
@ -281,8 +299,8 @@ class CustomGraph extends StatelessWidget {
];
// Add secondary line if provided (for dual-line graphs like blood pressure)
if (secondaryDataPoints != null && secondaryDataPoints!.isNotEmpty) {
final List<FlSpot> secondarySpots = secondaryDataPoints!.asMap().entries.map((entry) {
if (secondaryPoints != null && secondaryPoints.isNotEmpty) {
final List<FlSpot> secondarySpots = secondaryPoints.asMap().entries.map((entry) {
double value = (makeGraphBasedOnActualValue) ? double.tryParse(entry.value.actualValue) ?? 0.0 : entry.value.value;
return FlSpot(entry.key.toDouble(), value);
}).toList();

@ -289,15 +289,7 @@ class TextInputWidget extends StatelessWidget {
}
Widget _buildLabelText(Color? labelColor) {
return Text(
labelText,
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w500,
color: labelColor ?? AppColors.inputLabelTextColor,
letterSpacing: -0,
),
);
return labelText.toText12(fontWeight: FontWeight.w500, color: labelColor ?? AppColors.inputLabelTextColor);
}
Widget _buildTextField(BuildContext context) {

@ -5,6 +5,8 @@ import 'package:hmg_patient_app_new/core/api_consts.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';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -13,7 +15,7 @@ class LoaderBottomSheet {
static final NavigationService _navService = GetIt.I<NavigationService>();
static bool _isVisible = false;
static void showLoader({String? loadingText}) {
static void showLoader({String? loadingText, bool showCloseButton = false, Function? onCloseTap}) {
if (_isVisible) return;
_isVisible = true;
@ -32,7 +34,31 @@ class LoaderBottomSheet {
color: AppColors.whiteColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Center(
child: (showCloseButton) ? Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"".toText20(),
if (showCloseButton)
IconButton(
icon: Icon(Icons.close, color: AppColors.textColor),
onPressed: () {
context.pop();
if(onCloseTap != null) {
onCloseTap();
}
},
)
else
SizedBox(width: 48), // Placeholder for alignment
],
),
Center(
child: Utils.getLoadingWidget(loadingText: loadingText),
).paddingSymmetrical(24.w, 0),
],
) : Center(
child: Utils.getLoadingWidget(loadingText: loadingText),
).paddingSymmetrical(24.w, 0),
);

@ -10,7 +10,6 @@ class CustomPageRoute<T> extends PageRouteBuilder<T> {
CustomPageRoute({required this.page, this.direction = AxisDirection.right, this.fullScreenDialog = false, super.settings})
: super(
transitionDuration: const Duration(milliseconds: 1500),
reverseTransitionDuration: const Duration(milliseconds: 500),
fullscreenDialog: fullScreenDialog,

Loading…
Cancel
Save