Merge branch 'refs/heads/master' into faiz_dev

# Conflicts:
#	lib/core/dependencies.dart
#	lib/main.dart
#	lib/presentation/home/landing_page.dart
#	lib/presentation/medical_file/medical_file_page.dart
#	lib/routes/app_routes.dart
#	lib/widgets/appbar/collapsing_list_view.dart
pull/113/head
faizatflutter 1 month ago
commit 34f4cd4f91

@ -0,0 +1,3 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="mapbox_access_token" translatable="false" tools:ignore="UnusedResources">sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg</string>
</resources>

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 10.4189C5 6.60727 8.12437 3.5 12 3.5C15.8756 3.5 19 6.60727 19 10.4189C19 11.7227 18.6361 12.94 18.003 13.98C17.7158 14.4518 17.8654 15.067 18.3372 15.3542C18.8089 15.6414 19.4242 15.4917 19.7113 15.02C20.5292 13.6763 21 12.1008 21 10.4189C21 5.48352 16.9609 1.5 12 1.5C7.03907 1.5 3 5.48352 3 10.4189C3 12.1008 3.47075 13.6763 4.28867 15.02C4.57584 15.4917 5.19107 15.6414 5.66283 15.3542C6.13458 15.067 6.28421 14.4518 5.99704 13.98C5.36394 12.94 5 11.7227 5 10.4189Z" fill="#FFAF15"/>
<path d="M16.2105 15.755C16.1022 15.75 15.9736 15.75 15.8432 15.75L13 15.75L13 12.3377C13.6525 12.122 14.2312 11.7074 14.7242 11.1896C15.1051 10.7896 15.0896 10.1567 14.6896 9.77581C14.2896 9.39495 13.6567 9.41044 13.2758 9.81041C12.783 10.3279 12.3512 10.5 12 10.5C11.6488 10.5 11.217 10.3279 10.7242 9.81041C10.3433 9.41045 9.71037 9.39495 9.31041 9.77581C8.91044 10.1567 8.89495 10.7896 9.27581 11.1896C9.76884 11.7074 10.3475 12.122 11 12.3377L11 15.75H8.15684C8.02643 15.75 7.89781 15.75 7.78945 15.755C7.67503 15.7602 7.52127 15.7728 7.3611 15.8212C6.8626 15.9717 6.5267 16.3363 6.50126 16.7498C6.49309 16.8826 6.52614 16.9996 6.55585 17.0857C6.8873 18.7676 7.57151 19.25 8.1569 19.25L15.8431 19.25C16.4285 19.25 17.1127 18.7676 17.4441 17.0857C17.4739 16.9996 17.5069 16.8826 17.4987 16.7498C17.4733 16.3363 17.1374 15.9717 16.6389 15.8212C16.4787 15.7728 16.325 15.7602 16.2105 15.755Z" fill="#FFAF15"/>
<path d="M10.3321 22.4013C10.6774 22.5018 11.0856 22.5018 11.9019 22.5018C12.7182 22.5018 13.1264 22.5018 13.4717 22.4013C14.0057 22.2458 14.4551 21.9204 14.7324 21.4884C14.8356 21.3276 14.906 21.1423 14.9776 20.8791C15.0674 20.5491 15.1123 20.3841 15.0223 20.2663C14.9322 20.1484 14.7551 20.1484 14.4009 20.1484L9.40295 20.1484C9.04873 20.1484 8.87162 20.1484 8.78156 20.2663C8.69151 20.3841 8.73641 20.5491 8.82623 20.8791C8.89786 21.1423 8.96819 21.3276 9.07142 21.4884C9.34876 21.9204 9.79812 22.2458 10.3321 22.4013Z" fill="#FFAF15"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.14286 7.27526C6.06081 7.19321 5.94953 7.14712 5.8335 7.14712C5.68326 7.14712 5.53722 7.14711 5.396 7.14757V9.54083C6.06931 9.73127 6.56266 10.3503 6.56266 11.0846V11.668C6.56266 11.9096 6.36679 12.1055 6.12516 12.1055C5.88354 12.1055 5.68766 11.9096 5.68766 11.668V11.0846C5.68766 10.6819 5.3612 10.3555 4.9585 10.3555C4.55579 10.3555 4.22933 10.6819 4.22933 11.0846V11.668C4.22933 11.9096 4.03345 12.1055 3.79183 12.1055C3.55021 12.1055 3.35433 11.9096 3.35433 11.668V11.0846C3.35433 10.3503 3.84768 9.73127 4.521 9.54083V7.16254C4.27509 7.17241 4.05216 7.18858 3.85498 7.21509C3.32991 7.28568 2.88782 7.4367 2.5367 7.78782C2.18558 8.13894 2.03456 8.58103 1.96397 9.1061C1.89598 9.61182 1.89598 10.2548 1.896 11.0526V12.8346C1.896 13.0762 2.09187 13.2721 2.3335 13.2721H11.6668C11.9085 13.2721 12.1043 13.0762 12.1043 12.8346V11.0526C12.1043 10.2548 12.1044 9.61183 12.0364 9.1061C11.9658 8.58103 11.8147 8.13894 11.4636 7.78782C11.1125 7.4367 10.6704 7.28568 10.1454 7.21509C10.0293 7.19949 9.90432 7.18746 9.771 7.1782V10.4725C10.0325 10.6238 10.2085 10.9066 10.2085 11.2305C10.2085 11.7137 9.81675 12.1055 9.3335 12.1055C8.85025 12.1055 8.4585 11.7137 8.4585 11.2305C8.4585 10.9066 8.63446 10.6238 8.896 10.4725V7.14936C8.66672 7.14711 8.42324 7.14711 8.16711 7.14712C8.05108 7.14712 7.93952 7.19321 7.85747 7.27526L7.00016 8.13257L6.14286 7.27526Z" fill="#18C273"/>
<path d="M7.00016 0.730469C5.63096 0.730469 4.521 1.84043 4.521 3.20964V3.79297C4.521 5.16217 5.63096 6.27214 7.00016 6.27214C8.36937 6.27214 9.47933 5.16217 9.47933 3.79297V3.20964C9.47933 1.84043 8.36937 0.730469 7.00016 0.730469Z" fill="#18C273"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5658 1.95717C9.82689 0.890531 8.26692 1.31563 7.32452 2.02335C7.26938 2.06476 7.22039 2.10153 7.17659 2.13413C7.08676 2.20099 7.04185 2.23442 6.98387 2.23442C6.92589 2.23442 6.88098 2.20099 6.79115 2.13413C6.74735 2.10153 6.69836 2.06476 6.64322 2.02335C5.70082 1.31563 4.14085 0.890531 2.40195 1.95717C1.53619 2.48823 0.943044 3.43564 0.767402 4.5853C0.741775 4.75304 0.728961 4.83691 0.781341 4.89789C0.83372 4.95888 0.923033 4.95887 1.10166 4.95885L3.00095 4.95868C3.24133 4.95767 3.64619 4.95596 4.03964 5.0991C4.19392 5.15522 4.33336 5.22877 4.4573 5.3094C4.63656 5.42602 4.72618 5.48433 4.79864 5.46761C4.87109 5.45089 4.92018 5.36908 5.01835 5.20545L5.44147 4.50026C5.71062 4.05168 6.19943 3.78162 6.72246 3.79256C7.24549 3.80349 7.72259 4.09374 7.97275 4.55319L8.72948 5.94294C8.77795 6.03196 8.80218 6.07647 8.8435 6.10102C8.88482 6.12557 8.9355 6.12557 9.03686 6.12557H9.31698C10.1224 6.12557 10.7753 6.77849 10.7753 7.5839C10.7753 8.38932 10.1224 9.04224 9.31698 9.04224L8.66574 9.04348C8.42373 9.04908 7.69714 9.06588 7.06184 8.57794C7.0432 8.56363 7.02501 8.54906 7.00725 8.5343C6.81047 8.37075 6.71208 8.28898 6.63276 8.30247C6.55344 8.31596 6.49635 8.41111 6.38218 8.6014L6.19249 8.91754C5.93991 9.33851 5.49248 9.60414 5.00197 9.62434C4.51146 9.64453 4.0437 9.41658 3.75738 9.0178L3.04199 8.02146C2.99064 7.94995 2.96497 7.91419 2.92736 7.8949C2.88975 7.8756 2.84573 7.8756 2.75769 7.8756L1.88603 7.87559C1.63453 7.87558 1.50878 7.87558 1.45819 7.96269C1.40759 8.0498 1.46788 8.15548 1.58846 8.36685C2.2106 9.45742 3.18686 10.5632 4.58832 11.6005C5.43495 12.2275 6.05634 12.6877 6.98387 12.6877C7.91141 12.6877 8.53279 12.2275 9.37943 11.6005C11.8175 9.79586 12.9688 7.78416 13.2073 6.00548C13.4447 4.23535 12.7682 2.69472 11.5658 1.95717Z" fill="#ED1C2B"/>
<path d="M7.20469 4.97105C7.10463 4.78727 6.91379 4.67117 6.70458 4.66679C6.49537 4.66242 6.29984 4.77044 6.19218 4.94988L4.8987 7.10568L4.50819 6.5618C4.50002 6.55043 4.49145 6.53935 4.48248 6.52859C4.47338 6.51766 4.4634 6.50547 4.45255 6.49222C4.32787 6.33991 4.08822 6.04716 3.74091 5.92081C3.49697 5.83206 3.23003 5.83265 2.965 5.83324L2.90072 5.83333H1.73405C1.41188 5.83333 1.15072 6.0945 1.15072 6.41667C1.15072 6.73883 1.41188 7 1.73405 7H2.90072C3.27245 7 3.31696 7.00823 3.34111 7.01684C3.34329 7.01792 3.36229 7.02776 3.40145 7.06546C3.45005 7.11226 3.4979 7.16947 3.57276 7.25931L4.46854 8.50689C4.58307 8.6664 4.77018 8.75758 4.96638 8.74951C5.16258 8.74143 5.34156 8.63518 5.44259 8.46679L6.66782 6.42473L7.16648 7.34054C7.17206 7.35078 7.17794 7.36086 7.18412 7.37075L7.20842 7.40983C7.30282 7.56206 7.41744 7.74688 7.59523 7.88343C7.98366 8.18176 8.4448 8.17225 8.67006 8.16761C8.69423 8.16711 8.71569 8.16667 8.73405 8.16667H9.31738C9.63955 8.16667 9.90072 7.9055 9.90072 7.58333C9.90072 7.26117 9.63955 7 9.31738 7H8.73405C8.69863 7 8.66723 7.00017 8.63892 7.00032C8.53614 7.00086 8.47421 7.00119 8.40933 6.99253C8.35288 6.98499 8.32768 6.97346 8.31135 6.96218C8.30864 6.95892 8.30403 6.95313 8.29726 6.94384C8.27206 6.90926 8.24113 6.86063 8.18271 6.76722L7.20469 4.97105Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.25 20C9.25 25.928 14.072 30.75 20 30.75C25.928 30.75 30.75 25.928 30.75 20C30.75 14.0721 25.9282 9.25021 20.0004 9.25C14.0724 9.25 9.25 14.072 9.25 20ZM28.9732 17.75C29.154 18.4705 29.25 19.2243 29.25 20C29.25 20.7757 29.154 21.5295 28.9732 22.25H24.5746C24.687 21.5359 24.751 20.7846 24.751 19.9997C24.751 19.2151 24.687 18.464 24.5747 17.75H28.9732ZM28.4553 16.25H24.2585C23.6395 13.9132 22.5564 12.0788 21.7158 10.9096C24.7331 11.4779 27.2382 13.5166 28.4553 16.25ZM23.049 17.75C23.1761 18.4613 23.25 19.213 23.25 19.9997C23.25 20.7866 23.1761 21.5386 23.049 22.25H16.9511C16.8239 21.5386 16.75 20.7866 16.75 19.9997C16.75 19.2131 16.8239 18.4613 16.9509 17.75H23.049ZM17.309 16.25H22.6909C21.966 13.828 20.7108 12.0282 20 11.1427C19.2892 12.0276 18.0339 13.8277 17.309 16.25ZM15.4263 17.75C15.314 18.464 15.25 19.2151 15.25 19.9997C15.25 20.7846 15.3141 21.5359 15.4264 22.25H11.0268C10.846 21.5295 10.75 20.7757 10.75 20C10.75 19.2243 10.846 18.4705 11.0268 17.75H15.4263ZM11.5447 16.25H15.7424C16.3613 13.9131 17.4442 12.0786 18.2848 10.9094C15.2672 11.4776 12.7619 13.5164 11.5447 16.25ZM21.7159 29.0904C24.7331 28.522 27.2382 26.4834 28.4553 23.75H24.2584C23.6395 26.0869 22.5565 27.9213 21.7159 29.0904ZM20 28.8568C20.7107 27.9719 21.9659 26.1721 22.6908 23.75H17.3093C18.0342 26.1717 19.2893 27.9713 20 28.8568ZM18.2858 29.0907C17.4451 27.9217 16.3618 26.0871 15.7427 23.75H11.5447C12.7621 26.4839 15.2678 28.5228 18.2858 29.0907Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -178,6 +178,10 @@ class AppAssets {
static const String doctor_profile_rating_icon = '$svgBasePath/doctor_profile_rating_icon.svg';
static const String doctor_profile_reviews_icon = '$svgBasePath/doctor_profile_reviews_icon.svg';
static const String waiting_appointment_icon = '$svgBasePath/waitingAppo.svg';
static const String call_for_vitals = '$svgBasePath/call_for_vitals.svg';
static const String call_for_doctor = '$svgBasePath/call_for_doctor.svg';
static const String bulb_icon = '$svgBasePath/bulb_icon.svg';
static const String select_city_icon = '$svgBasePath/select_city_icon.svg';
//bottom navigation//
static const String homeBottom = '$svgBasePath/home_bottom.svg';

