Merge branch 'refs/heads/master' into dev_sikander

# Conflicts:
#	lib/core/app_assets.dart
pull/11/head
Sikander Saleem 2 months ago
commit 6473ccecb5

1
.gitignore vendored

@ -43,3 +43,4 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
/android/

@ -0,0 +1,3 @@
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.99984 12.7702C3.53655 12.7702 0.729004 9.96263 0.729004 6.49935C0.729004 3.03606 3.53655 0.228516 6.99984 0.228516C10.4631 0.228516 13.2707 3.03606 13.2707 6.49935C13.2707 9.96263 10.4631 12.7702 6.99984 12.7702ZM9.1623 5.16184C9.39012 4.93404 9.39013 4.5647 9.16233 4.33688C8.93453 4.10907 8.56519 4.10906 8.33737 4.33686L6.99972 5.67442L5.6623 4.33709C5.43449 4.10929 5.06514 4.1093 4.83734 4.33711C4.60955 4.56493 4.60956 4.93427 4.83737 5.16207L6.17474 6.49935L4.83737 7.83663C4.60956 8.06442 4.60955 8.43377 4.83734 8.66158C5.06514 8.8894 5.43449 8.88941 5.6623 8.66161L6.99972 7.32428L8.33737 8.66184C8.56519 8.88964 8.93453 8.88963 9.16233 8.66181C9.39013 8.434 9.39012 8.06465 9.1623 7.83686L7.82471 6.49935L9.1623 5.16184Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 909 B

@ -0,0 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.03033 6.96967C7.73744 6.67678 7.26256 6.67678 6.96967 6.96967C6.67678 7.26256 6.67678 7.73744 6.96967 8.03033L9.93934 11L6.96967 13.9697C6.67678 14.2626 6.67678 14.7374 6.96967 15.0303C7.26256 15.3232 7.73744 15.3232 8.03033 15.0303L11 12.0607L13.9697 15.0303C14.2626 15.3232 14.7374 15.3232 15.0303 15.0303C15.3232 14.7374 15.3232 14.2626 15.0303 13.9697L12.0607 11L15.0303 8.03033C15.3232 7.73744 15.3232 7.26256 15.0303 6.96967C14.7374 6.67678 14.2626 6.67678 13.9697 6.96967L11 9.93934L8.03033 6.96967Z" fill="#2B353E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 0.25C5.06294 0.25 0.25 5.06294 0.25 11C0.25 16.9371 5.06294 21.75 11 21.75C16.9371 21.75 21.75 16.9371 21.75 11C21.75 5.06294 16.9371 0.25 11 0.25ZM1.75 11C1.75 5.89137 5.89137 1.75 11 1.75C16.1086 1.75 20.25 5.89137 20.25 11C20.25 16.1086 16.1086 20.25 11 20.25C5.89137 20.25 1.75 16.1086 1.75 11Z" fill="#2B353E"/>
</svg>

After

Width:  |  Height:  |  Size: 1018 B

@ -0,0 +1,4 @@
<svg width="22" height="23" viewBox="0 0 22 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.4932 13.7808C20.7528 9.09639 17.955 4.27023 13.2297 3.00853C9.70851 2.06832 6.11906 3.37593 3.99728 6.04158C3.84468 6.2333 3.86798 6.50772 4.04125 6.68099L5.04846 7.6882C5.18151 7.82125 5.1938 8.04137 5.18151 8.24316C5.14872 8.78189 4.6854 9.19203 4.14668 9.15923L1.69362 9.00989C1.38358 8.99102 1.10097 8.82587 0.932324 8.56502C0.76368 8.30416 0.72909 7.97867 0.839142 7.6882C2.77489 2.57901 8.3266 -0.323654 13.7339 1.12017C19.4935 2.65805 22.9235 8.55039 21.3807 14.2883C19.8381 20.025 13.9137 23.4174 8.1554 21.8799C3.87709 20.7375 0.886935 17.1944 0.261338 13.0982C0.179854 12.5647 0.546317 12.0661 1.07986 11.9846C1.61339 11.9031 2.11197 12.2696 2.19345 12.8031C2.70523 16.1541 5.15307 19.0552 8.65962 19.9915C13.3861 21.2536 18.2332 18.4666 19.4932 13.7808Z" fill="#ED1C2B"/>
<path d="M12 7.5C12 6.94772 11.5523 6.5 11 6.5C10.4477 6.5 10 6.94772 10 7.5V11.5C10 11.7652 10.1054 12.0196 10.2929 12.2071L12.2929 14.2071C12.6834 14.5976 13.3166 14.5976 13.7071 14.2071C14.0976 13.8166 14.0976 13.1834 13.7071 12.7929L12 11.0858V7.5Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,4 @@
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.16613 2.14867C6.59446 2.05148 7.04084 2 7.50016 2C10.8139 2 13.5002 4.68629 13.5002 8C13.5002 9.53082 12.9272 10.928 11.9827 11.9886L11.9827 11C11.9827 10.5858 11.6469 10.25 11.2327 10.25C10.8185 10.25 10.4827 10.5858 10.4827 11L10.4827 13.625C10.4827 14.0392 10.8185 14.375 11.2327 14.375H13.8752C14.2894 14.375 14.6252 14.0392 14.6252 13.625C14.6252 13.2108 14.2894 12.875 13.8752 12.875H13.1997C14.3217 11.5644 15.0002 9.86153 15.0002 8C15.0002 3.85786 11.6423 0.5 7.50016 0.5C6.9284 0.5 6.3707 0.564117 5.8342 0.685856C5.43025 0.777516 5.1771 1.17928 5.26876 1.58323C5.36042 1.98717 5.76218 2.24033 6.16613 2.14867Z" fill="#18C273"/>
<path d="M1.125 1.63672C0.710786 1.63672 0.375 1.97251 0.375 2.38672C0.375 2.80093 0.710786 3.13672 1.125 3.13672H1.79048C0.674497 4.44574 0 6.14405 0 8.00006C0 12.1422 3.35786 15.5001 7.5 15.5001C8.07176 15.5001 8.62946 15.4359 9.16597 15.3142C9.56991 15.2225 9.82307 14.8208 9.73141 14.4168C9.63975 14.0129 9.23798 13.7597 8.83404 13.8514C8.4057 13.9486 7.95932 14.0001 7.5 14.0001C4.18629 14.0001 1.5 11.3138 1.5 8.00006C1.5 6.4787 2.06588 5.08936 3 4.03118V5.00006C3 5.41427 3.33579 5.75006 3.75 5.75006C4.16421 5.75006 4.5 5.41427 4.5 5.00006V2.38672C4.5 1.97251 4.16421 1.63672 3.75 1.63672L1.125 1.63672Z" fill="#18C273"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because it is too large Load Diff

@ -782,8 +782,29 @@
"resultsPending": "النتائج معلقة", "resultsPending": "النتائج معلقة",
"resultsAvailable": "النتائج متاحة", "resultsAvailable": "النتائج متاحة",
"viewReport": "عرض التقرير", "viewReport": "عرض التقرير",
"prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.",
"checkAvailability": "التحقق من التوفر", "checkAvailability": "التحقق من التوفر",
"readInstructions": "قراءة التعليمات" "readInstructions": "قراءة التعليمات",
"searchLabReport" : "ابحث عن تقرير المختبر",
"prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.",
"receiveOtpToast": "أين تود تلقي رمز التحقق OTP؟",
"enterPhoneNumber": "أدخل رقم الهاتف",
"enterEmailDesc": "أدخل عنوان بريدك الإلكتروني لإكمال عملية إنشاء ملف طبي",
"enterPhoneDesc": "أدخل رقم هاتفك لتلقي رمز التحقق ",
"pleaseChooseOption": "الرجاء اختيار من الخيارات أدناه لتلقي رمز التحقق OTP",
"prepareToElevate": "هل أنت مستعد لتحسين صحتك ورفاهتك؟",
"iAcceptTermsConditions": "أوافق على الشروط والأحكام",
"alreadyHaveAccount": "هل لديك حساب بالفعل؟",
"loginNow": "تسجيل الدخول الآن",
"notice": "إشعار",
"oR": "أو",
"sendOTPWHATSAPP": "أرسل لي OTP عبر واتساب",
"sendOTPSMS": "أرسل لي OTP عبر الرسائل القصيرة",
"fullName": "الاسم الكامل",
"married": "متزوج",
"uae": "الإمارات العربية المتحدة",
"malE": "ذكر",
"loginBy": "تسجيل الدخول بواسطة",
"loginByOTP": "تسجيل الدخول بواسطة OTP",
"guest": "زائر",
"switchAccount": "تبديل الحساب"
} }

@ -772,13 +772,35 @@
"aboutApp": "About the app", "aboutApp": "About the app",
"aboutPoints": "Online Appointment Booking & rescheduling, Insurance approval status, Find A doctor, Ask your doctor, Medical prescriptions, Lab results, Hospitals contact numbers, Doctor profiles, Hospitals locations, Pharmacies Locations, Hospital's Virtual Tour, Official Social Media, Vaccines Schedule, Health Calculators, Other Services", "aboutPoints": "Online Appointment Booking & rescheduling, Insurance approval status, Find A doctor, Ask your doctor, Medical prescriptions, Lab results, Hospitals contact numbers, Doctor profiles, Hospitals locations, Pharmacies Locations, Hospital's Virtual Tour, Official Social Media, Vaccines Schedule, Health Calculators, Other Services",
"termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...", "termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...",
"receiveOtpToast": "Where would you like to receive OTP?",
"enterPhoneNumber": "Enter Phone Number",
"enterEmailDesc": "Enter your email address to complete the process of creating a medical file",
"enterPhoneDesc": "Enter your phone number to receive OTP verification code",
"pleaseChooseOption": "Please select from the below options to receive OTP",
"dontHaveAccount": "Don't have an account?", "dontHaveAccount": "Don't have an account?",
"loginOrRegister": "Login or Register", "loginOrRegister": "Login or Register",
"myFiles": "My Files", "myFiles": "My Files",
"resultsPending": "Results Pending", "resultsPending": "Results Pending",
"resultsAvailable": "Results Available", "resultsAvailable": "Results Available",
"viewReport": "View Report", "viewReport": "View Report",
"prescriptionDeliveryError": "This clinic doesn't support refill",
"checkAvailability": "Check Availability", "checkAvailability": "Check Availability",
"readInstructions": "Read Instructions" "readInstructions": "Read Instructions",
"searchLabReport" : "Search Lab Report",
"prescriptionDeliveryError": "This clinic doesn't support refill",
"prepareToElevate": "Prepared to elevate your health and well-being?",
"iAcceptTermsConditions": "I Accept the Terms and Conditions",
"alreadyHaveAccount": "Already have an account?",
"loginNow": "Login Now",
"notice": "Notice",
"oR": "OR",
"sendOTPWHATSAPP": "Send me OTP on Whatsapp",
"sendOTPSMS": "Send me OTP on SMS",
"fullName": "Full Name",
"married": "Married",
"uae" : "United Arab Emirates",
"malE": "Male",
"loginBy": "Login By",
"loginByOTP": "Login By OTP",
"guest": "Guest",
"switchAccount": "Switch Account"
} }