@ -5,6 +5,8 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/location_util.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.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_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/common/common_repo.dart';
@ -119,6 +121,7 @@ class AppDependencies {
getIt.registerLazySingleton<ContactUsRepo>(() => ContactUsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HmgServicesRepo>(() => HmgServicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<SymptomsCheckerRepo>(() => SymptomsCheckerRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<BloodDonationRepo>(() => BloodDonationRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -225,6 +228,10 @@ class AppDependencies {
() => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
);
getIt.registerLazySingleton<BloodDonationViewModel>(
() => BloodDonationViewModel(bloodDonationRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt()),
);
// Screen-specific VMs Factory
// getIt.registerFactory<BookAppointmentsViewModel>(
// () => BookAppointmentsViewModel(

@ -329,7 +329,7 @@ class Utils {
repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill),
SizedBox(height: 16.h),
(noDataText ?? LocaleKeys.noDataAvailable.tr())
.toText16(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true)
.toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true)
.paddingSymmetrical(64.w, 0.h),
SizedBox(height: 16.h),
callToActionButton
@ -875,4 +875,54 @@ class Utils {
Uri uri = Uri.parse(url);
launchUrl(uri, mode: LaunchMode.inAppBrowserView);
}
static Color getCardBorderColor(int currentQueueStatus) {
switch (currentQueueStatus) {
case 0:
return AppColors.ratingColorYellow;
case 1:
return AppColors.primaryRedColor;
case 2:
return AppColors.successColor;
}
return AppColors.textColor;
}
static Color getCardButtonColor(int currentQueueStatus) {
switch (currentQueueStatus) {
case 0:
return AppColors.textColor.withValues(alpha: 0.08);
case 1:
return AppColors.primaryRedColor;
case 2:
return AppColors.successColor;
}
return AppColors.primaryRedColor;
}
static Color getCardButtonTextColor(int currentQueueStatus) {
switch (currentQueueStatus) {
case 0:
return Color(0xFFA2A2A2);
case 1:
return AppColors.whiteColor;
case 2:
return AppColors.whiteColor;
}
return AppColors.primaryRedColor;
}
static String getCardButtonText(int currentQueueStatus) {
switch (currentQueueStatus) {
case 0:
return "Please wait! you will be called for vital signs".needTranslation;
case 1:
return "Please visit Room S5 for vital signs".needTranslation;
case 2:
return "Please visit Room S5 to the Doctor".needTranslation;
}
return "";
}
}

@ -263,7 +263,7 @@ extension EmailValidator on String {
style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
Widget toText20({Color? color, FontWeight? weight, bool isBold = false}) => Text(
Widget toText20({Color? color, FontWeight? weight, bool isBold = false, }) => Text(
this,
style: TextStyle(
fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),

@ -101,10 +101,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> checkPatientAuthentication({
required dynamic checkPatientAuthenticationReq,
String? languageID,
}) async {
Future<Either<Failure, GenericApiModel<dynamic>>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq, String? languageID}) async {
int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1;
checkPatientAuthenticationReq.patientOutSA = isOutKsa;
try {
@ -149,7 +146,6 @@ class AuthenticationRepoImp implements AuthenticationRepo {
sendActivationCodeReq.isDentalAllowedBackend = false;
final payload = sendActivationCodeReq.toJson();
if (isFormFamilyFile) {
payload.remove("MobileNo");
payload.remove("NationalID");
payload.remove("SMSSignature");
@ -266,10 +262,10 @@ class AuthenticationRepoImp implements AuthenticationRepo {
newRequest.forRegisteration = newRequest.isRegister ?? false;
newRequest.isRegister = false;
//silent login case removed token and login token
if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true) {
newRequest.logInTokenID = null;
newRequest.deviceToken = null;
}
// if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true) {
// newRequest.logInTokenID = null;
// newRequest.deviceToken = null;
// }
}

@ -852,16 +852,13 @@ class AuthenticationViewModel extends ChangeNotifier {
request['isRegister'] = true;
_appState.setAppAuthToken = response['LogInTokenID'];
if (isPatientOutsideSA(request: response)) {
print("=======OUT SA=======");
sendActivationCode(
otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]),
nationalIdOrFileNumber: request["PatientIdentificationID"].toString(),
phoneNumber: request["PatientMobileNumber"].toString(),
payload: request,
isForRegister: true,
);
otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]),
nationalIdOrFileNumber: request["PatientIdentificationID"].toString(),
phoneNumber: request["PatientMobileNumber"].toString(),
payload: request,
isForRegister: true);
} else {
print("=======IN SA=======");
chekUserNHICData(request: request);
}
}

@ -0,0 +1,95 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class BloodDonationRepo {
Future<Either<Failure, GenericApiModel<List<CitiesModel>>>> getAllCities();
Future<Either<Failure, GenericApiModel<List_BloodGroupDetailsModel>>> getPatientBloodGroupDetails();
}
class BloodDonationRepoImp implements BloodDonationRepo {
final ApiClient apiClient;
final LoggerService loggerService;
BloodDonationRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<CitiesModel>>>> getAllCities() async {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<List<CitiesModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_CITIES_REQUEST,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ListCities'];
final citiesList = list.map((item) => CitiesModel.fromJson(item as Map<String, dynamic>)).toList().cast<CitiesModel>();
apiResponse = GenericApiModel<List<CitiesModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: citiesList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List_BloodGroupDetailsModel>>> getPatientBloodGroupDetails() async {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<List_BloodGroupDetailsModel>? apiResponse;
Failure? failure;
await apiClient.post(
GET_BLOOD_REQUEST,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_BloodGroupDetails'][0];
final patientBloodGroup = List_BloodGroupDetailsModel.fromJson(list);
apiResponse = GenericApiModel<List_BloodGroupDetailsModel>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: patientBloodGroup,
);
} 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()));
}
}
}