@ -68,17 +68,21 @@ class AppAssets {
static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg'; static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg';
static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg'; static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg';
static const String prescription_reminder_icon = '$svgBasePath/prescription_reminder_icon.svg'; static const String prescription_reminder_icon = '$svgBasePath/prescription_reminder_icon.svg';
static const String insurance_history_icon = '$svgBasePath/insurance_history_icon.svg';
static const String cancel_circle_icon = '$svgBasePath/cancel_circle.svg';
static const String update_insurance_card_icon = '$svgBasePath/update_insurance_card.svg';
static const String close_bottom_sheet_icon = '$svgBasePath/close_bottom_sheet_icon.svg';
static const String insurance = '$svgBasePath/insurance.svg'; static const String insurance = '$svgBasePath/insurance.svg';
static const String requests = '$svgBasePath/requests.svg'; static const String requests = '$svgBasePath/requests.svg';
static const String more = '$svgBasePath/more.svg'; static const String more = '$svgBasePath/more.svg';
//bottom navigation// //bottom navigation//
static const String homeBottom = '$svgBasePath/home_bottom.svg'; static const String homeBottom = '$svgBasePath/home_bottom.svg';
static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg'; static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg';
static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg'; static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg';
static const String toDoBottom = '$svgBasePath/todo_bottom.svg'; static const String toDoBottom = '$svgBasePath/todo_bottom.svg';
static const String servicesBottom = '$svgBasePath/services_bottom.svg'; static const String servicesBottom = '$svgBasePath/services_bottom.svg';
static const String closeBottomNav = '$svgBasePath/close_bottom_nav.svg';
// PNGS // // PNGS //
static const String hmg_logo = '$pngBasePath/hmg_logo.png'; static const String hmg_logo = '$pngBasePath/hmg_logo.png';

@ -22,13 +22,8 @@ class AppState {
set setUserLong(v) => userLong = v; set setUserLong(v) => userLong = v;
final PostParamsModel _postParamsInitConfig = PostParamsModel( final PostParamsModel _postParamsInitConfig =
channel: 3, PostParamsModel(channel: 3, versionID: ApiConsts.VERSION_ID, ipAddress: '10.20.10.20', generalId: 'Cs2020@2016\$2958', deviceTypeID: "2", sessionID: 'TMRhVmkGhOsvamErw');
versionID: ApiConsts.VERSION_ID,
ipAddress: '10.20.10.20',
generalId: 'Cs2020@2016\$2958',
deviceTypeID: "2",
sessionID: 'TMRhVmkGhOsvamErw');
void setPostParamsInitConfig() { void setPostParamsInitConfig() {
isAuthenticated = false; isAuthenticated = false;
@ -50,7 +45,9 @@ class AppState {
bool isArabic() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar"; bool isArabic() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar";
int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; int getLanguageID() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar" ? 1 : 2;
String? getLanguageCode() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode;
AuthenticatedUser? _authenticatedUser; AuthenticatedUser? _authenticatedUser;

@ -0,0 +1,29 @@
import 'dart:convert';
class NationalityCountries {
String? id;
String? name;
String? nameN;
NationalityCountries({
this.id,
this.name,
this.nameN,
});
factory NationalityCountries.fromRawJson(String str) => NationalityCountries.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory NationalityCountries.fromJson(Map<String, dynamic> json) => NationalityCountries(
id: json["ID"],
name: json["Name"],
nameN: json["NameN"],
);
Map<String, dynamic> toJson() => {
"ID": id,
"Name": name,
"NameN": nameN,
};
}

@ -6,6 +6,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo.
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_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_repo.dart';
import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
@ -65,6 +67,7 @@ class AppDependencies {
getIt.registerLazySingleton<LabRepo>(() => LabRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<LabRepo>(() => LabRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<RadiologyRepo>(() => RadiologyRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<RadiologyRepo>(() => RadiologyRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<PrescriptionsRepo>(() => PrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<PrescriptionsRepo>(() => PrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<InsuranceRepo>(() => InsuranceRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels // ViewModels
// Global/shared VMs LazySingleton // Global/shared VMs LazySingleton
@ -90,6 +93,13 @@ class AppDependencies {
), ),
); );
getIt.registerLazySingleton<InsuranceViewModel>(
() => InsuranceViewModel(
insuranceRepo: getIt(),
errorHandlerService: getIt(),
),
);
getIt.registerLazySingleton<AuthenticationViewModel>( getIt.registerLazySingleton<AuthenticationViewModel>(
() => AuthenticationViewModel( () => AuthenticationViewModel(
authenticationRepo: getIt(), authenticationRepo: getIt(),

@ -22,12 +22,6 @@ enum ViewStateEnum {
errorLocal, errorLocal,
} }
enum LoginTypeEnum {
fromLogin,
silentLogin,
silentWithOTP,
}
enum OTPTypeEnum { sms, whatsapp } enum OTPTypeEnum { sms, whatsapp }
enum CountryEnum { saudiArabia, unitedArabEmirates } enum CountryEnum { saudiArabia, unitedArabEmirates }
@ -40,6 +34,51 @@ enum MaritalStatusTypeEnum { single, married, divorced, widowed }
enum ChipTypeEnum { success, error, alert, info, warning } enum ChipTypeEnum { success, error, alert, info, warning }
enum LoginTypeEnum { sms, whatsapp, face, fingerprint }
extension LoginTypeExtension on LoginTypeEnum {
int get toInt {
switch (this) {
case LoginTypeEnum.sms:
return 1;
case LoginTypeEnum.whatsapp:
return 4;
case LoginTypeEnum.face:
return 3;
case LoginTypeEnum.fingerprint:
return 2;
}
}
String get displayName {
switch (this) {
case LoginTypeEnum.sms:
return 'SMS';
case LoginTypeEnum.whatsapp:
return 'WhatsApp';
case LoginTypeEnum.face:
return 'Face Recognition';
case LoginTypeEnum.fingerprint:
return 'Fingerprint';
}
}
static LoginTypeEnum? fromValue(int value) {
switch (value) {
case 1:
return LoginTypeEnum.sms;
case 2:
return LoginTypeEnum.fingerprint;
case 3:
return LoginTypeEnum.face;
case 4:
return LoginTypeEnum.whatsapp;
default:
return null;
}
}
}
extension OTPTypeEnumExtension on OTPTypeEnum { extension OTPTypeEnumExtension on OTPTypeEnum {
/// Convert enum to int /// Convert enum to int
int toInt() { int toInt() {

@ -11,6 +11,10 @@ extension ResponsiveExtension on num {
double get h => ((this * _width) / FIGMA_DESIGN_WIDTH); double get h => ((this * _width) / FIGMA_DESIGN_WIDTH);
double get fSize => ((this * _width) / FIGMA_DESIGN_WIDTH); double get fSize => ((this * _width) / FIGMA_DESIGN_WIDTH);
static double get screenHeight => SizeUtils.height;
/// Full screen width
static double get screenWidth => SizeUtils.width;
} }
extension FormatExtension on double { extension FormatExtension on double {

@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart'; import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart';
@ -59,14 +60,13 @@ class Utils {
// )); // ));
return !isAddHours return !isAddHours
? DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") ? DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US")
.format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal()) .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal())
: DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") : DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US")
.format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add( .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add(
Duration( Duration(
hours: isAddHours ? 3 : 0, hours: isAddHours ? 3 : 0,
), ),
)); ));
;
} }
static String convertStringToDateTime(String dateTimeString) { static String convertStringToDateTime(String dateTimeString) {
@ -209,12 +209,12 @@ class Utils {
builder: (BuildContext context) => LoadingDialog(), builder: (BuildContext context) => LoadingDialog(),
) )
.then((value) { .then((value) {
_isLoadingVisible = false; _isLoadingVisible = false;
}) })
.catchError((e) {}) .catchError((e) {})
.onError( .onError(
(error, stackTrace) {}, (error, stackTrace) {},
); );
} }
static void hideLoading() { static void hideLoading() {
@ -236,11 +236,12 @@ class Utils {
showDialog( showDialog(
barrierDismissible: false, barrierDismissible: false,
context: context, context: context,
builder: (cxt) => ConfirmDialog( builder: (cxt) =>
title: title!, ConfirmDialog(
message: message!, title: title!,
onTap: onTap, message: message!,
), onTap: onTap,
),
); );
} }
@ -371,7 +372,9 @@ class Utils {
static String formatHijriDateToDisplay(String hijriDateString) { static String formatHijriDateToDisplay(String hijriDateString) {
try { try {
// Assuming hijriDateString is in the format yyyy-MM-dd // Assuming hijriDateString is in the format yyyy-MM-dd
final datePart = hijriDateString.split("T").first; final datePart = hijriDateString
.split("T")
.first;
final parts = datePart.split('-'); final parts = datePart.split('-');
if (parts.length != 3) return ""; if (parts.length != 3) return "";
@ -429,8 +432,14 @@ class Utils {
void Function(LottieComposition)? onLoaded, void Function(LottieComposition)? onLoaded,
}) { }) {
return Lottie.asset(assetPath, return Lottie.asset(assetPath,
height: height ?? MediaQuery.of(context).size.height * 0.26, height: height ?? MediaQuery
width: width ?? MediaQuery.of(context).size.width, .of(context)
.size
.height * 0.26,
width: width ?? MediaQuery
.of(context)
.size
.width,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
repeat: repeat, repeat: repeat,
@ -487,9 +496,13 @@ class Utils {
], ],
); );
} }
static Future<bool> isGoogleServicesAvailable() async { static Future<bool> isGoogleServicesAvailable() async {
GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability(); GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability();
String status = availability.toString().split('.').last; String status = availability
.toString()
.split('.')
.last;
if (status == "success") { if (status == "success") {
return true; return true;
} }
@ -510,3 +523,26 @@ class Utils {
return crypto.md5.convert(utf8.encode(input)).toString(); return crypto.md5.convert(utf8.encode(input)).toString();
} }
} }
class ValidationUtils {
static DialogService dialogService = getIt.get<DialogService>();
static bool isValidatePhoneAndId({
String? nationalId,
String? phoneNumber
}) {
if (nationalId == null || nationalId.isEmpty) {
dialogService.showErrorDialog(message: "Please enter a valid national ID or file number", onOkPressed: () {});
return false;
}
if (phoneNumber == null || phoneNumber.isEmpty) {
dialogService.showErrorDialog(message: "Please enter a valid phone number", onOkPressed: () {});
return false;
}
return true;
}
}

@ -2,15 +2,32 @@ import 'package:flutter/material.dart';
extension ContextUtils on BuildContext { extension ContextUtils on BuildContext {
double get screenHeight => MediaQuery.of(this).size.height; double get screenHeight => MediaQuery.of(this).size.height;
double get screenWidth => MediaQuery.of(this).size.width; double get screenWidth => MediaQuery.of(this).size.width;
ThemeData get theme => Theme.of(this); ThemeData get theme => Theme.of(this);
TextTheme get textTheme => theme.textTheme; TextTheme get textTheme => theme.textTheme;
// TextStyle get headline1 => textTheme.headline1!; // TextStyle get headline1 => textTheme.headline1!;
// TextStyle get headline2 => textTheme.headline2!; // TextStyle get headline2 => textTheme.headline2!;
// TextStyle get headline3 => textTheme.headline3!; // TextStyle get headline3 => textTheme.headline3!;
// TextStyle get headline4 => textTheme.headline4!; // TextStyle get headline4 => textTheme.headline4!;
// TextStyle get headline5 => textTheme.headline5!; // TextStyle get headline5 => textTheme.headline5!;
// TextStyle get headline6 => textTheme.headline6!; // TextStyle get headline6 => textTheme.headline6!;
// TextStyle get bodyText1 => textTheme.bodyText1!; // TextStyle get bodyText1 => textTheme.bodyText1!;
// TextStyle get bodyText2 => textTheme.bodyText2!; // TextStyle get bodyText2 => textTheme.bodyText2!;
}
extension ShowBottomSheet on BuildContext {
Future<T?> showBottomSheet<T>({isScrollControlled = true, isDismissible = false, required Widget child, Color? backgroundColor, enableDra = false, useSafeArea = false}) {
return showModalBottomSheet<T>(
context: this,
isScrollControlled: isScrollControlled,
isDismissible: isDismissible,
enableDrag: enableDra,
useSafeArea: useSafeArea,
backgroundColor: backgroundColor ?? Colors.transparent,
builder: (_) => child,
);
}
} }

@ -1,3 +1,5 @@
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -11,16 +13,13 @@ extension CapExtension on String {
String get allInCaps => this.toUpperCase(); String get allInCaps => this.toUpperCase();
String get capitalizeFirstofEach => String get capitalizeFirstofEach => this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : "";
this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : "";
} }
extension EmailValidator on String { extension EmailValidator on String {
Widget get toWidget => Text(this); Widget get toWidget => Text(this);
Widget toText8( Widget toText8({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
{Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) =>
Text(
this, this,
maxLines: maxlines, maxLines: maxlines,
overflow: textOverflow, overflow: textOverflow,
@ -33,14 +32,7 @@ extension EmailValidator on String {
), ),
); );
Widget toText10( Widget toText10({Color? color, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
{Color? color,
bool isBold = false,
bool isUnderLine = false,
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow}) =>
Text(
this, this,
maxLines: maxlines, maxLines: maxlines,
overflow: textOverflow, overflow: textOverflow,
@ -54,15 +46,7 @@ extension EmailValidator on String {
decorationColor: color ?? AppColors.blackColor), decorationColor: color ?? AppColors.blackColor),
); );
Widget toText11( Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0.64}) => Text(
{Color? color,
FontWeight? weight,
bool isUnderLine = false,
bool isCenter = false,
bool isBold = false,
int maxLine = 0,
double letterSpacing = 0.64}) =>
Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null, maxLines: (maxLine > 0) ? maxLine : null,
@ -76,20 +60,11 @@ extension EmailValidator on String {
), ),
); );
Widget toText12( Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, int maxLine = 0}) => Text(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
FontWeight? fontWeight,
bool isCenter = false,
double? height,
int maxLine = 0}) =>
Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null, maxLines: (maxLine > 0) ? maxLine : null,
style: TextStyle( style: TextStyle(
fontSize: 12.fSize, fontSize: 12.fSize,
fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor, color: color ?? AppColors.blackColor,
@ -100,9 +75,7 @@ extension EmailValidator on String {
), ),
); );
Widget toText12Auto( Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => AutoSizeText(
{Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) =>
AutoSizeText(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null, maxLines: (maxLine > 0) ? maxLine : null,
@ -163,14 +136,7 @@ extension EmailValidator on String {
decoration: isUnderLine ? TextDecoration.underline : null), decoration: isUnderLine ? TextDecoration.underline : null),
); );
Widget toText14( Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
FontWeight? weight,
int? maxlines}) =>
Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines, maxLines: maxlines,
@ -182,14 +148,7 @@ extension EmailValidator on String {
decoration: isUnderLine ? TextDecoration.underline : null), decoration: isUnderLine ? TextDecoration.underline : null),
); );
Widget toText15( Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
FontWeight? weight,
int? maxlines}) =>
Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines, maxLines: maxlines,
@ -227,93 +186,68 @@ extension EmailValidator on String {
Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
style: TextStyle( style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor,
fontSize: 17.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
); );
Widget toText18({Color? color, bool isBold = false, bool isCenter = false, int? maxlines}) => Text( Widget toText18({Color? color, bool isBold = false, bool isCenter = false, int? maxlines}) => Text(
maxLines: maxlines, maxLines: maxlines,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
this, this,
style: TextStyle( style: TextStyle(fontSize: 18.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
fontSize: 18.fSize,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
); );
Widget toText19({Color? color, bool isBold = false}) => Text( Widget toText19({Color? color, bool isBold = false}) => Text(
this, this,
style: TextStyle( style: TextStyle(fontSize: 19.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
fontSize: 19.fSize,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
); );
Widget toText20({Color? color, bool isBold = false}) => Text( Widget toText20({Color? color, bool isBold = false}) => Text(
this, this,
style: TextStyle( style: TextStyle(fontSize: 20.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
fontSize: 20.fSize,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
); );
Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text(
this, this,
maxLines: maxlines, maxLines: maxlines,
style: TextStyle( style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.fSize, letterSpacing: -0.4, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
color: color ?? AppColors.blackColor,
fontSize: 21.fSize,
letterSpacing: -0.4,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
); );
Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
style: TextStyle( style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
height: 1,
color: color ?? AppColors.blackColor,
fontSize: 22.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
); );
Widget toText24({Color? color, bool isBold = false, bool isCenter = false}) => Text( Widget toText24({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
style: TextStyle( style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
height: 23 / 24,
color: color ?? AppColors.blackColor,
fontSize: 24.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
); );
Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text( Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
style: TextStyle( style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
height: 32 / 32,
color: color ?? AppColors.blackColor,
fontSize: 32.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
); );
Widget toText44({Color? color, bool isBold = false}) => Text( Widget toText44({Color? color, bool isBold = false}) => Text(
this, this,
style: TextStyle( style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
height: 32 / 32,
color: color ?? AppColors.blackColor,
fontSize: 44.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
); );
Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) {
@ -349,9 +283,7 @@ extension EmailValidator on String {
} }
bool isValidEmail() { bool isValidEmail() {
return RegExp( return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this);
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
.hasMatch(this);
} }
String toFormattedDate() { String toFormattedDate() {
@ -453,3 +385,155 @@ class FontUtils {
return isArabic ? 'Cairo' : 'Poppins'; return isArabic ? 'Cairo' : 'Poppins';
} }
} }
extension CountryExtension on CountryEnum {
String get displayName {
switch (this) {
case CountryEnum.saudiArabia:
return "Kingdom Of Saudi Arabia";
case CountryEnum.unitedArabEmirates:
return "United Arab Emirates";
}
}
String get nameArabic {
switch (this) {
case CountryEnum.saudiArabia:
return "المملكة العربية السعودية";
case CountryEnum.unitedArabEmirates:
return "الإمارات العربية المتحدة";
}
}
String get iconPath {
switch (this) {
case CountryEnum.saudiArabia:
return AppAssets.ksa;
case CountryEnum.unitedArabEmirates:
return AppAssets.uae;
}
}
String get countryCode {
switch (this) {
case CountryEnum.saudiArabia:
return "966";
case CountryEnum.unitedArabEmirates:
return "971";
}
}
static CountryEnum fromDisplayName(String name) {
switch (name) {
case "Kingdom Of Saudi Arabia":
case "المملكة العربية السعودية":
return CountryEnum.saudiArabia;
case "United Arab Emirates":
case "الإمارات العربية المتحدة":
return CountryEnum.unitedArabEmirates;
default:
throw Exception("Invalid country name");
}
}
}
extension GenderTypeExtension on GenderTypeEnum {
String get value => this == GenderTypeEnum.male ? "M" : "F";
String get type => this == GenderTypeEnum.male ? "Male" : "Female";
String get typeAr => this == GenderTypeEnum.male ? "ذكر" : "أنثى";
static GenderTypeEnum? fromValue(String? value) {
switch (value) {
case "M":
return GenderTypeEnum.male;
case "F":
return GenderTypeEnum.female;
default:
return null;
}
}
static GenderTypeEnum? fromType(String? type) {
switch (type) {
case "Male":
return GenderTypeEnum.male;
case "Female":
return GenderTypeEnum.female;
default:
return null;
}
}
}
extension MaritalStatusTypeExtension on MaritalStatusTypeEnum {
String get value {
switch (this) {
case MaritalStatusTypeEnum.single:
return "U";
case MaritalStatusTypeEnum.married:
return "M";
case MaritalStatusTypeEnum.divorced:
return "D";
case MaritalStatusTypeEnum.widowed:
return "W";
}
}
String get type {
switch (this) {
case MaritalStatusTypeEnum.single:
return "Single";
case MaritalStatusTypeEnum.married:
return "Married";
case MaritalStatusTypeEnum.divorced:
return "Divorced";
case MaritalStatusTypeEnum.widowed:
return "Widowed";
}
}
String get typeAr {
switch (this) {
case MaritalStatusTypeEnum.single:
return "أعزب";
case MaritalStatusTypeEnum.married:
return "متزوج";
case MaritalStatusTypeEnum.divorced:
return "مطلق";
case MaritalStatusTypeEnum.widowed:
return "أرمل";
}
}
static MaritalStatusTypeEnum? fromValue(String? value) {
switch (value) {
case "U":
return MaritalStatusTypeEnum.single;
case "M":
return MaritalStatusTypeEnum.married;
case "D":
return MaritalStatusTypeEnum.divorced;
case "W":
return MaritalStatusTypeEnum.widowed;
default:
return null;
}
}
static MaritalStatusTypeEnum? fromType(String? type) {
switch (type) {
case "Single":
return MaritalStatusTypeEnum.single;
case "Married":
return MaritalStatusTypeEnum.married;
case "Divorced":
return MaritalStatusTypeEnum.divorced;
case "Widowed":
return MaritalStatusTypeEnum.widowed;
default:
return null;
}
}
}

@ -20,8 +20,7 @@ abstract class AuthenticationRepo {
required CheckPatientAuthenticationReq checkPatientAuthenticationReq, required CheckPatientAuthenticationReq checkPatientAuthenticationReq,
}); });
Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister( Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister({required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID});
{required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID});
} }
class AuthenticationRepoImp implements AuthenticationRepo { class AuthenticationRepoImp implements AuthenticationRepo {
@ -114,8 +113,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
} }
@override @override
Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister( Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister({required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}) async {
{required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}) async {
int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1; int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1;
//TODO : We will use all these from AppState directly in the ApiClient //TODO : We will use all these from AppState directly in the ApiClient

@ -1,7 +1,12 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart';
import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_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/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart';
import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_patient_authentication_request_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_patient_authentication_request_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart';
@ -20,12 +25,67 @@ class AuthenticationViewModel extends ChangeNotifier {
required this.dialogService, required this.dialogService,
}); });
final TextEditingController nationalIdController = TextEditingController(); final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController();
final TextEditingController phoneNumberController = TextEditingController(); CountryEnum selectedCountrySignup = CountryEnum.saudiArabia;
MaritalStatusTypeEnum? maritalStatus;
GenderTypeEnum? genderType;
bool isTermsAccepted = false;
List<NationalityCountries>? countriesList;
NationalityCountries? pickedCountryByUAEUser;
void login() {
if (ValidationUtils.isValidatePhoneAndId(nationalId: nationalIdController.text, phoneNumber: phoneNumberController.text)) {
} else {}
}
void clearDefaults() {
nationalIdController.clear();
phoneNumberController.clear();
dobController.clear();
maritalStatus = null;
genderType = null;
isTermsAccepted = false;
selectedCountrySignup = CountryEnum.saudiArabia;
pickedCountryByUAEUser = null;
}
void onCountryChange(CountryEnum country) {
selectedCountrySignup = country;
notifyListeners();
}
void loadCountriesData({required BuildContext context}) async {
final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json');
final List<dynamic> data = json.decode(response);
countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList();
}
void onMaritalStatusChange(String? status) {
maritalStatus = MaritalStatusTypeExtension.fromType(status)!;
notifyListeners();
}
void onGenderChange(String? status) {
genderType = GenderTypeExtension.fromType(status)!;
notifyListeners();
}
void onUAEUserCountrySelection(String? value) {
pickedCountryByUAEUser = countriesList!.firstWhere((element) => element.name == value);
notifyListeners();
}
void onPhoneNumberChange(String? phoneNumber) {
phoneNumberController.text = phoneNumber!;
}
void onTermAccepted() {
isTermsAccepted = !isTermsAccepted;
notifyListeners();
}
Future<void> selectDeviceImei({Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> selectDeviceImei({Function(dynamic)? onSuccess, Function(String)? onError}) async {
String firebaseToken = String firebaseToken = "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc";
"dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc";
final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken);
result.fold( result.fold(
@ -40,96 +100,96 @@ class AuthenticationViewModel extends ChangeNotifier {
); );
} }
// Future<void> checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async { // Future<void> checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome( // CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome(
// phoneNumber: '0567184134', // phoneNumber: '0567184134',
// otpTypeEnum: OTPTypeEnum.sms, // otpTypeEnum: OTPTypeEnum.sms,
// deviceToken: 'dummyDeviceToken123', // deviceToken: 'dummyDeviceToken123',
// patientOutSA: true, // patientOutSA: true,
// loginTokenID: 'dummyLoginToken456', // loginTokenID: 'dummyLoginToken456',
// registeredData: null, // registeredData: null,
// patientId: 12345, // patientId: 12345,
// nationIdText: '1234567890', // nationIdText: '1234567890',
// countryCode: 'SA', // countryCode: 'SA',
// ); // );
// //
// final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); // final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
// result.fold( // result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure), // (failure) async => await errorHandlerService.handleError(failure: failure),
// (apiResponse) { // (apiResponse) {
// if (apiResponse.data['isSMSSent']) { // if (apiResponse.data['isSMSSent']) {
// // TODO: set this in AppState // // TODO: set this in AppState
// // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']); // // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']);
// // loginTokenID = value['LogInTokenID'], // // loginTokenID = value['LogInTokenID'],
// // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request), // // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request),
// sendActivationCode(type); // sendActivationCode(type);
// } else { // } else {
// if (apiResponse.data['IsAuthenticated']) { // if (apiResponse.data['IsAuthenticated']) {
// checkActivationCode(onWrongActivationCode: (String? message) {}); // checkActivationCode(onWrongActivationCode: (String? message) {});
// } // }
// } // }
// }, // },
// ); // );
// } // }
// Future<void> sendActivationCode({required OTPTypeEnum otpTypeEnum}) async { // Future<void> sendActivationCode({required OTPTypeEnum otpTypeEnum}) async {
// var request = RequestUtils.getCommonRequestAuthProvider( // var request = RequestUtils.getCommonRequestAuthProvider(
// otpTypeEnum: otpTypeEnum, // otpTypeEnum: otpTypeEnum,
// registeredData: null, // registeredData: null,
// deviceToken: "dummyLoginToken456", // deviceToken: "dummyLoginToken456",
// mobileNumber: "0567184134", // mobileNumber: "0567184134",
// zipCode: "SA", // zipCode: "SA",
// patientOutSA: true, // patientOutSA: true,
// loginTokenID: "dummyLoginToken456", // loginTokenID: "dummyLoginToken456",
// selectedOption: selectedOption, // selectedOption: selectedOption,
// patientId: 12345, // patientId: 12345,
// ); // );
// //
// request.sMSSignature = await SMSOTP.getSignature(); // request.sMSSignature = await SMSOTP.getSignature();
// selectedOption = type; // selectedOption = type;
// // GifLoaderDialogUtils.showMyDialog(context); // // GifLoaderDialogUtils.showMyDialog(context);
// if (healthId != null || isDubai) { // if (healthId != null || isDubai) {
// if (!isDubai) { // if (!isDubai) {
// request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob)); // request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob));
// } // }
// request.healthId = healthId; // request.healthId = healthId;
// request.isHijri = isHijri; // request.isHijri = isHijri;
// await this.apiClient.sendActivationCodeRegister(request).then((result) { // await this.apiClient.sendActivationCodeRegister(request).then((result) {
// // GifLoaderDialogUtils.hideDialog(context); // // GifLoaderDialogUtils.hideDialog(context);
// if (result != null && result['isSMSSent'] == true) { // if (result != null && result['isSMSSent'] == true) {
// this.startSMSService(type); // this.startSMSService(type);
// } // }
// }).catchError((r) { // }).catchError((r) {
// GifLoaderDialogUtils.hideDialog(context); // GifLoaderDialogUtils.hideDialog(context);
// context.showBottomSheet( // context.showBottomSheet(
// child: ExceptionBottomSheet( // child: ExceptionBottomSheet(
// message: r.toString(), // message: r.toString(),
// onOkPressed: () { // onOkPressed: () {
// Navigator.of(context).pop(); // Navigator.of(context).pop();
// }, // },
// )); // ));
// // AppToast.showErrorToast(message: r); // // AppToast.showErrorToast(message: r);
// }); // });
// } else { // } else {
// request.dob = ""; // request.dob = "";
// request.healthId = ""; // request.healthId = "";
// request.isHijri = 0; // request.isHijri = 0;
// await this.authService.sendActivationCode(request).then((result) { // await this.authService.sendActivationCode(request).then((result) {
// GifLoaderDialogUtils.hideDialog(context); // GifLoaderDialogUtils.hideDialog(context);
// if (result != null && result['isSMSSent'] == true) { // if (result != null && result['isSMSSent'] == true) {
// this.startSMSService(type); // this.startSMSService(type);
// } // }
// }).catchError((r) { // }).catchError((r) {
// GifLoaderDialogUtils.hideDialog(context); // GifLoaderDialogUtils.hideDialog(context);
// context.showBottomSheet( // context.showBottomSheet(
// child: ExceptionBottomSheet( // child: ExceptionBottomSheet(
// message: r.toString(), // message: r.toString(),
// onOkPressed: () { // onOkPressed: () {
// Navigator.of(context).pop(); // Navigator.of(context).pop();
// }, // },
// )); // ));
// // AppToast.showErrorToast(message: r.toString()); // // AppToast.showErrorToast(message: r.toString());
// }); // });
// } // }
// } // }
} }