@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_list_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
class BloodDonationViewModel extends ChangeNotifier {
final DialogService dialogService;
BloodDonationRepo bloodDonationRepo;
ErrorHandlerService errorHandlerService;
final NavigationService navigationService;
final AppState appState;
List<CitiesModel> citiesList = [];
List<BloodGroupListModel> bloodGroupList = [
BloodGroupListModel("O+", 0),
BloodGroupListModel("O-", 1),
BloodGroupListModel("AB+", 2),
BloodGroupListModel("AB-", 3),
BloodGroupListModel("A+", 4),
BloodGroupListModel("A-", 5),
BloodGroupListModel("B+", 6),
BloodGroupListModel("B-", 7),
];
late CitiesModel selectedCity;
late BloodGroupListModel selectedBloodGroup;
int _selectedHospitalIndex = 0;
int _selectedBloodTypeIndex = 0;
String _selectedBloodType = '';
List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel();
BloodDonationViewModel({required this.bloodDonationRepo, required this.errorHandlerService, required this.navigationService, required this.dialogService, required this.appState});
setSelectedCity(CitiesModel city) {
selectedCity = city;
notifyListeners();
}
Future<void> getRegionSelectedClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async {
citiesList.clear();
selectedCity = CitiesModel();
notifyListeners();
final result = await bloodDonationRepo.getAllCities();
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? 'An unexpected error occurred');
} else if (apiResponse.messageStatus == 1) {
citiesList = apiResponse.data!;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getPatientBloodGroupDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await bloodDonationRepo.getPatientBloodGroupDetails();
result.fold(
(failure) async {
onError!(failure.message);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? 'An unexpected error occurred');
} else if (apiResponse.messageStatus == 1) {
patientBloodGroupDetailsModel = apiResponse.data!;
CitiesModel citiesModel = CitiesModel();
citiesModel.iD = getSelectedCityID();
_selectedHospitalIndex = (citiesModel.iD! - 1);
citiesModel.description = citiesList[_selectedHospitalIndex].description;
citiesModel.descriptionN = citiesList[_selectedHospitalIndex].descriptionN;
selectedCity = citiesModel;
_selectedBloodType = patientBloodGroupDetailsModel.bloodGroup!;
_selectedBloodTypeIndex = getBloodIndex(_selectedBloodType);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
int getSelectedCityID() {
int cityID = 1;
citiesList.forEach((element) {
if (element.description == patientBloodGroupDetailsModel.city) {
cityID = element.iD!;
}
});
return cityID;
}
int getBloodIndex(String type) {
switch (type) {
case "O+":
return 0;
case "O-":
return 1;
case "AB+":
return 2;
case "AB-":
return 3;
case "A+":
return 4;
case "A-":
return 5;
case "B+":
return 6;
case "B-":
return 7;
default:
return 0;
}
}
}

@ -0,0 +1,6 @@
class BloodGroupListModel {
String name;
int value;
BloodGroupListModel(this.name, this.value);
}

@ -0,0 +1,61 @@
class List_BloodGroupDetailsModel {
int? iD;
int? patientID;
int? patientType;
bool? patientOutSA;
int? zipCode;
String? cellNumber;
String? cityCode;
String? city;
int? gender;
String? bloodGroup;
String? nationalID;
bool? isActive;
List_BloodGroupDetailsModel({
this.iD,
this.patientID,
this.patientType,
this.patientOutSA,
this.zipCode,
this.cellNumber,
this.cityCode,
this.city,
this.gender,
this.bloodGroup,
this.nationalID,
this.isActive,
});
List_BloodGroupDetailsModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
patientID = json['PatientID'];
patientType = json['PatientType'];
patientOutSA = json['PatientOutSA'];
zipCode = json['ZipCode'];
cellNumber = json['CellNumber'];
cityCode = json['CityCode'];
city = json['City'];
gender = json['Gender'];
bloodGroup = json['BloodGroup'];
nationalID = json['NationalID'];
isActive = json['IsActive'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ID'] = this.iD;
data['PatientID'] = this.patientID;
data['PatientType'] = this.patientType;
data['PatientOutSA'] = this.patientOutSA;
data['ZipCode'] = this.zipCode;
data['CellNumber'] = this.cellNumber;
data['CityCode'] = this.cityCode;
data['City'] = this.city;
data['Gender'] = this.gender;
data['BloodGroup'] = this.bloodGroup;
data['NationalID'] = this.nationalID;
data['IsActive'] = this.isActive;
return data;
}
}

@ -0,0 +1,21 @@
class CitiesModel {
int? iD;
String? description;
String? descriptionN;
CitiesModel({this.iD, this.description, this.descriptionN});
CitiesModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
description = json['Description'];
descriptionN = json['DescriptionN'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ID'] = this.iD;
data['Description'] = this.description;
data['DescriptionN'] = this.descriptionN;
return data;
}
}

@ -497,8 +497,8 @@ class BookAppointmentsViewModel extends ChangeNotifier {
}
initialSlotDuration = apiResponse.data["InitialSlotDuration"];
freeSlotsResponse = apiResponse.data['FreeTimeSlots'];
// isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"];
isWaitingAppointmentAvailable = true;
isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"];
// isWaitingAppointmentAvailable = true;
freeSlotsResponse.forEach((element) {
// date = (isLiveCareSchedule != null && isLiveCareSchedule)

@ -0,0 +1,37 @@
class GetAllergiesResponseModel {
int? patientID;
int? allergyDiseaseType;
int? allergyDiseaseID;
String? description;
String? descriptionN;
String? remarks;
GetAllergiesResponseModel({
this.patientID,
this.allergyDiseaseType,
this.allergyDiseaseID,
this.description,
this.descriptionN,
this.remarks,
});
GetAllergiesResponseModel.fromJson(Map<String, dynamic> json) {
patientID = json['PatientID'];
allergyDiseaseType = json['AllergyDiseaseType'];
allergyDiseaseID = json['AllergyDiseaseID'];
description = json['Description'];
descriptionN = json['DescriptionN'];
remarks = json['Remarks'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['PatientID'] = this.patientID;
data['AllergyDiseaseType'] = this.allergyDiseaseType;
data['AllergyDiseaseID'] = this.allergyDiseaseID;
data['Description'] = this.description;
data['DescriptionN'] = this.descriptionN;
data['Remarks'] = this.remarks;
return data;
}
}

@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_allergies_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart';
@ -38,6 +39,8 @@ abstract class MedicalFileRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> removeFamilyFile({required int? id});
Future<Either<Failure, GenericApiModel<dynamic>>> acceptRejectFamilyFile({required int? id, required int? status});
Future<Either<Failure, GenericApiModel<List<GetAllergiesResponseModel>>>> getPatientAllergiesList({Function(dynamic)? onSuccess, Function(String)? onError});
}
class MedicalFileRepoImp implements MedicalFileRepo {
@ -549,4 +552,42 @@ class MedicalFileRepoImp implements MedicalFileRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<GetAllergiesResponseModel>>>> getPatientAllergiesList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {"isDentalAllowedBackend": false, "OutSA": 0};
try {
GenericApiModel<List<GetAllergiesResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PATIENT_ALLERGIES,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['Patient_Allergies'];
final vaccinesList = list.map((item) => GetAllergiesResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetAllergiesResponseModel>();
apiResponse = GenericApiModel<List<GetAllergiesResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: vaccinesList,
);
} 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()));
}
}
}