@ -0,0 +1,77 @@
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/insurance/models/resp_models/patient_insurance_details_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class InsuranceRepo {
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({required String patientId});
}
class InsuranceRepoImp implements InsuranceRepo {
final ApiClient apiClient;
final LoggerService loggerService;
InsuranceRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({required String patientId}) async {
final mapDevice = {
"isDentalAllowedBackend": false,
"VersionID": 50.0,
"Channel": 3,
"LanguageID": 2,
"IPAdress": "10.20.10.20",
"generalid": "Cs2020@2016\$2958",
"Latitude": 0.0,
"Longitude": 0.0,
"DeviceTypeID": 1,
"PatientType": 1,
"PatientTypeID": 1,
"TokenID": "@dm!n",
"PatientID": "3628599",
"PatientOutSA": "0",
"SessionID": "03478TYC02N80874CTYN04883475!?"
};
try {
GenericApiModel<List<PatientInsuranceDetailsResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PAtIENTS_INSURANCE,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus}) {
try {
final list = response['List_PatientInsuranceCard'];
if (list == null || list.isEmpty) {
throw Exception("insurance list is empty");
}
final labOrders = list.map((item) => PatientInsuranceDetailsResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientInsuranceDetailsResponseModel>();
apiResponse = GenericApiModel<List<PatientInsuranceDetailsResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: labOrders,
);
} 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()));
}
throw UnimplementedError();
}
}

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart';
import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class InsuranceViewModel extends ChangeNotifier {
bool isInsuranceLoading = false;
bool isInsuranceHistoryLoading = false;
InsuranceRepo insuranceRepo;
ErrorHandlerService errorHandlerService;
List<PatientInsuranceDetailsResponseModel> patientInsuranceList = [];
InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService});
initInsuranceProvider() {
patientInsuranceList.clear();
isInsuranceLoading = true;
isInsuranceHistoryLoading = true;
getPatientInsuranceDetails();
notifyListeners();
}
setIsInsuranceHistoryLoading(bool val) {
isInsuranceHistoryLoading = val;
notifyListeners();
}
Future<void> getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755");
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
patientInsuranceList = apiResponse.data!;
isInsuranceLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -0,0 +1,96 @@
class PatientInsuranceDetailsResponseModel {
String? setupID;
int? projectID;
bool? isActive;
int? patientID;
int? companyID;
int? subCategoryID;
dynamic companyType;
String? patientCardID;
String? cardValidTo;
int? patientCreditLimit;
String? subPolicyNo;
String? companyName;
String? companyNameN;
String? subCategoryDesc;
dynamic subCategoryDescN;
bool? isElectronicClaim;
String? subCategoryValidTo;
dynamic groupID;
String? groupName;
dynamic groupNameN;
String? insurancePolicyNo;
PatientInsuranceDetailsResponseModel(
{this.setupID,
this.projectID,
this.isActive,
this.patientID,
this.companyID,
this.subCategoryID,
this.companyType,
this.patientCardID,
this.cardValidTo,
this.patientCreditLimit,
this.subPolicyNo,
this.companyName,
this.companyNameN,
this.subCategoryDesc,
this.subCategoryDescN,
this.isElectronicClaim,
this.subCategoryValidTo,
this.groupID,
this.groupName,
this.groupNameN,
this.insurancePolicyNo});
PatientInsuranceDetailsResponseModel.fromJson(Map<String, dynamic> json) {
setupID = json['SetupID'];
projectID = json['ProjectID'];
isActive = json['IsActive'];
patientID = json['PatientID'];
companyID = json['CompanyID'];
subCategoryID = json['SubCategoryID'];
companyType = json['CompanyType'];
patientCardID = json['PatientCardID'];
cardValidTo = json['CardValidTo'];
patientCreditLimit = json['PatientCreditLimit'];
subPolicyNo = json['SubPolicyNo'];
companyName = json['CompanyName'];
companyNameN = json['CompanyNameN'];
subCategoryDesc = json['SubCategoryDesc'];
subCategoryDescN = json['SubCategoryDescN'];
isElectronicClaim = json['IsElectronicClaim'];
subCategoryValidTo = json['SubCategoryValidTo'];
groupID = json['GroupID'];
groupName = json['GroupName'];
groupNameN = json['GroupNameN'];
insurancePolicyNo = json['InsurancePolicyNo'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['SetupID'] = this.setupID;
data['ProjectID'] = this.projectID;
data['IsActive'] = this.isActive;
data['PatientID'] = this.patientID;
data['CompanyID'] = this.companyID;
data['SubCategoryID'] = this.subCategoryID;
data['CompanyType'] = this.companyType;
data['PatientCardID'] = this.patientCardID;
data['CardValidTo'] = this.cardValidTo;
data['PatientCreditLimit'] = this.patientCreditLimit;
data['SubPolicyNo'] = this.subPolicyNo;
data['CompanyName'] = this.companyName;
data['CompanyNameN'] = this.companyNameN;
data['SubCategoryDesc'] = this.subCategoryDesc;
data['SubCategoryDescN'] = this.subCategoryDescN;
data['IsElectronicClaim'] = this.isElectronicClaim;
data['SubCategoryValidTo'] = this.subCategoryValidTo;
data['GroupID'] = this.groupID;
data['GroupName'] = this.groupName;
data['GroupNameN'] = this.groupNameN;
data['InsurancePolicyNo'] = this.insurancePolicyNo;
return data;
}
}

@ -12,6 +12,10 @@ class LabViewModel extends ChangeNotifier {
List<PatientLabOrdersResponseModel> patientLabOrders = []; List<PatientLabOrdersResponseModel> patientLabOrders = [];
List<String> labSuggestionsList =[];
get labSuggestions => labSuggestionsList;
LabViewModel({required this.labRepo, required this.errorHandlerService}); LabViewModel({required this.labRepo, required this.errorHandlerService});
initLabProvider() { initLabProvider() {
@ -42,4 +46,8 @@ class LabViewModel extends ChangeNotifier {
}, },
); );
} }
filterSuggestions(){
}
} }