@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/utils/request_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_allergies_response_model.dart';
import 'package:hmg_patient_app_new/features/common/models/family_file_request.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart';
@ -27,6 +28,7 @@ class MedicalFileViewModel extends ChangeNotifier {
bool isPatientSickLeaveListLoading = false;
bool isPatientSickLeavePDFLoading = false;
bool isPatientMedicalReportsListLoading = false;
bool isPatientAllergiesListLoading = false;
MedicalFileRepo medicalFileRepo;
ErrorHandlerService errorHandlerService;
@ -34,6 +36,8 @@ class MedicalFileViewModel extends ChangeNotifier {
List<PatientVaccineResponseModel> patientVaccineList = [];
List<PatientSickLeavesResponseModel> patientSickLeaveList = [];
List<GetAllergiesResponseModel> patientAllergiesList = [];
List<PatientMedicalReportResponseModel> patientMedicalReportList = [];
List<PatientMedicalReportResponseModel> patientMedicalReportRequestedList = [];
@ -69,8 +73,10 @@ class MedicalFileViewModel extends ChangeNotifier {
initMedicalFileProvider() {
patientMedicalReportAppointmentHistoryList.clear();
patientAllergiesList.clear();
isPatientVaccineListLoading = true;
isPatientMedicalReportsListLoading = true;
isPatientAllergiesListLoading = true;
notifyListeners();
}
@ -89,7 +95,6 @@ class MedicalFileViewModel extends ChangeNotifier {
void onFamilyFileTabChange(int index) {
setSelectedFamilyFileTabIndex = index;
notifyListeners();
}
setIsPatientVaccineListLoading(bool isLoading) {
@ -162,6 +167,32 @@ class MedicalFileViewModel extends ChangeNotifier {
);
}
Future<void> getPatientAllergiesList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
isPatientAllergiesListLoading = true;
patientAllergiesList.clear();
notifyListeners();
final result = await medicalFileRepo.getPatientAllergiesList();
result.fold(
(failure) async {
isPatientAllergiesListLoading = false;
notifyListeners();
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
patientAllergiesList = apiResponse.data!;
isPatientAllergiesListLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getPatientSickLeaveList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
patientSickLeaveList.clear();
final result = await medicalFileRepo.getPatientSickLeavesList();
@ -442,9 +473,6 @@ class MedicalFileViewModel extends ChangeNotifier {
);
}
Future<void> addFamilyFile({required OTPTypeEnum otpTypeEnum}) async {
LoaderBottomSheet.showLoader();
AuthenticationViewModel authVM = getIt.get<AuthenticationViewModel>();
@ -489,6 +517,13 @@ class MedicalFileViewModel extends ChangeNotifier {
});
}
Future<void> clearAuthValues() async {
authVM.nationalIdController.clear();
authVM.phoneNumberController.clear();
authVM.emailController.clear();
authVM.dobController.clear();
}
Future<void> handleFamilyFileRequestOTPVerification() async {
LoaderBottomSheet.showLoader();
if (!_appState.getIsChildLoggedIn) {
@ -600,7 +635,7 @@ class MedicalFileViewModel extends ChangeNotifier {
getFamilyFiles(status: 0);
getAllPendingRecordsByResponseId();
LoaderBottomSheet.hideLoader();
onFamilyFileTabChange(0);
// onFamilyFileTabChange(0);
}
},
);

@ -37,6 +37,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
DateTime? start = null;
DateTime? end = null;
bool isPatientHasQueueAppointment = false;
int currentQueueStatus = 0;
List<PatientAppointmentHistoryResponseModel> patientAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> filteredAppointmentList = [];
@ -85,6 +88,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
isTamaraDetailsLoading = true;
isAppointmentPatientShareLoading = true;
isEyeMeasurementsAppointmentsLoading = true;
isPatientHasQueueAppointment = false;
notifyListeners();
}
setCurrentQueueStatus(int currentQueueStatus) {
this.currentQueueStatus = currentQueueStatus;
notifyListeners();
}

@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/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/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
@ -149,6 +150,9 @@ void main() async {
),
ChangeNotifierProvider<SymptomsCheckerViewModel>(
create: (_) => getIt.get<SymptomsCheckerViewModel>(),
),
ChangeNotifierProvider<BloodDonationViewModel>(
create: (_) => getIt.get<BloodDonationViewModel>(),
)
], child: MyApp()),
),

@ -0,0 +1,138 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
class AllergiesListPage extends StatelessWidget {
AllergiesListPage({super.key});
late MedicalFileViewModel medicalFileViewModel;
@override
Widget build(BuildContext context) {
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.allergies.tr(),
child: SingleChildScrollView(
child: Consumer<MedicalFileViewModel>(builder: (context, medicalFileVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
ListView.separated(
scrollDirection: Axis.vertical,
itemCount: medicalFileVM.isPatientAllergiesListLoading
? 5
: medicalFileVM.patientAllergiesList.isNotEmpty
? medicalFileVM.patientAllergiesList.length
: 1,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(left: 24.h, right: 24.h),
itemBuilder: (context, index) {
return medicalFileVM.isPatientAllergiesListLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.allergy_info_icon, width: 36.w, height: 36.h, fit: BoxFit.contain).toShimmer2(isShow: true),
SizedBox(width: 16.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.h,
runSpacing: 4.h,
children: [
// AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
// AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
],
),
],
),
),
],
),
],
),
),
)
: medicalFileVM.patientAllergiesList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.allergy_info_icon, width: 36.w, height: 36.h, fit: BoxFit.contain),
SizedBox(height: 16.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(medicalFileVM.patientAllergiesList[index].description).toString().toText16(isBold: true).toShimmer2(isShow: false),
(medicalFileVM.patientAllergiesList[index].remarks).toString().toText12(),
],
),
),
],
),
],
),
),
),
),
),
)
: Utils.getNoDataWidget(context, noDataText: "No allergies data found...".needTranslation);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 60.h),
],
);
}),
),
),
);
}
}

@ -0,0 +1,220 @@
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';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class AppointmentQueuePage extends StatelessWidget {
AppointmentQueuePage({super.key});
AppState? appState;
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: "Queueing".needTranslation,
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w),
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
labelText: myAppointmentsVM.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation,
backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20),
textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus),
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
],
),
SizedBox(height: 10.h),
"Hala ${appState!.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true),
SizedBox(height: 8.h),
"Thank you for your patience, here is your queue number.".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 8.h),
"IMD W-A-5".needTranslation.toText32(isBold: true),
SizedBox(height: 8.h),
CustomButton(
text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus),
onPressed: () {},
backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus),
borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01),
textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus),
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
iconColor: AppColors.whiteColor,
iconSize: 18.h,
),
],
),
),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Serving Now".needTranslation.toText16(isBold: true),
SizedBox(height: 18.h),
ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: 3,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"IMD W-A-2".needTranslation.toText17(isBold: true),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
"Room: S2".toText12(fontWeight: FontWeight.w500),
SizedBox(width: 8.w),
AppCustomChipWidget(
deleteIcon: AppAssets.call_for_vitals,
labelText: "Call for vital signs".needTranslation,
iconColor: AppColors.primaryRedColor,
textColor: AppColors.primaryRedColor,
iconSize: 14.w,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h),
),
],
),
],
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h),
),
],
),
),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.bulb_icon, width: 24.w, height: 24.h),
SizedBox(width: 8.w),
"Things to ask your doctor today".needTranslation.toText16(isBold: true),
],
),
SizedBox(height: 8.h),
// What can I do to improve my overall health?
// Are there any routine screenings I should get?
// What is this medication for?
// Are there any side effects I should know about?
// When should I come back for a follow-up?
"${"What can I do to improve my overall health?"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${"Are there any routine screenings I should get?"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${"What is this medication for?"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${"Are there any side effects I should know about?"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 4.h),
"${"When should I come back for a follow-up?"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 16.h),
],
),
),
),
],
),
),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: CustomButton(
text: "Go to homepage".needTranslation,
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 50.h,
icon: AppAssets.homeBottom,
iconColor: AppColors.whiteColor,
iconSize: 18.h,
).paddingSymmetrical(16.h, 24.h),
)
],
);
}),
);
}
}

@ -124,47 +124,37 @@ class AppointmentCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(
children: [
Image.network(
isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : patientAppointmentHistoryResponseModel.doctorImageURL!,
width: 63.w,
height: 63.h,
fit: BoxFit.cover,
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
width: 63.w,
height: 20,
color: AppColors.textColorLight.withValues(alpha: 0.25),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.rating_icon,
width: 12.w,
height: 12.h,
fit: BoxFit.contain,
),
SizedBox(width: 4.w),
isLoading ? "Rating".toText12() : patientAppointmentHistoryResponseModel.decimalDoctorRate.toString().toText12(),
],
),
Image.network(
isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : patientAppointmentHistoryResponseModel.doctorImageURL!,
width: 63.w,
height: 63.h,
fit: BoxFit.cover,
).circle(100.r).toShimmer2(isShow: isLoading),
Transform.translate(
offset: Offset(0.0, -20.h),
child: Container(
width: 40.w,
height: 40.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
shape: BoxShape.circle, // Makes the container circular
border: Border.all(
color: AppColors.scaffoldBgColor, // Color of the border
width: 1.5.w, // Width of the border
),
)
],
).circle(100).toShimmer2(isShow: isLoading),
// SizedBox(height: 12.h),
// AppCustomChipWidget(
// icon: AppAssets.rating_icon,
// iconColor: AppColors.ratingColorYellow,
// labelText: isLoading ? "Rating" : "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}".needTranslation)
// .toShimmer2(isShow: isLoading),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h),
SizedBox(height: 2.h),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
],
),
).circle(100).toShimmer2(isShow: isLoading),
),
],
),
SizedBox(width: 16.h),
@ -181,10 +171,14 @@ class AppointmentCard extends StatelessWidget {
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(labelText: isLoading ? 'Cardiology' : patientAppointmentHistoryResponseModel.clinicName!)
.toShimmer2(isShow: isLoading),
AppCustomChipWidget(labelText: isLoading ? 'Olaya' : patientAppointmentHistoryResponseModel.projectName!)
.toShimmer2(isShow: isLoading),
AppCustomChipWidget(
labelText: isLoading
? 'Cardiology'
: (patientAppointmentHistoryResponseModel.clinicName!.length > 15
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...'
: patientAppointmentHistoryResponseModel.clinicName!),
).toShimmer2(isShow: isLoading),
AppCustomChipWidget(labelText: isLoading ? 'Olaya' : patientAppointmentHistoryResponseModel.projectName!).toShimmer2(isShow: isLoading),
AppCustomChipWidget(
icon: AppAssets.appointment_calendar_icon,
labelText: isLoading

@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -156,7 +157,9 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(page: MyAppointmentsPage()),
CustomPageRoute(
page: AppointmentQueuePage(),
),
);
}, isFullScreen: false);
},

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
@ -50,8 +51,29 @@ class AppointmentDoctorCard extends StatelessWidget {
height: 63.h,
fit: BoxFit.cover,
).circle(100.r),
SizedBox(height: 12.h),
AppCustomChipWidget(icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"),
Transform.translate(
offset: Offset(0.0, -20.h),
child: Container(
width: 40.w,
height: 40.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
shape: BoxShape.circle, // Makes the container circular
border: Border.all(
color: AppColors.scaffoldBgColor, // Color of the border
width: 1.5.w, // Width of the border
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h),
SizedBox(height: 2.h),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
],
),
).circle(100),
),
],
),
SizedBox(width: 16.w),
@ -60,13 +82,16 @@ class AppointmentDoctorCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(isBold: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.w,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: patientAppointmentHistoryResponseModel.clinicName!,
labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 15
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...'
: patientAppointmentHistoryResponseModel.clinicName!),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),
AppCustomChipWidget(
@ -75,8 +100,7 @@ class AppointmentDoctorCard extends StatelessWidget {
),
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText:
"${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}, ${DateUtil.formatDateToTimeLang(
labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(
DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
false,
)}",
@ -100,8 +124,7 @@ class AppointmentDoctorCard extends StatelessWidget {
),
],
),
SizedBox(height: 16.h),
SizedBox(height: 8.h),
Visibility(
visible: renderWidgetForERDisplay == false,
child: getAppointmentActionButtons(

@ -88,10 +88,10 @@ class LoginScreenState extends State<LoginScreen> {
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h),
leadingIcon: AppAssets.student_card,
errorMessage: "Please enter a valid national ID or file number",
errorMessage: "Please enter a valid national ID or file number".needTranslation,
hasError: false,
),
SizedBox(height: 16.h), // Adjusted to sizer unit (approx 16px)
SizedBox(height: 16.h),
CustomButton(
height: 50.h,
text: LocaleKeys.login.tr(),
@ -110,17 +110,12 @@ class LoginScreenState extends State<LoginScreen> {
}
},
),
SizedBox(height: 10.h), // Adjusted to sizer unit (approx 14px)
SizedBox(height: 10.h),
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: context.dynamicTextStyle(
color: Colors.black,
fontSize: 14.f, // Adjusted to sizer unit
height: 26 / 16, // This height is a ratio, may need re-evaluation
fontWeight: FontWeight.w500,
),
style: context.dynamicTextStyle(color: Colors.black, fontSize: 14.f, height: 26 / 16, fontWeight: FontWeight.w500),
children: <TextSpan>[
TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()),
TextSpan(text: " "),
@ -140,9 +135,9 @@ class LoginScreenState extends State<LoginScreen> {
),
],
),
).withVerticalPadding(2), // Adjusted to sizer unit
).withVerticalPadding(2.h),
),
SizedBox(height: 20.h), // Adjusted to sizer unit (approx 14px)
SizedBox(height: 20.h),
],
),
),