@ -781,7 +781,29 @@ abstract class LocaleKeys {
static const resultsAvailable = 'resultsAvailable'; static const resultsAvailable = 'resultsAvailable';
static const viewReport = 'viewReport'; static const viewReport = 'viewReport';
static const prescriptionDeliveryError = 'prescriptionDeliveryError'; static const prescriptionDeliveryError = 'prescriptionDeliveryError';
static const receiveOtpToast = 'receiveOtpToast';
static const enterPhoneNumber = 'enterPhoneNumber';
static const enterEmailDesc = 'enterEmailDesc';
static const enterPhoneDesc = 'enterPhoneDesc';
static const pleaseChooseOption = 'pleaseChooseOption';
static const prepareToElevate = 'prepareToElevate';
static const iAcceptTermsConditions = 'iAcceptTermsConditions';
static const alreadyHaveAccount = 'alreadyHaveAccount';
static const loginNow = 'loginNow';
static const notice = 'notice';
static const oR = 'oR';
static const sendOTPWHATSAPP = 'sendOTPWHATSAPP';
static const sendOTPSMS = 'sendOTPSMS';
static const fullName = 'fullName';
static const married = 'married';
static const uae = 'uae';
static const malE = 'malE';
static const loginBy = 'loginBy';
static const loginByOTP = 'loginByOTP';
static const guest = 'guest';
static const switchAccount = 'switchAccount';
static const checkAvailability = 'checkAvailability'; static const checkAvailability = 'checkAvailability';
static const readInstructions = 'readInstructions'; static const readInstructions = 'readInstructions';
static const searchLabReport = 'searchLabReport';
} }

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
@ -71,6 +72,12 @@ void main() async {
errorHandlerService: getIt(), errorHandlerService: getIt(),
), ),
), ),
ChangeNotifierProvider<InsuranceViewModel>(
create: (_) => InsuranceViewModel(
insuranceRepo: getIt(),
errorHandlerService: getIt(),
),
),
ChangeNotifierProvider<AuthenticationViewModel>( ChangeNotifierProvider<AuthenticationViewModel>(
create: (_) => AuthenticationViewModel( create: (_) => AuthenticationViewModel(
authenticationRepo: getIt(), authenticationRepo: getIt(),

@ -1,17 +1,21 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/context_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:sizer/sizer.dart'; // Import sizer import 'package:provider/provider.dart';
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
@override @override
@ -31,99 +35,165 @@ class _LoginScreen extends State<LoginScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Sizer(// Wrap with Sizer AuthenticationViewModel authVm = context.read<AuthenticationViewModel>();
builder: (context, orientation, deviceType) { return Scaffold(
return Scaffold( backgroundColor: AppColors.bgScaffoldColor,
backgroundColor: AppColors.bgScaffoldColor, appBar: CustomAppBar(
appBar: CustomAppBar( onBackPressed: () {
onBackPressed: () { Navigator.of(context).pop();
}, },
onLanguageChanged: (String value) { onLanguageChanged: (String value) {
print(value); // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US'));
context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US')); },
}, ),
body: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside
},
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 200.h, height: 200.h, repeat: true, fit: BoxFit.cover),
SizedBox(height: 130.h), // Adjusted to sizer unit
LocaleKeys.welcomeToDrSulaiman.tr().toText32(isBold: true, color: AppColors.textColor),
SizedBox(height: 32.h),
TextInputWidget(
labelText: "${LocaleKeys.nationalId.tr()} / ${LocaleKeys.fileNo.tr()}",
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
keyboardType: TextInputType.number,
isEnable: true,
prefix: null,
autoFocus: true,
isAllowRadius: true,
isBorderAllowed: false,
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",
hasError: true,
),
SizedBox(height: 16.h), // Adjusted to sizer unit (approx 16px)
CustomButton(
text: LocaleKeys.login.tr(),
icon: AppAssets.login1,
iconColor: Colors.white,
onPressed: () {
showLoginModel(context: context, authVM: authVm);
// if (nationIdController.text.isNotEmpty) {
// } else {
// showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterNationalIdOrFileNo,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// }
},
),
SizedBox(height: 10.h), // Adjusted to sizer unit (approx 14px)
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: context.dynamicTextStyle(
color: Colors.black,
fontSize: 14.fSize, // Adjusted to sizer unit
height: 26 / 16, // This height is a ratio, may need re-evaluation
fontWeight: FontWeight.w500,
),
children: <TextSpan>[
TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()),
TextSpan(text: " "),
TextSpan(
text: LocaleKeys.registernow.tr(),
style: context.dynamicTextStyle(
color: AppColors.primaryRedColor,
fontSize: 14.fSize, // Adjusted to sizer unit
height: 26 / 16, // Ratio
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context) => RegisterNew()),
);
},
),
],
),
).withVerticalPadding(2), // Adjusted to sizer unit
),
],
),
),
), ),
body: GestureDetector( ),
onTap: () { );
FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside }
},
child: SingleChildScrollView( void showLoginModel({required BuildContext context, required AuthenticationViewModel authVM}) {
child: Padding( context.showBottomSheet(
padding: EdgeInsets.only(left: 6.w, right: 6.w), isScrollControlled: true,
child: Column( isDismissible: false,
mainAxisSize: MainAxisSize.min, useSafeArea: true,
crossAxisAlignment: CrossAxisAlignment.start, backgroundColor: Colors.transparent,
children: [ child: StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 45.w, height: 22.h, repeat: true, fit: BoxFit.cover), return Padding(
SizedBox(height: 19.h), // Adjusted to sizer unit padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
LocaleKeys.welcomeToDrSulaiman.tr().toText22(isBold: true, color: AppColors.textColor), child: SingleChildScrollView(
// Text( child: GenericBottomSheet(
// LocaleKeys.welcomeToDrSulaiman.tr(), countryCode: authVM.selectedCountrySignup.countryCode,
// style: context.dynamicTextStyle( initialPhoneNumber: "",
// fontSize: 22, textController: authVM.phoneNumberController,
// fontWeight: FontWeight.w600, isEnableCountryDropdown: true,
// color: AppColors.textColor, onCountryChange: authVM.onCountryChange,
// letterSpacing: -0.4, onChange: authVM.onPhoneNumberChange,
// height: 40 / 28, buttons: [
// ), Padding(
// ), padding: EdgeInsets.only(bottom: 10.h),
SizedBox(height: 4.h), // Adjusted to sizer unit (approx 32px) child: CustomButton(
TextInputWidget( text: LocaleKeys.sendOTPSMS.tr(),
labelText: "${LocaleKeys.nationalId.tr()} / ${LocaleKeys.fileNo.tr()}", onPressed: () {},
hintText: "xxxxxxxxx", backgroundColor: AppColors.primaryRedColor,
controller: TextEditingController(), borderColor: AppColors.primaryRedBorderColor,
keyboardType: TextInputType.number, textColor: AppColors.whiteColor,
isEnable: true, icon: AppAssets.message,
prefix: null, ),
autoFocus: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 1.h, horizontal: 2.w),
leadingIcon: AppAssets.student_card,
errorMessage: "Please enter a valid national ID or file number",
hasError: true,
),
SizedBox(height: 2.h), // Adjusted to sizer unit (approx 16px)
CustomButton(
text: LocaleKeys.login.tr(),
icon: AppAssets.login1,
iconColor: Colors.white,
onPressed: () {},
), ),
SizedBox(height: 1.8.h), // Adjusted to sizer unit (approx 14px) Row(
Center( crossAxisAlignment: CrossAxisAlignment.center,
child: RichText( mainAxisAlignment: MainAxisAlignment.center,
textAlign: TextAlign.center, children: [
text: TextSpan( Padding(
style: context.dynamicTextStyle( padding: EdgeInsets.symmetric(horizontal: 8.h),
color: Colors.black, child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor),
fontSize: 14.sp, // Adjusted to sizer unit
height: 26 / 16, // This height is a ratio, may need re-evaluation
fontWeight: FontWeight.w500,
),
children: <TextSpan>[
TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()),
TextSpan(text: " "),
TextSpan(
text: LocaleKeys.registernow.tr(),
style: context.dynamicTextStyle(
color: AppColors.primaryRedColor,
fontSize: 14.sp, // Adjusted to sizer unit
height: 26 / 16, // Ratio
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
],
), ),
).withVerticalPadding(2.h), // Adjusted to sizer unit ],
),
Padding(
padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () {},
backgroundColor: Colors.white,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor,
icon: AppAssets.whatsapp,
),
), ),
], ],
), ),
), ),
), );
), }));
);
});
} }
} }

@ -0,0 +1,316 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton;
import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/otp/otp.dart';
import 'package:provider/provider.dart';
class RegisterNew extends StatefulWidget {
@override
_RegisterNew createState() => _RegisterNew();
}
class _RegisterNew extends State<RegisterNew> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
AuthenticationViewModel authVm = context.read<AuthenticationViewModel>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
appBar: CustomAppBar(
onBackPressed: () {
Navigator.of(context).pop();
},
onLanguageChanged: (String value) {
// context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US'));
},
),
body: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(overscroll: false, physics: const ClampingScrollPhysics()),
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (notification) {
notification.disallowIndicator();
return true;
},
child: SingleChildScrollView(
physics: ClampingScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column(
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),
SizedBox(height: 16.h),
LocaleKeys.prepareToElevate.tr().toText32(isBold: true),
SizedBox(height: 24.h),
Directionality(
textDirection: Directionality.of(context),
child: Container(
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)),
padding: EdgeInsets.symmetric(horizontal: 16.h),
child: Column(
children: [
CustomCountryDropdown(
countryList: CountryEnum.values,
onCountryChange: authVm.onCountryChange,
isRtl: Directionality.of(context) == TextDirection.LTR,
).withVerticalPadding(8.h),
Divider(height: 1.h),
TextInputWidget(
labelText: LocaleKeys.nationalIdNumber.tr(),
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
autoFocus: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.student_card,
// onChange: (value) {
// print(value);
// }
).withVerticalPadding(8),
Divider(height: 1),
TextInputWidget(
labelText: LocaleKeys.dob.tr(),
hintText: "11 July, 1994",
controller: authVm.dobController,
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.birthday_cake,
selectionType: SelectionTypeEnum.calendar,
).withVerticalPadding(8),
],
),
),
),
SizedBox(height: 25.h),
GestureDetector(
onTap: authVm.onTermAccepted,
child: Row(
children: [
Selector<AuthenticationViewModel, bool>(
selector: (_, viewModel) => viewModel.isTermsAccepted,
shouldRebuild: (previous, next) => previous != next,
builder: (context, isTermsAccepted, child) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 24.h,
width: 24.h,
decoration: BoxDecoration(
color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h),
),
child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null,
);
},
),
SizedBox(width: 12.h),
Expanded(
child: Text(
LocaleKeys.iAcceptTermsConditions.tr(),
style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
),
),
],
),
),
SizedBox(height: 25.h),
CustomButton(
text: "Register",
icon: AppAssets.note_edit,
onPressed: () {
showRegisterModel(context: context, authVM: authVm);
},
),
SizedBox(height: 14),
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: context.dynamicTextStyle(
color: Colors.black,
fontSize: 16.fSize,
height: 26 / 16,
fontWeight: FontWeight.w500,
),
children: <TextSpan>[
TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(), style: context.dynamicTextStyle()),
TextSpan(text: " "),
TextSpan(
text: LocaleKeys.loginNow.tr(),
style: context.dynamicTextStyle(
color: AppColors.primaryRedColor,
fontSize: 16.fSize,
height: 26 / 16,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.of(context).pop();
},
),
],
),
),
),
SizedBox(height: 30),
],
),
),
),
),
));
}
void showRegisterModel({required BuildContext context, required AuthenticationViewModel authVM}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
useSafeArea: true,
backgroundColor: Colors.transparent,
builder: (bottomSheetContext) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom),
child: SingleChildScrollView(
child: GenericBottomSheet(
countryCode: authVM.selectedCountrySignup.countryCode,
initialPhoneNumber: "",
textController: authVM.phoneNumberController,
isEnableCountryDropdown: true,
onCountryChange: authVM.onCountryChange,
onChange: authVM.onPhoneNumberChange,
buttons: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => OTPVerificationPage(
phoneNumber: '12234567',
)));
// if (mobileNo.isEmpty) {
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterMobile,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// } else if (!Utils.validateMobileNumber(mobileNo)) {
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterValidMobile,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// } else {
// registerUser(1);
// }
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567')));
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: AppColors.whiteColor,
icon: AppAssets.message,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.h),
child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor),
),
],
),
Padding(
padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () {
// if (mobileNo.isEmpty) {
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterMobile,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// } else if (!Utils.validateMobileNumber(mobileNo)) {
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: TranslationBase.of(context).pleaseEnterValidMobile,
// showCancel: false,
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ),
// );
// } else {
// registerUser(4);
// }
// int? val = Utils.onOtpBtnPressed(OTPType.whatsapp, mobileNo, context);
// registerUser(val);
},
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor,
icon: AppAssets.whatsapp,
),
),
],
),
),
),
);
}
}