@ -78,8 +78,7 @@ class _RegisterNew extends State<RegisterNew> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.showLottie(
context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.h, height: 200.h, fit: BoxFit.cover, repeat: true),
Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.w, height: 200.h, fit: BoxFit.cover, repeat: true),
SizedBox(height: 16.h),
LocaleKeys.prepareToElevate.tr().toText32(isBold: true),
SizedBox(height: 24.h),
@ -113,7 +112,7 @@ class _RegisterNew extends State<RegisterNew> {
Divider(height: 1),
TextInputWidget(
labelText: LocaleKeys.dob.tr(),
hintText: "11 July, 1994",
hintText: "11 July, 1994".needTranslation,
controller: authVm.dobController,
focusNode: _dobFocusNode,
isEnable: true,
@ -240,7 +239,7 @@ class _RegisterNew extends State<RegisterNew> {
),
),
),
SizedBox(height: 30),
SizedBox(height: 30.h),
],
),
),

@ -69,7 +69,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
height: double.infinity,
child: SingleChildScrollView(
reverse: false,
padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 0.h),
padding: EdgeInsets.only(left: 24.w, right: 24.w, top: 0.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@ -98,7 +98,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
leadingIcon: AppAssets.user_circle,
labelColor: AppColors.textColor,
).paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
Divider(height: 1.h, color: AppColors.greyColor),
TextInputWidget(
labelText: LocaleKeys.nationalIdNumber.tr(),
hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : (appState.getNHICUserData.idNumber ?? ""),
@ -130,7 +130,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
hasSelectionCustomIcon: true,
isAllowRadius: false,
labelColor: AppColors.textColor,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
padding: EdgeInsets.only(top: 8.h, bottom: 8.h, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.user_full,
).withVerticalPadding(8);
@ -167,7 +167,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
hasSelectionCustomIcon: true,
isAllowRadius: false,
labelColor: AppColors.textColor,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
padding: EdgeInsets.only(top: 8.h, bottom: 8.h, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.smart_phone,
).withVerticalPadding(8);
@ -188,7 +188,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
leadingIcon: AppAssets.smart_phone,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
Divider(height: 1.h, color: AppColors.greyColor),
authVM!.isUserFromUAE()
? Selector<AuthenticationViewModel, ({List<NationalityCountries>? countriesList, NationalityCountries? selectedCountry, bool isArabic})>(
selector: (context, authViewModel) {
@ -217,10 +217,10 @@ class _RegisterNew extends State<RegisterNewStep2> {
hasSelectionCustomIcon: true,
labelColor: AppColors.textColor,
isAllowRadius: false,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
padding: EdgeInsets.only(top: 8.h, bottom: 8.h, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.globe,
).withVerticalPadding(8);
).withVerticalPadding(8.h);
},
)
: TextInputWidget(
@ -256,7 +256,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
leadingIcon: AppAssets.call)
.paddingSymmetrical(0.h, 16.h),
Divider(
height: 1,
height: 1.h,
color: AppColors.greyColor,
),
TextInputWidget(
@ -292,9 +292,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
iconColor: AppColors.primaryRedColor,
),
),
SizedBox(
width: 16,
),
SizedBox(width: 16.w),
Expanded(
child: CustomButton(
backgroundColor: AppColors.primaryRedColor,

@ -0,0 +1,172 @@
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';
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/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_city_widget.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class BloodDonationPage extends StatelessWidget {
BloodDonationPage({super.key});
late AppState appState;
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<BloodDonationViewModel>(builder: (context, bloodDonationVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.bloodDonation.tr(),
child: Padding(
padding: EdgeInsets.all(24.w),
child: SingleChildScrollView(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.select_city_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.city.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
(appState.isArabic()
? (bloodDonationVM.selectedCity.descriptionN ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedCity.description ?? LocaleKeys.select.tr(context: context))
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
],
).onPress(() async {
LoaderBottomSheet.showLoader(loadingText: "Fetching Cities...");
await bloodDonationVM.getRegionSelectedClinics(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.selectCity.tr(context: context),
isDismissible: true,
child: SelectCityWidget(
bloodDonationViewModel: bloodDonationVM,
),
callBackFunc: () {});
}, onError: (err) {
LoaderBottomSheet.hideLoader();
});
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.gender.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
"Male".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
],
),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
"AB+".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
],
),
],
),
),
),
),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomButton(
text: LocaleKeys.save.tr(),
onPressed: () {
// openDoctorScheduleCalendar();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 50.h,
iconSize: 18.h,
).paddingSymmetrical(16.h, 24.h),
],
),
),
),
],
);
}),
);
}
}

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class CityListItem extends StatelessWidget {
final CitiesModel cityModel;
late AppState appState;
CityListItem({super.key, required this.cityModel});
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [hospitalName],
),
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
),
),
],
).paddingSymmetrical(16.h, 16.h),
);
}
Widget get hospitalName => Row(
children: [
Expanded(
child: (appState.isArabic() ? cityModel.descriptionN : cityModel.description)!.toText16(color: AppColors.textColor, isBold: true),
)
],
);
}

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/city_list_item.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class SelectCityWidget extends StatelessWidget {
SelectCityWidget({super.key, required this.bloodDonationViewModel});
BloodDonationViewModel bloodDonationViewModel;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 8.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
return CityListItem(
cityModel: bloodDonationViewModel.citiesList[index],
).onPress(() {
bloodDonationViewModel.setSelectedCity(bloodDonationViewModel.citiesList[index]);
Navigator.of(context).pop();
});
},
separatorBuilder: (_, __) => SizedBox(
height: 8.h,
),
itemCount: bloodDonationViewModel.citiesList.length),
)
],
);
}
}

@ -25,6 +25,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immed
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.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';
@ -72,6 +73,15 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.bookAppo.tr(context: context),
isLeading: true,
leadingCallback: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
child: SingleChildScrollView(
child: Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
return Column(

@ -67,40 +67,4 @@ class HospitalListItemAdvancePayment extends StatelessWidget {
)
],
);
// Widget get distanceInfo => Row(
// children: [
// Visibility(
// visible: (hospitalModel.distanceInKMs != "0"),
// child: AppCustomChipWidget(
// labelText: "${hospitalData?.distanceInKMs ?? ""} km".needTranslation,
// deleteIcon: AppAssets.location_red,
// deleteIconSize: Size(9, 12),
// backgroundColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.errorColor,
// ),
// ),
// Visibility(
// visible: (hospitalData?.distanceInKMs == "0"),
// child: Row(
// children: [
// AppCustomChipWidget(
// labelText: "Distance not available".needTranslation,
// textColor: AppColors.blackColor,
// ),
// SizedBox(
// width: 8.h,
// )
// ],
// )),
// Visibility(
// visible: !isLocationEnabled,
// child: AppCustomChipWidget(
// labelText: "Location turned off".needTranslation,
// deleteIcon: AppAssets.location_unavailable,
// deleteIconSize: Size(9, 12),
// textColor: AppColors.blackColor,
// )),
// ],
// );
}