@ -0,0 +1,353 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/context_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_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/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:provider/provider.dart';
class RegisterNewStep2 extends StatefulWidget {
var nHICData;
var payload;
RegisterNewStep2(this.nHICData, this.payload, {Key? key}) : super(key: key);
@override
_RegisterNew createState() => _RegisterNew();
}
class _RegisterNew extends State<RegisterNewStep2> {
bool isFromDubai = true;
AuthenticationViewModel? authVM;
@override
void initState() {
super.initState();
authVM = context.read<AuthenticationViewModel>();
authVM!.loadCountriesData(context: context);
// isFromDubai = widget.payload.zipCode!.contains("971") || widget.payload.zipCode!.contains("+971");
}
@override
void dispose() {
super.dispose();
authVM!.clearDefaults();
}
@override
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
return Scaffold(
appBar: CustomAppBar(
onBackPressed: () {
Navigator.of(context).pop();
authVM!.clearDefaults();
},
onLanguageChanged: (lang) {},
hideLogoAndLang: true,
),
body: SingleChildScrollView(
reverse: false,
padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Directionality(
textDirection: Directionality.of(context),
child: Container(
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)),
padding: EdgeInsets.only(left: 16.h, right: 16.h),
child: Column(
children: [
TextInputWidget(
labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(),
hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()),
controller: null,
isEnable: true,
prefix: null,
isAllowRadius: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
isAllowLeadingIcon: true,
isReadOnly: isFromDubai ? false : true,
leadingIcon: AppAssets.user_circle)
.paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
TextInputWidget(
labelText: LocaleKeys.nationalIdNumber.tr(),
hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""),
controller: null,
isEnable: true,
prefix: null,
isAllowRadius: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
leadingIcon: AppAssets.student_card)
.paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
isFromDubai
? Selector<AuthenticationViewModel, GenderTypeEnum?>(
selector: (_, authViewModel) => authViewModel.genderType,
shouldRebuild: (previous, next) => previous != next,
builder: (context, genderType, child) {
final authVM = context.read<AuthenticationViewModel>();
return DropdownWidget(
labelText: LocaleKeys.gender.tr(),
hintText: LocaleKeys.malE.tr(),
isEnable: true,
dropdownItems: GenderTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(),
selectedValue: genderType != null ? (appState!.isArabic() ? genderType!.typeAr : genderType!.type) : "",
onChange: authVM.onGenderChange,
isBorderAllowed: false,
hasSelectionCustomIcon: true,
isAllowRadius: false,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.user_full,
).withVerticalPadding(8);
})
: TextInputWidget(
labelText: LocaleKeys.gender.tr(),
hintText: (widget.nHICData!.gender ?? ""),
controller: null,
isEnable: true,
prefix: null,
isAllowRadius: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: isFromDubai ? false : true,
leadingIcon: AppAssets.user_full,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
isFromDubai
? Selector<AuthenticationViewModel, MaritalStatusTypeEnum?>(
selector: (_, authViewModel) => authViewModel.maritalStatus,
shouldRebuild: (previous, next) => previous != next,
builder: (context, maritalStatus, child) {
final authVM = context.read<AuthenticationViewModel>(); // For onChange
return DropdownWidget(
labelText: LocaleKeys.maritalStatus.tr(),
hintText: LocaleKeys.married.tr(),
isEnable: true,
dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(),
selectedValue: maritalStatus != null ? (appState!.isArabic() ? maritalStatus.typeAr : maritalStatus.type) : "",
onChange: authVM.onMaritalStatusChange,
isBorderAllowed: false,
hasSelectionCustomIcon: true,
isAllowRadius: false,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.smart_phone,
).withVerticalPadding(8);
},
)
: TextInputWidget(
labelText: LocaleKeys.maritalStatus.tr(),
hintText: appState!.isArabic()
? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr)
: (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type),
isEnable: true,
prefix: null,
isAllowRadius: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
leadingIcon: AppAssets.smart_phone,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
isFromDubai
? Selector<AuthenticationViewModel, ({List<NationalityCountries>? countriesList, NationalityCountries? selectedCountry, bool isArabic})>(
selector: (context, authViewModel) {
final appState = getIt.get<AppState>();
return (countriesList: authViewModel.countriesList, selectedCountry: authViewModel.pickedCountryByUAEUser, isArabic: appState.isArabic());
},
shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic,
builder: (context, data, child) {
final authVM = context.read<AuthenticationViewModel>();
return DropdownWidget(
labelText: LocaleKeys.country.tr(),
hintText: LocaleKeys.uae.tr(),
isEnable: true,
dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(),
selectedValue: data.selectedCountry != null
? data.isArabic
? data.selectedCountry!.nameN ?? ""
: data.selectedCountry!.name ?? ""
: "",
onChange: authVM.onUAEUserCountrySelection,
isBorderAllowed: false,
hasSelectionCustomIcon: true,
isAllowRadius: false,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.globe,
).withVerticalPadding(8);
},
)
: TextInputWidget(
labelText: LocaleKeys.nationality.tr(),
hintText: appState.isArabic()
? (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "")
: (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""),
isEnable: true,
prefix: null,
isAllowRadius: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
leadingIcon: AppAssets.globe,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
Divider(
height: 1,
color: AppColors.greyColor,
),
TextInputWidget(
labelText: LocaleKeys.mobileNumber.tr(),
hintText: ("widget.payload.mobileNo" ?? ""),
controller: authVM!.phoneNumberController,
isEnable: true,
prefix: null,
isAllowRadius: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
leadingIcon: AppAssets.call,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
Divider(
height: 1,
color: AppColors.greyColor,
),
TextInputWidget(
labelText: LocaleKeys.dob.tr(),
hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""),
controller: authVM!.dobController,
isEnable: true,
prefix: null,
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
leadingIcon: AppAssets.birthday_cake,
selectionType: SelectionTypeEnum.calendar,
).paddingSymmetrical(0.h, 16.h),
],
),
),
),
SizedBox(height: 50.h),
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(),
icon: AppAssets.cancel,
onPressed: () {
Navigator.of(context).pop();
authVM!.clearDefaults();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
iconColor: AppColors.primaryRedColor,
),
),
SizedBox(
width: 16,
),
Expanded(
child: CustomButton(
backgroundColor: AppColors.lightGreenColor,
borderColor: AppColors.lightGreenColor,
textColor: AppColors.textGreenColor,
text: LocaleKeys.confirm.tr(),
icon: AppAssets.confirm,
iconColor: AppColors.textGreenColor,
onPressed: () {
// if (isFromDubai) {
// if (name == null) {
// AppToast.showErrorToast(message: LocaleKeys.enterFullName);
// return;
// }
// if (!name!.contains(" ")) if (selectedGenderType == null) {
// AppToast.showErrorToast(message: TranslationBase.of(context).enterFullName);
// return;
// }
// if (selectedMaritalStatusType == null) {
// AppToast.showErrorToast(message: TranslationBase.of(context).chooseMaritalStatus);
// return;
// }
// if (selectedCountry == null) {
// AppToast.showErrorToast(message: TranslationBase.of(context).chooseCountry);
// return;
// }
// }
showModel(context: context);
},
),
)
],
),
],
),
),
);
}
void showModel({required BuildContext context}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
backgroundColor: Colors.transparent,
builder: (bottomSheetContext) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom),
child: SingleChildScrollView(
child: GenericBottomSheet(
textController: TextEditingController(),
isForEmail: true,
buttons: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: CustomButton(
text: LocaleKeys.submit,
onPressed: () {
// if (emailAddress.text.isEmpty) {
// Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress);
// return;
// } else {
// Navigator.of(context).pop();
// registerNow();
// }
},
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
textColor: AppColors.whiteColor),
),
],
),
),
),
);
}
}

@ -0,0 +1,289 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
class SavedLogin extends StatefulWidget {
// final SelectDeviceIMEIRES savedLoginData;
// const SavedLogin(this.savedLoginData, {Key? key}) : super(key: key);
const SavedLogin();
@override
_SavedLogin createState() => _SavedLogin();
}
class _SavedLogin extends State<SavedLogin> {
LoginTypeEnum loginType = LoginTypeEnum.sms;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
onBackPressed: () {},
onLanguageChanged: (lang) {},
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Spacer(flex: 2),
// Welcome back text
LocaleKeys.welcomeBack.tr().toText16(color: AppColors.inputLabelTextColor),
SizedBox(height: 16.h),
("widget.savedLoginData.name!.toLowerCase().capitalizeFirstofEach").toText26(isBold: true, height: 26 / 36, color: AppColors.textColor),
SizedBox(height: 24.h),
Container(
padding: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: AppColors.whiteColor,
border: Border.all(color: AppColors.whiteColor),
borderRadius: BorderRadius.circular(24.h),
boxShadow: [
BoxShadow(color: Color(0x0D000000), blurRadius: 16, offset: Offset(0, 0), spreadRadius: 5),
],
),
child: Column(
children: [
// Last login info
('LocaleKeys.lastloginBy.tr()' + ' {getType(widget.savedLoginData.logInType!, context)}').toText14(isBold: true, color: AppColors.greyTextColor),
('widget.savedLoginData.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(widget.savedLoginData.createdOn!), "d MMMM, y at HH:mm") : --')
.toText16(isBold: true, color: AppColors.textColor),
Container(margin: EdgeInsets.all(16.h), child: Utils.buildSvgWithAssets(icon: getTypeIcons(loginType.toInt), iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)),
// Face ID login button
Container(
height: 45,
child: CustomButton(
text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}",
onPressed: () {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
// loginWithFingerPrintFace(loginType.toInt, widget.savedLoginData.iMEI!);
} else {
int? val = loginType.toInt;
//checkUserAuthentication(val);
}
},
backgroundColor: Color(0xffED1C2B),
borderColor: Color(0xffFEE9EA),
textColor: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
icon: AppAssets.apple_finder,
),
),
],
),
),
const SizedBox(height: 24),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
LocaleKeys.oR.tr(),
style: context.dynamicTextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
),
const SizedBox(height: 24),
// OTP login button
loginType != null && loginType.toInt != 1
? Column(
children: [
loginType.toInt != 1
? CustomButton(
text: LocaleKeys.loginByOTP.tr(),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
useSafeArea: true,
backgroundColor: Colors.transparent,
enableDrag: false,
// Prevent dragging to avoid focus conflicts
builder: (bottomSheetContext) => StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom),
child: SingleChildScrollView(
child: GenericBottomSheet(
countryCode: "966",
initialPhoneNumber: "",
textController: TextEditingController(),
isFromSavedLogin: true,
isEnableCountryDropdown: true,
onCountryChange: (value) {},
onChange: (String? value) {},
buttons: [
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () {
Navigator.of(context).pop();
loginType = LoginTypeEnum.sms;
int? val = loginType.toInt;
// checkUserAuthentication(val);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: AppColors.whiteColor,
icon: AppAssets.message,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(padding: EdgeInsets.symmetric(horizontal: 8.h), child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)),
],
),
Padding(
padding: const EdgeInsets.only(bottom: 10, top: 10),
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () {
Navigator.of(context).pop();
loginType = LoginTypeEnum.whatsapp;
int? val = loginType.toInt;
// checkUserAuthentication(val);
},
backgroundColor: AppColors.transparent,
borderColor: AppColors.textColor,
textColor: AppColors.textColor,
icon: AppAssets.whatsapp,
),
),
],
),
),
);
}),
);
},
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor,
borderWidth: 2,
padding: EdgeInsets.fromLTRB(0, 14, 0, 14),
icon: AppAssets.password_validation,
)
: Container(),
SizedBox(
height: 20,
),
],
)
: CustomButton(
text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}",
onPressed: () {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
// loginWithFingerPrintFace(loginType.toInt, "iMEI");
} else {
loginType = LoginTypeEnum.whatsapp;
int? val = loginType.toInt;
// checkUserAuthentication(val);
}
},
backgroundColor: AppColors.whiteColor,
borderColor: Color(0xFF2E3039),
textColor: Color(0xFF2E3039),
borderWidth: 2,
padding: EdgeInsets.fromLTRB(0, 14, 0, 14),
icon: AppAssets.whatsapp,
),
const Spacer(flex: 2),
// OR divider
const SizedBox(height: 24),
// Guest and Switch account
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
height: 56,
child: CustomButton(
text: LocaleKeys.guest.tr(),
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
// icon: "assets/images/svg/apple-finder.svg",
),
),
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.05,
),
Expanded(
child: Container(
height: 56,
child: CustomButton(
text: LocaleKeys.switchAccount.tr(),
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 15,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
// icon: "assets/images/svg/apple-finder.svg",
),
),
),
],
),
const SizedBox(height: 20),
],
),
),
),
);
}
String getTypeIcons(int type) {
final types = {
1: AppAssets.sms,
2: AppAssets.fingerprint,
3: AppAssets.fingerprint,
4: AppAssets.whatsapp,
};
if (types.containsKey(type)) {
return types[type]!;
} else {
throw Exception('Invalid login type: $type');
}
}
}

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
@ -50,7 +51,8 @@ class _LandingPageState extends State<LandingPage> {
CustomButton( CustomButton(
text: LocaleKeys.loginOrRegister.tr(context: context), text: LocaleKeys.loginOrRegister.tr(context: context),
onPressed: () async { onPressed: () async {
await authenticationViewModel.selectDeviceImei(); // await authenticationViewModel.selectDeviceImei();
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => LoginScreen()));
}, },
backgroundColor: Color(0xffFEE9EA), backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA),
@ -179,10 +181,7 @@ class _LandingPageState extends State<LandingPage> {
) )
: Container( : Container(
height: 127.h, height: 127.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24),
color: AppColors.whiteColor,
borderRadius: 24,
),
child: Padding( child: Padding(
padding: EdgeInsets.all(16.h), padding: EdgeInsets.all(16.h),
child: Column( child: Column(

@ -20,22 +20,19 @@ class _LandingNavigationState extends State<LandingNavigation> {
body: PageView( body: PageView(
controller: _pageController, controller: _pageController,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
children: const [ children: [
LandingPage(), const LandingPage(),
MedicalFilePage(), MedicalFilePage(),
LandingPage(), const LandingPage(),
LandingPage(), const LandingPage(),
LandingPage(), const LandingPage(),
], ],
), ),
bottomNavigationBar: BottomNavigation( bottomNavigationBar: BottomNavigation(
currentIndex: _currentIndex, currentIndex: _currentIndex,
onTap: (index) { onTap: (index) {
setState(() => _currentIndex = index); setState(() => _currentIndex = index);
_pageController.animateToPage( _pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
index,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut);
}, },
), ),
); );

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.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/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
@ -78,6 +79,11 @@ class SmallServiceCard extends StatelessWidget {
); );
break; break;
case "insurance_update": case "insurance_update":
Navigator.of(context).push(
FadePage(
page: InsuranceHomePage(),
),
);
break; break;
default: default:
// Handle unknown service // Handle unknown service

@ -0,0 +1,90 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart';
import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.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/shimmer/movies_shimmer_widget.dart';
import 'package:provider/provider.dart';
import 'widgets/insurance_history.dart';
class InsuranceHomePage extends StatefulWidget {
const InsuranceHomePage({super.key});
@override
State<InsuranceHomePage> createState() => _InsuranceHomePageState();
}
class _InsuranceHomePageState extends State<InsuranceHomePage> {
late InsuranceViewModel insuranceViewModel;
@override
void initState() {
scheduleMicrotask(() {
insuranceViewModel.initInsuranceProvider();
});
super.initState();
}
@override
Widget build(BuildContext context) {
insuranceViewModel = Provider.of<InsuranceViewModel>(context);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
appBar: AppBar(
title: LocaleKeys.insurance.tr(context: context).toText18(),
backgroundColor: AppColors.bgScaffoldColor,
),
body: SingleChildScrollView(
child: Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true),
CustomButton(
icon: AppAssets.insurance_history_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 21.h,
text: LocaleKeys.history.tr(context: context),
onPressed: () {
insuranceVM.setIsInsuranceHistoryLoading(true);
showCommonBottomSheet(context,
child: InsuranceHistory(), callBackFunc: () {}, title: "", height: ResponsiveExtension.screenHeight * 0.5, isCloseButtonVisible: false, isFullScreen: false);
},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w600,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
).paddingSymmetrical(24.h, 24.h),
insuranceVM.isInsuranceLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0)
: PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))),
],
);
}),
),
);
}
}

@ -0,0 +1,39 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:provider/provider.dart';
class InsuranceHistory extends StatelessWidget {
InsuranceHistory({super.key});
late InsuranceViewModel insuranceViewModel;
@override
Widget build(BuildContext context) {
insuranceViewModel = Provider.of<InsuranceViewModel>(context);
return Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.history.tr(context: context).toText24(isBold: true),
Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon).onPress(() {
Navigator.of(context).pop();
}),
],
).paddingSymmetrical(24.h, 24.h),
insuranceVM.isInsuranceHistoryLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h) : Container()
],
);
});
}
}

@ -0,0 +1,133 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.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/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_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/buttons/custom_button.dart';
import 'package:provider/provider.dart';
class PatientInsuranceCard extends StatelessWidget {
PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired});
PatientInsuranceDetailsResponseModel insuranceCardDetailsModel;
bool isInsuranceExpired = false;
late InsuranceViewModel insuranceViewModel;
@override
Widget build(BuildContext context) {
insuranceViewModel = Provider.of<InsuranceViewModel>(context);
return Container(
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Haroon Amjad".toText18(isBold: true),
"Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor),
],
),
CustomButton(
icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon,
iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
iconSize: 13.h,
text: isInsuranceExpired ? "Insurance Expired" : "Insurance Active",
onPressed: () {},
backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15),
borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01),
textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
SizedBox(height: 12.h),
insuranceCardDetailsModel.groupName!.toText12(isBold: true),
insuranceCardDetailsModel.companyName!.toText12(isBold: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.h,
runSpacing: 6.h,
children: [
Row(
children: [
CustomButton(
icon: AppAssets.doctor_calendar_icon,
iconColor: AppColors.blackColor,
iconSize: 13.h,
text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
children: [
CustomButton(
text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
],
),
SizedBox(height: 10.h),
isInsuranceExpired
? CustomButton(
icon: AppAssets.update_insurance_card_icon,
iconColor: AppColors.successColor,
iconSize: 15.h,
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
textColor: AppColors.bgGreenColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
)
: Container(),
],
),
),
).paddingSymmetrical(24.h, 0.h);
}
}

@ -5,14 +5,19 @@ import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.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/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_config.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.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/shimmer/movies_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -25,7 +30,7 @@ class LabOrdersPage extends StatefulWidget {
class _LabOrdersPageState extends State<LabOrdersPage> { class _LabOrdersPageState extends State<LabOrdersPage> {
late LabViewModel labProvider; late LabViewModel labProvider;
List<List<TestDetails>?> labSuggestions = [];
int? expandedIndex; int? expandedIndex;
@override @override
@ -57,7 +62,17 @@ class _LabOrdersPageState extends State<LabOrdersPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
LocaleKeys.labResults.tr(context: context).toText24(isBold: true), LocaleKeys.labResults.tr(context: context).toText24(isBold: true),
Utils.buildSvgWithAssets(icon: AppAssets.search_icon), Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(() {
if(model.isLabOrdersLoading){
return;
} else {
labSuggestions = getLabSuggestions(model);
showCommonBottomSheet(context, child: SearchLabResultsContent(),
callBackFunc: () {},
title: LocaleKeys.searchLabReport.tr(),
height: ResponsiveExtension.screenHeight,
isCloseButtonVisible: true);
} }),
], ],
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -262,4 +277,13 @@ class _LabOrdersPageState extends State<LabOrdersPage> {
return ""; return "";
} }
} }
getLabSuggestions(LabViewModel model) {
if(model.patientLabOrders.isEmpty){
return [];
}
return model.patientLabOrders.map((m) => m.testDetails).toList();
}
} }

@ -0,0 +1,116 @@
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_export.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.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/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:sizer/sizer.dart';
class SearchLabResultsContent extends StatelessWidget {
const SearchLabResultsContent({super.key});
final List<String> _chipLabels = const [
"Blood Test",
"X-Ray",
"MRI Scan",
"CT Scan",
"Ultrasound",
"Urine Test",
"Allergy Test",
"Cholesterol Test",
"Diabetes Test",
"Thyroid Test",
];
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextInputWidget(
labelText:"Search lab results",
hintText: "Type test name",
controller: TextEditingController(),
isEnable: true,
prefix: null,
autoFocus: true,
isBorderAllowed: false,
padding: EdgeInsets.symmetric(vertical:ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h),
),
SizedBox(height: ResponsiveExtension(20).h),
"Suggestions".toText16(isBold: true),
const SizedBox(height: 12),
],
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Wrap(
alignment: WrapAlignment.start,
spacing: 10,
runSpacing: 10,
children: _chipLabels
.map((label) => SuggestionChip(
label: label,
onTap: () {},
))
.toList(),
),
),
),
Container(
color: Colors.white,
padding: EdgeInsets.all(ResponsiveExtension(20).h),
child: CustomButton(
text: LocaleKeys.search.tr(),
icon: AppAssets.search_icon,
iconColor: Colors.white,
onPressed: () => Navigator.pop(context),
),
),
],
);
}
}
class SuggestionChip extends StatelessWidget {
final String label;
final bool isSelected;
final VoidCallback? onTap;
const SuggestionChip({
super.key,
required this.label,
this.isSelected = false,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap, // optional tap callback
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor,
borderRadius: BorderRadius.circular(8),
),
child: label.toText12(
color: isSelected ? Colors.white : Colors.black87,
fontWeight: FontWeight.w500,
),
),
);
}
}

@ -1,21 +1,29 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:provider/provider.dart';
class MedicalFilePage extends StatelessWidget { class MedicalFilePage extends StatelessWidget {
const MedicalFilePage({super.key}); MedicalFilePage({super.key});
late InsuranceViewModel insuranceViewModel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
insuranceViewModel = Provider.of<InsuranceViewModel>(context);
return Scaffold( return Scaffold(
backgroundColor: AppColors.bgScaffoldColor, backgroundColor: AppColors.bgScaffoldColor,
appBar: AppBar( appBar: AppBar(
@ -42,7 +50,6 @@ class MedicalFilePage extends StatelessWidget {
isAllowLeadingIcon: true, isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
leadingIcon: AppAssets.student_card, leadingIcon: AppAssets.student_card,
hasError: true,
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Container( Container(
@ -175,101 +182,13 @@ class MedicalFilePage extends StatelessWidget {
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
//Insurance Tab Data //Insurance Tab Data
Container( Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
// height: 150.h, return insuranceVM.isInsuranceLoading
width: double.infinity, ? const MoviesShimmerWidget()
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( : PatientInsuranceCard(
color: AppColors.whiteColor, insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
borderRadius: 24, isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo)));
), }),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Haroon Amjad".toText18(isBold: true),
"Policy: 223123345".toText12(isBold: true, color: AppColors.lightGrayColor),
],
),
CustomButton(
icon: AppAssets.cross_circle,
iconColor: AppColors.primaryRedColor,
iconSize: 13.h,
text: "Insurance Expired",
onPressed: () {},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
textColor: AppColors.primaryRedColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
SizedBox(height: 12.h),
"NCCI".toText12(isBold: true),
"NC_Dr Sulaiman Al Habib Medical Group".toText12(isBold: true),
SizedBox(height: 8.h),
Row(
children: [
CustomButton(
icon: AppAssets.cross_circle,
iconColor: AppColors.primaryRedColor,
iconSize: 13.h,
text: "Expiry: 18 Mar, 2025",
onPressed: () {},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
textColor: AppColors.primaryRedColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
SizedBox(width: 5.h),
CustomButton(
text: "Patient Card ID: 3628599",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.normal,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
SizedBox(height: 10.h),
CustomButton(
icon: AppAssets.cross_circle,
iconColor: AppColors.primaryRedColor,
iconSize: 13.h,
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
textColor: AppColors.bgGreenColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
),
),
),
SizedBox(height: 10.h), SizedBox(height: 10.h),
GridView( GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13),

@ -9,7 +9,6 @@ class AppColors {
static const buttonColor = Color(0xFF6A46F5); static const buttonColor = Color(0xFF6A46F5);
static const splashBgColor = Color(0xFF3C355D); static const splashBgColor = Color(0xFF3C355D);
static const blackColor = Color(0xFF000000);
static const lightGray = Color(0xFFF4F5F7); static const lightGray = Color(0xFFF4F5F7);
static const lightPurple = Color(0xFFB7A3E6); static const lightPurple = Color(0xFFB7A3E6);
static const scaffoldBgColor = Color(0xFFF8F8F8); static const scaffoldBgColor = Color(0xFFF8F8F8);
@ -33,15 +32,23 @@ class AppColors {
static const Color borderOnlyColor = Color(0xFF2E3039); static const Color borderOnlyColor = Color(0xFF2E3039);
static const Color dividerColor = Color(0xFFD2D2D2); static const Color dividerColor = Color(0xFFD2D2D2);
static const Color warningColorYellow = Color(0xFFF4A308); static const Color warningColorYellow = Color(0xFFF4A308);
static const Color blackBgColor = Color(0xFF2E3039);
static const blackColor = textColor;
static const inputLabelTextColor = Color(0xff898A8D);
static const greyTextColor = Color(0xFF8F9AA3);
static const lightGreenColor = Color(0xFF0ccedde);
static const textGreenColor = Color(0xFF18C273);
static const Color ratingColorYellow = Color(0xFFFFAF15); static const Color ratingColorYellow = Color(0xFFFFAF15);
//Chips //Chips
static const Color successColor = Color(0xff18C273); static const Color successColor = Color(0xff18C273);
static const Color errorColor = Color(0xFFED1C2B); static const Color errorColor = Color(0xFFED1C2B);
static const Color alertColor = Color(0xFFD48D05); static const Color alertColor = Color(0xFFD48D05);
static const Color infoColor = Color(0xFF0B85F7); static const Color infoColor = Color(0xFF0B85F7);
static const Color warningColor = Color(0xFFFFCC00); static const Color warningColor = Color(0xFFFFCC00);
static const Color greyColor = Color(0xFFEFEFF0); static const Color greyColor = Color(0xFFEFEFF0);
static const Color successLightColor = Color(0xFF18C273); static const Color successLightColor = Color(0xFF18C273);
static const Color errorLightColor = Color(0xFFED1C2B); static const Color errorLightColor = Color(0xFFED1C2B);

@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/widgets/language_switcher.dart'; import 'package:hmg_patient_app_new/widgets/language_switcher.dart';
import '../../generated/locale_keys.g.dart'; import '../../generated/locale_keys.g.dart';
@ -9,11 +11,13 @@ import '../../generated/locale_keys.g.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final VoidCallback onBackPressed; final VoidCallback onBackPressed;
final ValueChanged<String> onLanguageChanged; final ValueChanged<String> onLanguageChanged;
bool hideLogoAndLang;
const CustomAppBar({ CustomAppBar({
Key? key, Key? key,
required this.onBackPressed, required this.onBackPressed,
required this.onLanguageChanged, required this.onLanguageChanged,
this.hideLogoAndLang = false,
}) : super(key: key); }) : super(key: key);
@override @override
@ -24,45 +28,45 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
return AppBar( return AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
leading: null, leading: null,
title: Row( automaticallyImplyLeading: false,
mainAxisAlignment: MainAxisAlignment.spaceBetween, title: Padding(
children: [ padding: EdgeInsets.symmetric(horizontal: 10.h),
// Arrow Back with click handler child: Row(
Expanded( mainAxisAlignment: MainAxisAlignment.start,
child: Align( children: [
alignment: Alignment.centerLeft, Expanded(
child: GestureDetector( child: Align(
onTap: onBackPressed, alignment: Alignment.centerLeft,
child: Utils.buildSvgWithAssets( child: GestureDetector(
icon: AppAssets.arrow_back, onTap: onBackPressed,
width: 32, child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h),
height: 32,
), ),
), ),
), ),
),
// Logo // Logo
Utils.buildSvgWithAssets( if (!hideLogoAndLang)
icon: AppAssets.habiblogo, Utils.buildSvgWithAssets(
), icon: AppAssets.habiblogo,
),
// Language Selector if (!hideLogoAndLang)
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: LanguageSelector( child: LanguageSelector(
currentLanguage: context.locale.languageCode, currentLanguage: context.locale.languageCode,
showOnlyIcon: false, showOnlyIcon: false,
onLanguageChanged: onLanguageChanged, onLanguageChanged: onLanguageChanged,
languages: [ languages: [
{'code': 'ar', 'name': LocaleKeys.arabic.tr()}, {'code': 'ar', 'name': LocaleKeys.arabic.tr()},
{'code': 'en', 'name': LocaleKeys.english.tr()} {'code': 'en', 'name': LocaleKeys.english.tr()}
], ],
),
),
), ),
), ],
), ),
],
), ),
centerTitle: true, centerTitle: true,
); );

@ -0,0 +1,111 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
class ExceptionBottomSheet extends StatelessWidget {
String message;
bool showOKButton;
bool showCancel;
Function() onOkPressed;
Function()? onCancelPressed;
ExceptionBottomSheet({Key? key, required this.message, this.showOKButton = true, this.showCancel = false, required this.onOkPressed, this.onCancelPressed}) : super(key: key);
@override
Widget build(BuildContext context) {
return SafeArea(
bottom: Platform.isIOS ? false : true, // Adjust for iOS to avoid bottom padding
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside
},
child: Builder(builder: (context) {
return Directionality(
textDirection: Directionality.of(context),
child: Container(
padding: EdgeInsets.all(24.h),
decoration: BoxDecoration(
color: Color(0xFFF8F8FA),
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.notice.tr().toText28(),
InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle),
)
],
),
SizedBox(height: 10.h),
(message ?? "").toText16(isBold: false, color: AppColors.textColor),
SizedBox(height: 10.h),
SizedBox(height: 24.h),
if (showOKButton && showCancel)
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(),
onPressed: onCancelPressed != null
? onCancelPressed!
: () {
Navigator.of(context).pop();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
icon: AppAssets.cancel,
iconColor: AppColors.primaryRedColor,
),
),
SizedBox(width: 5.h),
Expanded(
child: CustomButton(
text: showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(),
onPressed: onOkPressed,
backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor,
textColor: Colors.white,
icon: AppAssets.confirm,
),
),
],
),
if (showOKButton && !showCancel)
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: CustomButton(
text: LocaleKeys.ok.tr(),
onPressed: onOkPressed,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: Colors.white,
icon: AppAssets.confirm,
),
),
],
),
),
);
}),
),
);
}
}