@ -42,6 +42,16 @@ class ServicesPage extends StatelessWidget {
textColor: AppColors.blackColor,
route: AppRoutes.homeHealthCarePage,
),
HmgServicesComponentModel(
3,
"Blood Donation".needTranslation,
"".needTranslation,
AppAssets.emergency_services_icon,
true,
bgColor: AppColors.bgGreenColor,
textColor: AppColors.blackColor,
route: AppRoutes.bloodDonationPage,
),
HmgServicesComponentModel(
11,
"Virtual Tour".needTranslation,

@ -34,7 +34,7 @@ class PatientInsuranceCard extends StatelessWidget {
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
borderRadius: 24.r,
),
child: Padding(
padding: EdgeInsets.all(16.h),
@ -112,6 +112,6 @@ class PatientInsuranceCard extends StatelessWidget {
],
),
),
).paddingSymmetrical(24.h, 0.h);
).paddingSymmetrical(0.h, 0.h);
}
}

@ -24,12 +24,13 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_approvals_page.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart';
@ -54,6 +55,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
@ -85,6 +87,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
appState = getIt.get<AppState>();
scheduleMicrotask(() {
if (appState.isAuthenticated) {
labViewModel.initLabProvider();
insuranceViewModel.initInsuranceProvider();
medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
medicalFileViewModel.getPatientSickLeaveList();
@ -107,18 +110,22 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
trailing: Row(
children: [
Wrap(
spacing: -8.h,
// runSpacing: 0.h,
children: [
Utils.buildImgWithAssets(
icon: AppAssets.babyGirlImg,
height: 32.h,
border: 1.5,
height: 28.h,
width: 28.w,
border: 1,
fit: BoxFit.contain,
borderRadius: 50.r,
),
Utils.buildImgWithAssets(
icon: AppAssets.femaleImg,
height: 32.h,
border: 1.5,
height: 28.h,
width: 28.w,
border: 1,
borderRadius: 50.r,
fit: BoxFit.contain,
),
@ -144,24 +151,18 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
},
profiles: medicalFileViewModel.patientFamilyFiles);
}),
isLeading: false,
isLeading: true,
leadingCallback: () {
Navigator.pushAndRemoveUntil(
navigationService.navigatorKey.currentContext!,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: "Type any record".needTranslation,
controller: TextEditingController(),
keyboardType: TextInputType.number,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
leadingIcon: AppAssets.student_card,
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h),
Container(
width: double.infinity,
@ -189,7 +190,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
AppCustomChipWidget(
icon: AppAssets.file_icon,
labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}",
labelText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}",
labelPadding: EdgeInsetsDirectional.only(end: 6.w),
onChipTap: () {
navigationService.pushPage(
@ -225,9 +226,10 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
AppCustomChipWidget(
icon: AppAssets.blood_icon,
labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}".needTranslation,
labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}",
iconColor: AppColors.primaryRedColor,
labelPadding: EdgeInsetsDirectional.only(end: 4.w, start: 0.w),
labelPadding: EdgeInsetsDirectional.only(end: 4.w),
padding: EdgeInsets.zero,
),
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return AppCustomChipWidget(
@ -236,8 +238,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
iconSize: 12.w,
backgroundColor:
insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
labelPadding: EdgeInsetsDirectional.only(end: 8.w),
);
}),
@ -248,27 +249,112 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h),
Consumer<MedicalFileViewModel>(builder: (context, medicalFileVM, child) {
return Column(
children: [
CustomTabBar(
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1),
tabs: [
CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation),
CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)),
CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation),
CustomTabBarModel(AppAssets.more, "More".needTranslation),
],
onTabChange: (index) {
medicalFileVM.onTabChanged(index);
},
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 24.h),
getSelectedTabData(medicalFileVM.selectedTabIndex),
],
);
}),
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: "Type any record".needTranslation,
controller: TextEditingController(),
keyboardType: TextInputType.number,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
leadingIcon: AppAssets.search_icon,
hintColor: AppColors.textColor,
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h),
// Using CustomExpandableList
CustomExpandableList(
expansionMode: ExpansionMode.exactlyOne,
dividerColor: Color(0xFF2B353E1A),
itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
items: [
ExpandableListItem(
title: "Medical Services".toText18(weight: FontWeight.w600),
children: [
SizedBox(height: 10.h),
getSelectedTabData(0),
],
),
ExpandableListItem(
title: "Medical Reports".toText18(
weight: FontWeight.w600,
),
expandedBackgroundColor: Colors.transparent,
children: [
SizedBox(height: 10.h),
getSelectedTabData(2),
]),
ExpandableListItem(
title: "Insurance & Payments".toText18(weight: FontWeight.w600),
expandedBackgroundColor: Colors.transparent,
children: [
SizedBox(height: 10.h),
getSelectedTabData(1),
],
),
ExpandableListItem(
title: "Tracker & Others".toText18(weight: FontWeight.w600),
expandedBackgroundColor: Colors.transparent,
children: [
Text("Blood Report"),
SizedBox(height: 8),
Text("X-Ray Report"),
],
),
],
theme: ExpandableListTheme.custom(
defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor),
),
).paddingSymmetrical(16.w, 0.0),
// ExpansionTileList(
// shrinkWrap: true,
// trailing: Icon(Icons.arrow_drop_down),
// expansionMode: ExpansionMode.exactlyOne,
// children: <ExpansionTile>[
// ExpansionTile(
// title: "Medical Services".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// iconColor: Color(0xFF2E3039),
// trailing: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w),
// children: <Widget>[Text('Child 1')],
// ),
// ExpansionTile(
// title: "Medical Reports".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// children: <Widget>[Text('Child 2')],
// ),
// ExpansionTile(
// title: "Insurance & Payments".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// children: <Widget>[Text('Child 3')],
// ),
// ExpansionTile(
// title: "Tracker & Others".toText20(weight: FontWeight.w600, color: AppColors.textColor),
// children: <Widget>[Text('Child 4')],
// ),
// ],
// ),
// Consumer<MedicalFileViewModel>(builder: (context, medicalFileVM, child) {
// return Column(
// children: [
// CustomTabBar(
// activeTextColor: AppColors.primaryRedColor,
// activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1),
// tabs: [
// CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation),
// CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)),
// CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation),
// CustomTabBarModel(AppAssets.more, "More".needTranslation),
// ],
// onTabChange: (index) {
// medicalFileVM.onTabChanged(index);
// },
// ).paddingSymmetrical(24.w, 0.0),
// SizedBox(height: 24.h),
// getSelectedTabData(medicalFileVM.selectedTabIndex),
// ],
// );
// }),
],
),
);
@ -324,7 +410,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Appointments & visits".needTranslation.toText18(isBold: true),
"Appointments & visits".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
Row(
children: [
LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
@ -333,7 +419,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
],
),
],
).paddingSymmetrical(24.w, 0.h).onPress(() {
).paddingSymmetrical(0.w, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyAppointmentsPage(),
@ -356,8 +442,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? Container(
padding: EdgeInsets.all(12.w),
width: MediaQuery.of(context).size.width,
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: true),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: true),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
@ -390,7 +475,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
: ListView.separated(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.w, right: 24.w),
itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
@ -408,45 +492,18 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
});
},
onAskDoctorTap: () {},
)),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w),
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h),
),
);
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 24.h),
"Lab & Radiology".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 10.h),
"Lab & Radiology".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
SizedBox(height: 16.h),
Row(
children: [
@ -458,7 +515,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests,
isLoading: labViewModel.isLabOrdersLoading,
).onPress(() {
labViewModel.initLabProvider();
Navigator.of(context).push(
CustomPageRoute(
page: LabOrdersPage(),
@ -466,11 +522,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}),
),
SizedBox(width: 16.h),
SizedBox(width: 8.w),
Expanded(
child: LabRadCard(
icon: AppAssets.radiology_icon,
labelText: LocaleKeys.radiology.tr(context: context),
labelText: "${LocaleKeys.radiology.tr(context: context)} Results".needTranslation,
// labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar",
// labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"],
labOrderTests: [],
isLoading: false,
@ -483,19 +540,16 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
}),
),
],
).paddingSymmetrical(24.w, 0.h),
).paddingSymmetrical(0.w, 0.h),
SizedBox(height: 24.h),
"Active Medications & Prescriptions".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
"Active Medications & Prescriptions".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
SizedBox(height: 16.h),
Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) {
return prescriptionVM.isPrescriptionsOrdersLoading
? const CommonShimmerWidget().paddingSymmetrical(24.w, 0.h)
? const CommonShimmerWidget().paddingSymmetrical(0.w, 0.h)
: prescriptionVM.patientPrescriptionOrders.isNotEmpty
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
borderRadius: 12.r,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r, hasShadow: false),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
@ -532,13 +586,11 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
spacing: 3.w,
runSpacing: 4.w,
children: [
AppCustomChipWidget(
labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(
prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
false,
),
),
@ -551,19 +603,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
width: 15.w,
height: 15.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor)),
icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
],
).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(
isFromAppointments: false,
prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
),
);
}),
@ -573,9 +619,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 8.h),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 8.h),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
@ -600,7 +646,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
iconSize: 16.w,
),
),
SizedBox(width: 10.w),
SizedBox(width: 6.w),
Expanded(
child: CustomButton(
text: "All Medications".needTranslation,
@ -622,7 +668,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
],
),
),
).paddingSymmetrical(24.w, 0.h)
).paddingSymmetrical(0.w, 0.h)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
@ -636,14 +682,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 62.w,
height: 62.h,
),
).paddingSymmetrical(24.w, 0.h);
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 24.h),
//My Doctor Section
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.myDoctor.tr(context: context).toText18(isBold: true),
LocaleKeys.myDoctor.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2),
Row(
children: [
LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
@ -659,7 +705,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}),
],
).paddingSymmetrical(24.w, 0.h),
).paddingSymmetrical(0.w, 0.h),
SizedBox(height: 16.h),
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
return myAppointmentsVM.isPatientMyDoctorsLoading
@ -673,10 +719,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
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 Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
],
)
: myAppointmentsVM.patientMyDoctorsList.isEmpty
@ -694,14 +737,13 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 62.w,
height: 62.h,
),
).paddingSymmetrical(24.w, 0.h)
).paddingSymmetrical(0.w, 0.h)
: SizedBox(
height: 110.h,
height: 100.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: myAppointmentsVM.patientMyDoctorsList.length,
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.w, right: 24.w),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
@ -721,8 +763,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r),
SizedBox(height: 8.h),
SizedBox(
width: 80.w,
Expanded(
child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName)
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
@ -761,10 +802,10 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
);
).paddingSymmetrical(0.w, 0);
}),
SizedBox(height: 24.h),
"Others".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h),
"Others".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
SizedBox(height: 16.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
@ -801,7 +842,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.allergy_info_icon,
isLargeText: true,
iconSize: 36.w,
),
).onPress(() {
medicalFileViewModel.getPatientAllergiesList();
Navigator.of(context).push(
CustomPageRoute(
page: AllergiesListPage(),
),
);
}),
MedicalFileCard(
label: "Vaccine Info".needTranslation,
textColor: AppColors.blackColor,
@ -817,7 +865,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}),
],
).paddingSymmetrical(24.w, 0.0),
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 24.h),
],
);
@ -832,7 +880,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
labOrder: null,
index: index,
isLoading: true,
).paddingSymmetrical(24.w, 0.0)
).paddingSymmetrical(0.w, 0.0)
: insuranceVM.patientInsuranceList.isNotEmpty
? PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
@ -859,14 +907,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {
insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(),
appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(),
callBackFunc: () {},
title: "",
isCloseButtonVisible: false,
isFullScreen: false);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
@ -878,7 +921,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
height: isFoldable ? 50.h : 40.h,
).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h),
),
).paddingSymmetrical(24.w, 0.h);
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 10.h),
GridView(
@ -933,7 +976,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
iconSize: 36.w,
),
],
).paddingSymmetrical(24.w, 0.0),
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
],
);
@ -946,12 +989,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? PatientSickLeaveCard(
patientSickLeavesResponseModel: PatientSickLeavesResponseModel(),
isLoading: true,
).paddingSymmetrical(24.w, 0.0)
).paddingSymmetrical(0.w, 0.0)
: medicalFileVM.patientSickLeaveList.isNotEmpty
? PatientSickLeaveCard(
patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first,
isLoading: false,
).paddingSymmetrical(24.w, 0.0)
).paddingSymmetrical(0.w, 0.0)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
@ -965,7 +1008,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 62.w,
height: 62.h,
),
).paddingSymmetrical(24.w, 0.h);
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 16.h),
GridView(
@ -1018,7 +1061,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}),
],
).paddingSymmetrical(24.w, 0.0),
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 24.h),
],
);