@ -0,0 +1,148 @@
import 'dart:io';
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/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
class GenericBottomSheet extends StatefulWidget {
String? countryCode;
String? initialPhoneNumber;
final List<Widget> buttons;
TextEditingController? textController;
final bool isForEmail;
Function(CountryEnum)? onCountryChange;
final bool isEnableCountryDropdown;
final bool isFromSavedLogin;
Function(String?)? onChange;
// FocusNode myFocusNode;
GenericBottomSheet({
this.countryCode = "",
this.initialPhoneNumber = "",
required this.buttons,
this.textController,
this.isForEmail = false,
this.onCountryChange,
this.isEnableCountryDropdown = false,
this.isFromSavedLogin = false,
this.onChange,
// required this.myFocusNode
});
@override
_GenericBottomSheetState createState() => _GenericBottomSheetState();
}
class _GenericBottomSheetState extends State<GenericBottomSheet> {
@override
void initState() {
super.initState();
if (!widget.isForEmail && widget.textController != null) {
widget.textController!.text = widget.initialPhoneNumber!;
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
bottom: Platform.isIOS ? false : true,
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Directionality(
textDirection: Directionality.of(context),
child: Container(
padding: EdgeInsets.all(24.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bgScaffoldColor, borderRadius: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Title
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: widget.isFromSavedLogin
? LocaleKeys.receiveOtpToast.tr().toText24()
: widget.isForEmail
? LocaleKeys.enterEmail.tr().toText24()
: LocaleKeys.enterPhoneNumber.tr().toText24()),
InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Padding(
padding: EdgeInsets.only(top: 10.h),
child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle),
),
),
],
),
SizedBox(height: 8.h),
// Subtitle
widget.isFromSavedLogin
? LocaleKeys.pleaseChooseOption.tr().toText16()
: widget.isForEmail
? LocaleKeys.enterEmailDesc.tr().toText16()
: LocaleKeys.enterPhoneDesc.tr().toText16(),
if (widget.isFromSavedLogin)
...[]
else ...[
widget.textController != null
? TextInputWidget(
labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber,
hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx",
controller: widget.textController!,
padding: EdgeInsets.all(8.h),
keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number,
onChange: (value) {
if (widget.onChange != null) {
widget.onChange!(value);
}
},
onCountryChange: (value) {
if (widget.onCountryChange != null) {
widget.onCountryChange!(value);
}
},
isEnable: true,
isReadOnly: widget.isFromSavedLogin,
prefix: widget.isForEmail ? null : widget.countryCode,
isBorderAllowed: false,
isAllowLeadingIcon: true,
isCountryDropDown: widget.isEnableCountryDropdown,
leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone,
)
: SizedBox(),
],
SizedBox(height: 24.h),
...widget.buttons,
],
),
),
),
),
);
}
}

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
void showCommonBottomSheet(BuildContext context,
{required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true, bool isFullScreen = true}) {
showModalBottomSheet<String>(
sheetAnimationStyle: AnimationStyle(
duration: Duration(milliseconds: 500), // Custom animation duration
reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration
),
context: context,
isScrollControlled: true,
showDragHandle: false,
backgroundColor: AppColors.scaffoldBgColor,
builder: (BuildContext context) {
return Container(
height: height,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 24.h),
child: ButtonSheetContent(
title: title!,
isCloseButtonVisible: isCloseButtonVisible,
isFullScreen: isFullScreen,
child: child,
),
);
}).then((value) {
callBackFunc();
});
}
class ButtonSheetContent extends StatelessWidget {
final Widget child;
final String title;
final bool isCloseButtonVisible;
final bool isFullScreen;
const ButtonSheetContent({super.key, required this.child, required this.isCloseButtonVisible, required this.title, required this.isFullScreen});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// SizedBox(
// height: 20.h,
// ),
// Center(
// child: Container(
// margin: const EdgeInsets.only(top: 18, bottom: 12),
// height: 4,
// width: 40.h,
// decoration: BoxDecoration(
// color: Colors.grey[400],
// borderRadius: BorderRadius.circular(2),
// ),
// ),
// ),
// Close button
isCloseButtonVisible && isFullScreen
? Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32, height: 32).onPress(() {
Navigator.of(context).pop();
}),
)
: SizedBox(),
isFullScreen
? Column(
children: [
SizedBox(height: 20.h),
Padding(padding: EdgeInsets.symmetric(horizontal: 16.h), child: title.toText24(isBold: true)),
SizedBox(height: 16.h),
],
)
: SizedBox(),
Expanded(child: child)
],
);
}
}

@ -0,0 +1,210 @@
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/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
class CustomCountryDropdown extends StatefulWidget {
final List<CountryEnum> countryList;
final Function(CountryEnum)? onCountryChange;
final Function(String)? onPhoneNumberChanged;
final bool isRtl;
final bool isFromBottomSheet;
final bool isEnableTextField;
Widget? textField;
CustomCountryDropdown({
Key? key,
required this.countryList,
this.onCountryChange,
this.onPhoneNumberChanged,
required this.isRtl,
this.isFromBottomSheet = false,
this.isEnableTextField = false,
this.textField,
}) : super(key: key);
@override
_CustomCountryDropdownState createState() => _CustomCountryDropdownState();
}
class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
CountryEnum? selectedCountry;
late OverlayEntry _overlayEntry;
bool _isDropdownOpen = false;
FocusNode textFocusNode = new FocusNode();
@override
void initState() {
super.initState();
selectedCountry = CountryEnum.saudiArabia;
if (widget.isEnableTextField && widget.isFromBottomSheet) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && textFocusNode.canRequestFocus) {
FocusScope.of(context).requestFocus(textFocusNode);
}
});
}
}
@override
void dispose() {
textFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
height: 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h),
child: Row(
children: [
GestureDetector(
onTap: () {
if (_isDropdownOpen) {
_closeDropdown();
} else {
_openDropdown();
}
},
child: Utils.buildSvgWithAssets(icon: selectedCountry != null ? selectedCountry!.iconPath : AppAssets.ksa, width: 40.h, height: 40.h)),
SizedBox(width: 8.h),
Utils.buildSvgWithAssets(icon: _isDropdownOpen ? AppAssets.dropdow_icon : AppAssets.dropdow_icon),
SizedBox(width: 4.h),
if (widget.isFromBottomSheet)
GestureDetector(
onTap: () {
if (widget.isEnableTextField && textFocusNode.canRequestFocus) {
FocusScope.of(context).requestFocus(textFocusNode);
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Text(
LocaleKeys.phoneNumber.tr(),
style: TextStyle(fontSize: 12.fSize, height: 21 / 12, fontWeight: FontWeight.w500, letterSpacing: -1),
),
],
),
Row(
children: [
Text(
selectedCountry!.countryCode,
style: TextStyle(fontSize: 12.fSize, height: 21 / 18, fontWeight: FontWeight.w600, letterSpacing: -0.2),
),
SizedBox(width: 4.h),
if (widget.isEnableTextField)
SizedBox(
height: 20,
width: 200,
child: TextField(
focusNode: textFocusNode,
decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none),
keyboardType: TextInputType.phone,
onChanged: widget.onPhoneNumberChanged),
),
],
)
],
),
),
if (!widget.isFromBottomSheet)
Text(
selectedCountry != null ? selectedCountry!.displayName : "Select Country",
style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2),
),
],
),
);
}
void _openDropdown() {
if (textFocusNode.hasFocus) {
textFocusNode.unfocus();
}
RenderBox renderBox = context.findRenderObject() as RenderBox;
Offset offset = renderBox.localToGlobal(Offset.zero);
_overlayEntry = OverlayEntry(
builder: (context) => Stack(
children: [
// Dismiss dropdown when tapping outside
Positioned.fill(
child: GestureDetector(
onTap: _closeDropdown,
behavior: HitTestBehavior.translucent,
child: Container(),
),
),
Positioned(
top: offset.dy + renderBox.size.height,
left: widget.isRtl ? offset.dx + 6.h : offset.dx - 6.h,
width: renderBox.size.width,
child: Material(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12),
child: Column(
children: widget.countryList
.map(
(country) => GestureDetector(
onTap: () {
setState(() {
selectedCountry = country;
});
widget.onCountryChange?.call(country);
_closeDropdown();
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h),
if (!widget.isFromBottomSheet) SizedBox(width: 12.h),
if (!widget.isFromBottomSheet) Text(country.displayName, style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)),
],
),
),
),
)
.toList(),
),
),
),
),
],
),
);
Overlay.of(context)?.insert(_overlayEntry);
setState(() {
_isDropdownOpen = true;
});
}
void _closeDropdown() {
_overlayEntry.remove();
setState(() {
_isDropdownOpen = false;
});
if (widget.isEnableTextField && widget.isFromBottomSheet) {
Future.delayed(Duration(milliseconds: 100), () {
if (mounted && textFocusNode.canRequestFocus) {
FocusScope.of(context).requestFocus(textFocusNode);
}
});
}
}
}

@ -0,0 +1,154 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart' show Icons, PopupMenuItem, showMenu, Colors;
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/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class DropdownWidget extends StatelessWidget {
final String labelText;
final String hintText;
final List<String> dropdownItems;
final String? selectedValue;
final Function(String?)? onChange;
final bool isEnable;
final bool isBorderAllowed;
final bool isAllowRadius;
final EdgeInsetsGeometry? padding;
final bool hasSelectionCustomIcon;
final String? selectionCustomIcon;
final String? leadingIcon;
const DropdownWidget({
Key? key,
required this.labelText,
required this.hintText,
required this.dropdownItems,
this.selectedValue,
this.onChange,
this.isEnable = true,
this.isBorderAllowed = true,
this.isAllowRadius = true,
this.padding,
this.hasSelectionCustomIcon = false,
this.selectionCustomIcon,
this.leadingIcon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget content = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [_buildLabelText(), _buildDropdown(context)],
);
return Container(
padding: padding,
alignment: Alignment.center, // This might need adjustment based on layout
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
borderRadius: isAllowRadius ? 15.h : null,
side: isBorderAllowed ? BorderSide(color: const Color(0xffefefef), width: 1) : null,
),
child: Row(
// Wrap with a Row
crossAxisAlignment: CrossAxisAlignment.center, // Align items vertically in the center
children: [
if (leadingIcon != null) ...[
_buildLeadingIcon(),
SizedBox(width: 3.h),
],
Expanded(child: content),
],
),
);
}
Widget _buildLeadingIcon() {
return Container(
height: 40.h,
width: 40.h,
margin: EdgeInsets.only(right: 10.h),
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.greyColor),
child: Utils.buildSvgWithAssets(icon: leadingIcon!));
}
Widget _buildLabelText() {
return Text(
labelText,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
color: Color(0xff898A8D),
letterSpacing: -0.2,
height: 18 / 12,
),
);
}
Widget _buildDropdown(BuildContext context) {
return GestureDetector(
onTap: isEnable
? () async {
final renderBox = context.findRenderObject() as RenderBox;
final offset = renderBox.localToGlobal(Offset.zero);
final selected = await showMenu<String>(
context: context,
position: RelativeRect.fromLTRB(
offset.dx,
offset.dy + renderBox.size.height,
offset.dx + renderBox.size.width,
0,
),
items: dropdownItems
.map(
(e) => PopupMenuItem<String>(
value: e,
child: Text(
e,
style: TextStyle(
fontSize: 14.fSize,
height: 21 / 14,
fontWeight: FontWeight.w500,
letterSpacing: -0.2,
),
),
),
)
.toList(),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
);
if (selected != null && onChange != null) {
onChange!(selected);
}
}
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
(selectedValue == null || selectedValue!.isEmpty) ? hintText : selectedValue!,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 14.fSize,
height: 21 / 14,
fontWeight: FontWeight.w500,
color: (selectedValue != null && selectedValue!.isNotEmpty) ? const Color(0xff2E3039) : const Color(0xffB0B0B0),
letterSpacing: -0.2,
),
),
),
if (hasSelectionCustomIcon && selectionCustomIcon != null) Utils.buildSvgWithAssets(icon: selectionCustomIcon!) else const Icon(Icons.keyboard_arrow_down_outlined),
],
),
);
}
}

@ -1,132 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart' show Icons, PopupMenuItem, showMenu, Colors;
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
class DropdownWidget extends StatelessWidget {
final String labelText;
final String hintText;
final List<String> dropdownItems;
final String? selectedValue;
final Function(String?)? onChange;
final bool isEnable;
final bool isBorderAllowed;
final bool isAllowRadius;
final EdgeInsetsGeometry? padding;
final bool hasSelectionCustomIcon;
final String? selectionCustomIcon;
const DropdownWidget({
Key? key,
required this.labelText,
required this.hintText,
required this.dropdownItems,
this.selectedValue,
this.onChange,
this.isEnable = true,
this.isBorderAllowed = true,
this.isAllowRadius = true,
this.padding,
this.hasSelectionCustomIcon = false,
this.selectionCustomIcon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: padding,
alignment: Alignment.center,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
borderRadius: isAllowRadius ? 15 : null,
side: isBorderAllowed
? BorderSide(color: const Color(0xffefefef), width: 1)
: null,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabelText(),
_buildDropdown(context),
],
),
);
}
Widget _buildLabelText() {
return Text(
labelText,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xff898A8D),
letterSpacing: -0.2,
height: 18 / 12,
),
);
}
Widget _buildDropdown(BuildContext context) {
return GestureDetector(
onTap: isEnable
? () async {
final renderBox = context.findRenderObject() as RenderBox;
final offset = renderBox.localToGlobal(Offset.zero);
final selected = await showMenu<String>(
context: context,
position: RelativeRect.fromLTRB(
offset.dx,
offset.dy + renderBox.size.height,
offset.dx + renderBox.size.width,
0,
),
items: dropdownItems
.map(
(e) => PopupMenuItem<String>(
value: e,
child: Text(e),
),
)
.toList(),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
);
if (selected != null && onChange != null) {
onChange!(selected);
}
}
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
(selectedValue == null || selectedValue!.isEmpty)
? hintText
: selectedValue!,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 14,
height: 21 / 14,
fontWeight: FontWeight.w500,
color: (selectedValue != null && selectedValue!.isNotEmpty)
? const Color(0xff2E3039)
: const Color(0xffB0B0B0),
letterSpacing: -0.2,
),
),
),
if (hasSelectionCustomIcon && selectionCustomIcon != null)
Utils.buildSvgWithAssets(icon: selectionCustomIcon!)
else
const Icon(Icons.keyboard_arrow_down_outlined),
],
),
);
}
}

@ -1,6 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.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/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart';
import '../core/dependencies.dart';
// TODO: Import AppColors if bgRedColor is defined there // TODO: Import AppColors if bgRedColor is defined there
// import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart'; // import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart';
@ -24,11 +33,13 @@ class TextInputWidget extends StatelessWidget {
final bool isCountryDropDown; final bool isCountryDropDown;
final bool hasError; final bool hasError;
final String? errorMessage; final String? errorMessage;
Function(CountryEnum)? onCountryChange;
SelectionTypeEnum? selectionType;
// final List<Country> countryList; // final List<Country> countryList;
// final Function(Country)? onCountryChange; // final Function(Country)? onCountryChange;
const TextInputWidget({ TextInputWidget({
Key? key, Key? key,
required this.labelText, required this.labelText,
required this.hintText, required this.hintText,
@ -48,15 +59,15 @@ class TextInputWidget extends StatelessWidget {
this.isCountryDropDown = false, this.isCountryDropDown = false,
this.hasError = false, this.hasError = false,
this.errorMessage, this.errorMessage,
this.onCountryChange,
this.selectionType,
// this.countryList = const [], // this.countryList = const [],
// this.onCountryChange, // this.onCountryChange,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Assuming AppColors.bgRedColor exists, otherwise using Colors.red final errorColor = AppColors.primaryRedColor;
final errorColor = Colors.red; // Replace with AppColors.bgRedColor if available
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -66,34 +77,45 @@ class TextInputWidget extends StatelessWidget {
alignment: Alignment.center, alignment: Alignment.center,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white, color: Colors.white,
borderRadius: isAllowRadius ? 15 : null, borderRadius: isAllowRadius ? 12 : null,
side: isBorderAllowed ? BorderSide(color: hasError ? errorColor : const Color(0xffefefef), width: 1) : null, side: isBorderAllowed ? BorderSide(color: hasError ? errorColor : const Color(0xffefefef), width: 1) : null,
), ),
child: Row( child: Row(
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
children: [ children: [
if (isAllowLeadingIcon && leadingIcon != null) _buildLeadingIcon(context), if (isAllowLeadingIcon && leadingIcon != null && !isCountryDropDown) _buildLeadingIcon(context),
Expanded( isCountryDropDown
child: Column( ? CustomCountryDropdown(
mainAxisSize: MainAxisSize.min, countryList: CountryEnum.values,
crossAxisAlignment: CrossAxisAlignment.start, onCountryChange: onCountryChange,
children: [ isRtl: Directionality.of(context) == TextDirection.rtl,
_buildLabelText(), isFromBottomSheet: isCountryDropDown,
_buildTextField(context), isEnableTextField: true,
], onPhoneNumberChanged: onChange,
), // textField: _buildTextField(context),
), )
: Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabelText(),
_buildTextField(context),
],
),
),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context),
], ],
), ),
), ),
if (hasError && errorMessage != null) if (hasError && errorMessage != null)
Padding( Padding(
padding: const EdgeInsets.only(top: 4.0, left: 12.0), // Adjust padding as needed padding: EdgeInsets.only(top: 4.h, left: 12.h), // Adjust padding as needed
child: Text( child: Text(
errorMessage!, errorMessage!,
style: TextStyle( style: TextStyle(
color: errorColor, color: errorColor,
fontSize: 12, fontSize: 12.fSize,
), ),
), ),
), ),
@ -103,21 +125,56 @@ class TextInputWidget extends StatelessWidget {
Widget _buildLeadingIcon(BuildContext context) { Widget _buildLeadingIcon(BuildContext context) {
return Container( return Container(
height: 40, height: 40.h,
width: 40, width: 40.h,
margin: const EdgeInsets.only(right: 10), margin: EdgeInsets.only(right: 10.h),
padding: const EdgeInsets.all(8), padding: EdgeInsets.all(8.h),
decoration: const BoxDecoration(color: Color(0xFFEFEFF0), borderRadius: BorderRadius.all(Radius.circular(10))), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
borderRadius: 10.h,
color: AppColors.greyColor,
),
child: Utils.buildSvgWithAssets(icon: leadingIcon!)); child: Utils.buildSvgWithAssets(icon: leadingIcon!));
} }
Widget _buildTrailingIcon(BuildContext context) {
final AppState appState = getIt.get<AppState>();
return Container(
height: 40.h,
width: 40.h,
margin: EdgeInsets.zero,
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.whiteColor),
child: GestureDetector(
onTap: () async {
bool isGregorian = true;
final picked = await showHijriGregBottomSheet(context,
switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h),
language: appState.getLanguageCode()!,
initialDate: DateTime.now(),
okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)),
cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),
onCalendarTypeChanged: (bool value) {
isGregorian = value;
});
if (picked != null && onChange != null) {
// if (onCalendarTypeChanged != null) {
// onCalendarTypeChanged.call(isGregorian);
// }
onChange!(picked.toIso8601String());
}
},
child: Utils.buildSvgWithAssets(icon: AppAssets.calendar),
),
);
}
Widget _buildLabelText() { Widget _buildLabelText() {
return Text( return Text(
labelText, labelText,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12.fSize,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xff898A8D), color: AppColors.inputLabelTextColor,
letterSpacing: -0.2, letterSpacing: -0.2,
height: 18 / 12, height: 18 / 12,
), ),
@ -137,30 +194,18 @@ class TextInputWidget extends StatelessWidget {
onChanged: onChange, onChanged: onChange,
focusNode: focusNode, focusNode: focusNode,
autofocus: autoFocus, autofocus: autoFocus,
style: const TextStyle( style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2),
fontSize: 14,
height: 21 / 14,
fontWeight: FontWeight.w500,
color: Color(0xff2E3039),
letterSpacing: -0.2,
),
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
hintText: hintText, hintText: hintText,
hintStyle: const TextStyle( hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2),
fontSize: 14, prefixIconConstraints: BoxConstraints(minWidth: 45.h),
height: 21 / 16,
fontWeight: FontWeight.w500,
color: Color(0xff898A8D),
letterSpacing: -0.2,
),
prefixIconConstraints: const BoxConstraints(minWidth: 45),
prefixIcon: prefix == null prefixIcon: prefix == null
? null ? null
: Text( : Text(
"+" + prefix!, "+" + prefix!,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14.fSize,
height: 21 / 14, height: 21 / 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xff2E303A), color: Color(0xff2E303A),

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
@ -51,18 +52,18 @@ class _LanguageSelectorState extends State<LanguageSelector> {
widget.onLanguageChanged(newLanguage); widget.onLanguageChanged(newLanguage);
}, },
child: Container( child: Container(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8.h),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)), decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.language), Utils.buildSvgWithAssets(icon: AppAssets.language),
const SizedBox(width: 6), SizedBox(width: 6.h),
Text( Text(
currentLangData['name']?.toUpperCase() ?? 'EN', currentLangData['name']?.toUpperCase() ?? 'EN',
style: context.dynamicTextStyle( style: context.dynamicTextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 14, fontSize: 14.fSize,
color: AppColors.primaryRedColor, color: AppColors.primaryRedColor,
letterSpacing: 0.1, letterSpacing: 0.1,
isLanguageSwitcher: true, isLanguageSwitcher: true,

@ -0,0 +1,230 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
class OTPVerificationPage extends StatefulWidget {
final String phoneNumber;
const OTPVerificationPage({Key? key, required this.phoneNumber}) : super(key: key);
@override
State<OTPVerificationPage> createState() => _OTPVerificationPageState();
}
class _OTPVerificationPageState extends State<OTPVerificationPage> {
final int _otpLength = 4;
late final List<TextEditingController> _controllers;
late final List<FocusNode> _focusNodes;
Timer? _resendTimer;
int _resendTime = 60;
bool _isOtpComplete = false;
@override
void initState() {
super.initState();
_controllers = List.generate(_otpLength, (_) => TextEditingController());
_focusNodes = List.generate(_otpLength, (_) => FocusNode());
_startResendTimer();
// Focus the first field once the screen is built
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_focusNodes.isNotEmpty) {
FocusScope.of(context).requestFocus(_focusNodes[0]);
}
});
}
@override
void dispose() {
for (final c in _controllers) c.dispose();
for (final f in _focusNodes) f.dispose();
_resendTimer?.cancel();
super.dispose();
}
void _startResendTimer() {
_resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_resendTime > 0) {
setState(() => _resendTime--);
} else {
timer.cancel();
}
});
}
void _onOtpChanged(int index, String value) {
if (value.length == 1 && index < _otpLength - 1) {
_focusNodes[index + 1].requestFocus();
} else if (value.isEmpty && index > 0) {
_focusNodes[index - 1].requestFocus();
}
_checkOtpCompletion();
}
void _checkOtpCompletion() {
final isComplete = _controllers.every((c) => c.text.isNotEmpty);
if (isComplete != _isOtpComplete) {
setState(() => _isOtpComplete = isComplete);
if (isComplete) {
_verifyOtp();
}
}
}
void _resendOtp() {
if (_resendTime == 0) {
setState(() => _resendTime = 60);
_startResendTimer();
autoFillOtp("1234");
// call resend API here
}
}
String _getMaskedPhoneNumber() {
final phone = widget.phoneNumber;
return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
hideLogoAndLang: true,
onBackPressed: () {
Navigator.of(context).pop();
},
onLanguageChanged: (lang) {},
),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 40.h),
Text(
'OTP Verification',
style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold),
),
SizedBox(height: 16.h),
Text(
'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification',
style: TextStyle(fontSize: 16.fSize, color: Colors.grey),
),
SizedBox(height: 40.h),
// OTP Input Fields
SizedBox(
height: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(_otpLength, (index) {
return ValueListenableBuilder<TextEditingValue>(
valueListenable: _controllers[index],
builder: (context, value, _) {
final hasText = value.text.isNotEmpty;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
width: 70.h,
margin: EdgeInsets.symmetric(horizontal: 4.h),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: _isOtpComplete ? AppColors.successColor : (hasText ? AppColors.blackBgColor : AppColors.whiteColor), borderRadius: 16),
child: Center(
child: TextField(
controller: _controllers[index],
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
style: TextStyle(
fontSize: 40.fSize,
fontWeight: FontWeight.bold,
color: AppColors.whiteColor,
),
decoration: InputDecoration(
counterText: '',
filled: true,
fillColor: Colors.transparent,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(18),
borderSide: BorderSide.none,
),
),
onChanged: (v) => _onOtpChanged(index, v),
),
),
);
},
);
}),
),
),
const SizedBox(height: 32),
// Resend OTP
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Didn't receive it? "),
if (_resendTime > 0)
Text(
'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ',
style: const TextStyle(color: Colors.grey),
)
else
GestureDetector(
onTap: _resendOtp,
child: const Text(
'Resend',
style: TextStyle(
color: AppColors.primaryRedColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
),
);
}
void _verifyOtp() {
final otp = _controllers.map((c) => c.text).join();
debugPrint('Verifying OTP: $otp');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Verifying OTP: $otp')),
);
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => RegisterNewStep2(null, {"nationalID": "12345678654321"})));
}
/// Auto fill OTP into text fields
void autoFillOtp(String otp) {
if (otp.length != _otpLength) return;
for (int i = 0; i < _otpLength; i++) {
_controllers[i].text = otp[i];
}
// Move focus to the last field
_focusNodes[_otpLength - 1].requestFocus();
// Trigger completion check and color update
_checkOtpCompletion();
}
}

@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.0" version: "2.13.0"
audio_session: audio_session:
dependency: transitive dependency: transitive
description: description:
@ -742,6 +742,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.1.3" version: "13.1.3"
hijri_gregorian_calendar:
dependency: "direct main"
description:
name: hijri_gregorian_calendar
sha256: "9d23b52192783c1ad134b1ac001be7977342cb579c6b380647b6494fbd464d29"
url: "https://pub.dev"
source: hosted
version: "0.0.4"
html: html:
dependency: transitive dependency: transitive
description: description:
@ -1591,10 +1599,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.3.1" version: "15.0.0"
web: web:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1636,5 +1644,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.8.0-0 <4.0.0" dart: ">=3.8.1 <4.0.0"
flutter: ">=3.29.0" flutter: ">=3.29.0"

@ -68,6 +68,7 @@ dependencies:
web: any web: any
flutter_staggered_animations: ^1.1.1 flutter_staggered_animations: ^1.1.1
smooth_corner: ^1.1.1 smooth_corner: ^1.1.1
hijri_gregorian_calendar: ^0.0.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -81,6 +82,7 @@ flutter:
- assets/ - assets/
- assets/fonts/ - assets/fonts/
- assets/langs/ - assets/langs/
- assets/json/
- assets/images/ - assets/images/
- assets/images/svg/ - assets/images/svg/
- assets/images/png/ - assets/images/png/

Loading…
Cancel
Save