@ -27,20 +27,27 @@ class LabRadCard extends StatelessWidget {
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 18.r, hasShadow: false),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(
icon: icon,
width: 40.w,
height: 40.h,
fit: BoxFit.contain,
fit: BoxFit.cover,
).toShimmer2(isShow: false, radius: 12.r),
SizedBox(width: 8.w),
Flexible(child: labelText.toText14(isBold: true).toShimmer2(isShow: false, radius: 6.r, height: 32.h)),
Flexible(
child: labelText.toText12(isBold: true, maxLine: 2),
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 10.w, height: 10.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
),
],
),
// SizedBox(height: 16.h),
@ -61,21 +68,21 @@ class LabRadCard extends StatelessWidget {
// )
// : "You don't have any records yet".needTranslation.toText13(
// color: AppColors.greyTextColor, isCenter: true),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"View All".toText12(isBold: true),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)
.toShimmer2(isShow: false, radius: 12.r),
),
],
)
// SizedBox(height: 16.h),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// SizedBox.shrink(),
// Transform.flip(
// flipX: appState.isArabic(),
// child: Utils.buildSvgWithAssets(
// icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)
// .toShimmer2(isShow: false, radius: 12.r),
// ),
// ],
// )
],
).paddingAll(16.w),
).paddingAll(12.w),
);
}
}

@ -46,6 +46,9 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
void initState() {
super.initState();
medicalVM = context.read<MedicalFileViewModel>();
WidgetsBinding.instance.addPostFrameCallback((_) {
medicalVM?.onFamilyFileTabChange(0);
});
}
@override
@ -65,6 +68,7 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
text: "Add a new family member".needTranslation,
onPressed: () {
DialogService dialogService = getIt.get<DialogService>();
medicalVM!.clearAuthValues();
dialogService.showAddFamilyFileSheet(
label: "Add Family Member".needTranslation,
message: "Please fill the below field to add a new family member to your profile".needTranslation,
@ -83,13 +87,17 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
children: [
appState.isChildLoggedIn
? SizedBox()
: CustomTabBar(
activeBackgroundColor: AppColors.secondaryLightRedColor,
activeTextColor: AppColors.primaryRedColor,
tabs: [CustomTabBarModel(null, LocaleKeys.family.tr()), CustomTabBarModel(null, LocaleKeys.request.tr())],
onTabChange: (index) {
medicalVM!.onFamilyFileTabChange(index);
},
: Selector<MedicalFileViewModel, int>(
selector: (_, model) => model.getSelectedFamilyFileTabIndex,
builder: (context, selectedIndex, child) => CustomTabBar(
activeBackgroundColor: AppColors.secondaryLightRedColor,
activeTextColor: AppColors.primaryRedColor,
// selectedIndex: selectedIndex,
tabs: [CustomTabBarModel(null, LocaleKeys.family.tr()), CustomTabBarModel(null, LocaleKeys.request.tr())],
onTabChange: (index) {
medicalVM!.onFamilyFileTabChange(index);
},
),
),
appState.isChildLoggedIn ? SizedBox() : SizedBox(height: 25.h),
Selector<MedicalFileViewModel, int>(selector: (_, model) => model.getSelectedFamilyFileTabIndex, builder: (context, selectedIndex, child) => getFamilyTabs(index: selectedIndex)),
@ -117,7 +125,7 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
case 1:
return FamilyCards(
profiles: medicalVM!.pendingFamilyFiles,
isRequestDesign: true,
isRequestDesign: medicalVM!.getSelectedFamilyFileTabIndex == 1,
onSelect: (FamilyFileResponseModelLists profile) {
medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 3);
},

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
@ -27,6 +28,7 @@ class AppRoutes {
static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage';
static const String homeHealthCarePage = '/homeHealthCarePage';
static const String zoomCallPage = '/zoomCallPage';
static const String bloodDonationPage = '/bloodDonationPage';
// Symptoms Checker
static const String organSelectorPage = '/organSelectorPage';
@ -53,5 +55,7 @@ class AppRoutes {
suggestionsScreen: (context) => SuggestionsScreen(),
possibleConditionsScreen: (context) => PossibleConditionsScreen(),
triageScreen: (context) => TriageScreen()
zoomCallPage: (context) => CallScreen(),
bloodDonationPage: (context) => BloodDonationPage()
};
}

@ -129,13 +129,21 @@ class DialogServiceImp implements DialogService {
if (context == null) return;
showCommonBottomSheetWithoutHeight(context,
title: label ?? "",
child: FamilyCards(
profiles: profiles,
onSelect: (FamilyFileResponseModelLists profile) {
onSwitchPress(profile);
},
onRemove: (FamilyFileResponseModelLists profile) {},
isShowDetails: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
(message).toText16(isBold: false, color: AppColors.textColor),
SizedBox(height: 24.h),
FamilyCards(
profiles: profiles,
onSelect: (FamilyFileResponseModelLists profile) {
onSwitchPress(profile);
},
onRemove: (FamilyFileResponseModelLists profile) {},
isShowDetails: false,
),
],
),
callBackFunc: () {});
}

@ -10,7 +10,7 @@ class ExpandableBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("the currently selected item is ${bottomSheetType}");
print("the currently selected item is $bottomSheetType");
return AnimatedCrossFade(
duration: const Duration(milliseconds: 600),
firstChild:children[BottomSheetType.FIXED] ?? SizedBox.shrink(),

@ -0,0 +1,301 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
// ==================== MAIN CUSTOM WIDGET ====================
class CustomExpandableList extends StatefulWidget {
final List<ExpandableListItem> items;
final ExpansionMode expansionMode;
final EdgeInsetsGeometry? itemPadding;
final EdgeInsetsGeometry? contentPadding;
final Color? dividerColor;
final double dividerHeight;
final bool showDividers;
final Duration animationDuration;
final Curve animationCurve;
final ExpandableListTheme? theme;
final Function(int index, bool isExpanded)? onItemToggled;
const CustomExpandableList({
Key? key,
required this.items,
this.expansionMode = ExpansionMode.multiple,
this.itemPadding,
this.contentPadding,
this.dividerColor,
this.dividerHeight = 1.0,
this.showDividers = true,
this.animationDuration = const Duration(milliseconds: 300),
this.animationCurve = Curves.easeInOut,
this.theme,
this.onItemToggled,
}) : super(key: key);
@override
_CustomExpandableListState createState() => _CustomExpandableListState();
}
class _CustomExpandableListState extends State<CustomExpandableList> {
late List<bool> _expandedStates;
@override
void initState() {
super.initState();
_expandedStates = List<bool>.generate(
widget.items.length,
(index) => widget.items[index].initiallyExpanded,
);
}
void _toggleItem(int index) {
setState(() {
if (widget.expansionMode == ExpansionMode.exactlyOne) {
// Close all others, open this one
for (int i = 0; i < _expandedStates.length; i++) {
_expandedStates[i] = (i == index) ? !_expandedStates[i] : false;
}
} else if (widget.expansionMode == ExpansionMode.atMostOne) {
// Toggle this one, close if opening another
bool wasExpanded = _expandedStates[index];
for (int i = 0; i < _expandedStates.length; i++) {
if (i == index) {
_expandedStates[i] = !wasExpanded;
} else if (!wasExpanded) {
_expandedStates[i] = false;
}
}
} else {
// Multiple - just toggle the clicked item
_expandedStates[index] = !_expandedStates[index];
}
});
widget.onItemToggled?.call(index, _expandedStates[index]);
}
@override
Widget build(BuildContext context) {
final theme = widget.theme ?? ExpandableListTheme.defaultTheme(context);
return Column(
children: List.generate(widget.items.length, (index) {
final item = widget.items[index];
final isExpanded = _expandedStates[index];
final isLast = index == widget.items.length - 1;
return Column(
children: [
_ExpandableListItemWidget(
title: item.title,
isExpanded: isExpanded,
onTap: () => _toggleItem(index),
padding: widget.itemPadding,
contentPadding: widget.contentPadding,
theme: theme,
leading: item.leading,
trailing: item.trailing,
backgroundColor: item.backgroundColor,
expandedBackgroundColor: item.expandedBackgroundColor,
animationDuration: widget.animationDuration,
animationCurve: widget.animationCurve,
children: item.children,
),
if (widget.showDividers && !isLast)
Divider(
height: widget.dividerHeight,
color: widget.dividerColor ?? theme.dividerColor,
).paddingSymmetrical(12.h,0),
],
);
}),
);
}
}
// ==================== SUPPORTING WIDGETS ====================
class _ExpandableListItemWidget extends StatelessWidget {
final Widget title;
final List<Widget> children;
final bool isExpanded;
final VoidCallback onTap;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? contentPadding;
final ExpandableListTheme theme;
final Widget? leading;
final Widget? trailing;
final Color? backgroundColor;
final Color? expandedBackgroundColor;
final Duration animationDuration;
final Curve animationCurve;
const _ExpandableListItemWidget({
super.key,
required this.title,
required this.children,
required this.isExpanded,
required this.onTap,
this.padding,
this.contentPadding,
required this.theme,
this.leading,
this.trailing,
this.backgroundColor,
this.expandedBackgroundColor,
required this.animationDuration,
required this.animationCurve,
});
@override
Widget build(BuildContext context) {
return Container(
color: isExpanded
? (expandedBackgroundColor ?? theme.expandedBackgroundColor)
: (backgroundColor ?? theme.backgroundColor),
child: Column(
children: [
// Header
InkWell(
onTap: onTap,
child: Padding(
padding: padding ?? theme.itemPadding,
child: Row(
children: [
if (leading != null) ...[
leading!,
SizedBox(width: theme.leadingSpacing),
],
Expanded(child: title),
if (trailing != null)
trailing!
else
AnimatedRotation(
turns: isExpanded ? 0.5 : 0.0,
duration: animationDuration,
curve: animationCurve,
child: theme.defaultTrailingIcon,
),
],
),
),
),
// Content
AnimatedSize(
duration: animationDuration,
curve: animationCurve,
child: Container(
constraints: BoxConstraints(
minHeight: isExpanded ? 0.0 : 0.0,
maxHeight: isExpanded ? double.infinity : 0.0,
),
child: isExpanded
? Padding(
padding: contentPadding ?? theme.contentPadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
)
: SizedBox.shrink(),
),
),
],
),
);
}
}
// ==================== DATA MODELS ====================
class ExpandableListItem {
final Widget title;
final List<Widget> children;
final Widget? leading;
final Widget? trailing;
final bool initiallyExpanded;
final Color? backgroundColor;
final Color? expandedBackgroundColor;
const ExpandableListItem({
required this.title,
required this.children,
this.leading,
this.trailing,
this.initiallyExpanded = false,
this.backgroundColor,
this.expandedBackgroundColor,
});
}
// ==================== THEME ====================
class ExpandableListTheme {
final Color backgroundColor;
final Color expandedBackgroundColor;
final Color dividerColor;
final Widget defaultTrailingIcon;
final EdgeInsetsGeometry itemPadding;
final EdgeInsetsGeometry contentPadding;
final double leadingSpacing;
const ExpandableListTheme({
required this.backgroundColor,
required this.expandedBackgroundColor,
required this.dividerColor,
required this.defaultTrailingIcon,
required this.itemPadding,
required this.contentPadding,
this.leadingSpacing = 12.0,
});
factory ExpandableListTheme.defaultTheme(BuildContext context) {
final textColor = Theme.of(context).textTheme.bodyLarge?.color ?? Colors.black;
return ExpandableListTheme(
backgroundColor: Colors.transparent,
expandedBackgroundColor: Colors.transparent,
dividerColor: Colors.grey.shade300,
defaultTrailingIcon: Icon(
Icons.keyboard_arrow_down,
color: textColor,
size: 24,
),
itemPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
contentPadding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0),
);
}
factory ExpandableListTheme.custom({
Color? backgroundColor,
Color? expandedBackgroundColor,
Color? dividerColor,
Widget? defaultTrailingIcon,
EdgeInsetsGeometry? itemPadding,
EdgeInsetsGeometry? contentPadding,
double? leadingSpacing,
}) {
return ExpandableListTheme(
backgroundColor: backgroundColor ?? Colors.transparent,
expandedBackgroundColor: expandedBackgroundColor ?? Colors.grey.shade50,
dividerColor: dividerColor ?? Colors.grey.shade300,
defaultTrailingIcon: defaultTrailingIcon ??
Icon(Icons.keyboard_arrow_down, color: Colors.black, size: 24),
itemPadding: itemPadding ?? const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
contentPadding: contentPadding ?? const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0),
leadingSpacing: leadingSpacing ?? 12.0,
);
}
}
// ==================== ENUMS ====================
enum ExpansionMode {
multiple, // Multiple items can be expanded
exactlyOne, // Exactly one item expanded at a time
atMostOne, // Zero or one item expanded at a time
}

@ -47,38 +47,40 @@ class TextInputWidget extends StatelessWidget {
final bool isMultiline;
final int minLines;
final int maxLines;
final Color? hintColor;
// final List<Country> countryList;
// final Function(Country)? onCountryChange;
TextInputWidget(
{super.key,
required this.labelText,
required this.hintText,
this.controller,
this.onChange,
this.onCalendarTypeChanged,
this.prefix,
this.isEnable = true,
this.isBorderAllowed = true,
this.isAllowRadius = true,
this.isReadOnly = false,
this.keyboardType = TextInputType.number,
this.focusNode,
this.autoFocus = false,
this.padding,
this.isAllowLeadingIcon = false,
this.leadingIcon,
this.isCountryDropDown = false,
this.hasError = false,
this.errorMessage,
this.onCountryChange,
this.selectionType,
this.fontSize,
this.isWalletAmountInput = false,
this.suffix,
this.labelColor,
TextInputWidget({
super.key,
required this.labelText,
required this.hintText,
this.controller,
this.onChange,
this.onCalendarTypeChanged,
this.prefix,
this.isEnable = true,
this.isBorderAllowed = true,
this.isAllowRadius = true,
this.isReadOnly = false,
this.keyboardType = TextInputType.number,
this.focusNode,
this.autoFocus = false,
this.padding,
this.isAllowLeadingIcon = false,
this.leadingIcon,
this.isCountryDropDown = false,
this.hasError = false,
this.errorMessage,
this.onCountryChange,
this.selectionType,
this.fontSize,
this.isWalletAmountInput = false,
this.suffix,
this.labelColor,
this.onSubmitted,
this.hintColor,
// multiline defaults
this.isMultiline = false,
this.minLines = 3,
@ -274,7 +276,7 @@ class TextInputWidget extends StatelessWidget {
decoration: InputDecoration(
isDense: true,
hintText: hintText,
hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.75),
hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), letterSpacing: -0.75),
prefixIconConstraints: BoxConstraints(minWidth: 30.h),
prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500),
contentPadding: EdgeInsets.zero,

@ -11,14 +11,13 @@ class CustomRadioOption<T> extends StatelessWidget {
// final Widget child; // The content of your radio option (e.g., Text, Image)
const CustomRadioOption({
super.key,
required this.value,
required this.groupValue,
required this.onChanged,
// required this.child,
required this.text,
});
const CustomRadioOption(
{super.key,
required this.value,
required this.groupValue,
required this.onChanged,
// required this.child,
required this.text});
@override
Widget build(BuildContext context) {
@ -31,13 +30,12 @@ class CustomRadioOption<T> extends StatelessWidget {
child: Row(
children: [
Container(
width: 20.h,
height: 20.h,
width: 18.h,
height: 18.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor,
border: Border.all(color: isSelected ? AppColors.primaryRedColor : AppColors.bottomNAVBorder, width: 2.h),
),
shape: BoxShape.circle,
color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor,
border: Border.all(color: isSelected ? AppColors.primaryRedColor : AppColors.bottomNAVBorder, width: 2.h)),
),
SizedBox(width: 8.h),
text.toText16(weight: FontWeight.w500), // The provided content

Loading…
Cancel
Save