# Conflicts:
#	lib/presentation/lab/lab_orders_page.dart
pull/10/head
Sultan khan 5 months ago
commit 3cafc16d50

@ -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,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path d="M20 9.25C15.5442 9.25 11.9279 12.8415 11.9279 17.277C11.9278 18.3087 11.8584 19.0873 11.3827 19.7872C11.3166 19.8831 11.2288 20.0032 11.1329 20.1345C10.9663 20.3625 10.7753 20.624 10.6303 20.8505C10.3745 21.2503 10.1246 21.7324 10.0387 22.2942C9.75836 24.127 11.0506 25.3136 12.3375 25.8454C16.8702 27.7182 23.1298 27.7182 27.6625 25.8454C28.9494 25.3136 30.2416 24.127 29.9613 22.2942C29.8754 21.7324 29.6255 21.2503 29.3697 20.8505C29.2247 20.624 29.0337 20.3625 28.8671 20.1345C28.7712 20.0033 28.6835 19.8832 28.6173 19.7873C28.1416 19.0874 28.0722 18.3088 28.0721 17.277C28.0721 12.8415 24.4558 9.25 20 9.25Z" fill="#8F9AA3"/>
<path d="M19.9985 30.7477C21.0187 30.7477 21.9742 30.4563 22.7776 29.9511C23.4906 29.5027 23.8472 29.2785 23.7224 28.9057C23.5976 28.533 23.1057 28.5747 22.1217 28.6582C20.7146 28.7776 19.2824 28.7776 17.8752 28.6582C16.8913 28.5747 16.3993 28.533 16.2745 28.9057C16.1498 29.2784 16.5063 29.5027 17.2193 29.9511C18.0227 30.4563 18.9782 30.7477 19.9985 30.7477Z" fill="#8F9AA3"/>
</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,6 +782,29 @@
"resultsPending": "النتائج معلقة",
"resultsAvailable": "النتائج متاحة",
"viewReport": "عرض التقرير",
"checkAvailability": "التحقق من التوفر",
"readInstructions": "قراءة التعليمات",
"searchLabReport" : "ابحث عن تقرير المختبر",
"prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.",
"searchLabReport" : "ابحث عن تقرير المختبر"
"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,12 +772,35 @@
"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",
"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?",
"loginOrRegister": "Login or Register",
"myFiles": "My Files",
"resultsPending": "Results Pending",
"resultsAvailable": "Results Available",
"viewReport": "View Report",
"checkAvailability": "Check Availability",
"readInstructions": "Read Instructions",
"searchLabReport" : "Search Lab Report",
"prescriptionDeliveryError": "This clinic doesn't support refill",
"searchLabReport" : "Search Lab Report"
"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"
}

@ -749,7 +749,6 @@ class ApiConsts {
static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType';
static setBackendURLs() {
if (isDevelopment) {
baseUrl = "https://uat.hmgwebservices.com/";

@ -67,9 +67,13 @@ class AppAssets {
static const String rating_icon = '$svgBasePath/rating_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_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';
//bottom navigation//
static const String homeBottom = '$svgBasePath/home_bottom.svg';
static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg';
static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg';

@ -22,13 +22,8 @@ class AppState {
set setUserLong(v) => userLong = v;
final PostParamsModel _postParamsInitConfig = PostParamsModel(
channel: 3,
versionID: ApiConsts.VERSION_ID,
ipAddress: '10.20.10.20',
generalId: 'Cs2020@2016\$2958',
deviceTypeID: "2",
sessionID: 'TMRhVmkGhOsvamErw');
final PostParamsModel _postParamsInitConfig =
PostParamsModel(channel: 3, versionID: ApiConsts.VERSION_ID, ipAddress: '10.20.10.20', generalId: 'Cs2020@2016\$2958', deviceTypeID: "2", sessionID: 'TMRhVmkGhOsvamErw');
void setPostParamsInitConfig() {
isAuthenticated = false;
@ -50,7 +45,9 @@ class AppState {
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;

@ -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/book_appointments/book_appointments_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_view_model.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<RadiologyRepo>(() => RadiologyRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<PrescriptionsRepo>(() => PrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<InsuranceRepo>(() => InsuranceRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -90,6 +93,13 @@ class AppDependencies {
),
);
getIt.registerLazySingleton<InsuranceViewModel>(
() => InsuranceViewModel(
insuranceRepo: getIt(),
errorHandlerService: getIt(),
),
);
getIt.registerLazySingleton<AuthenticationViewModel>(
() => AuthenticationViewModel(
authenticationRepo: getIt(),

@ -22,12 +22,6 @@ enum ViewStateEnum {
errorLocal,
}
enum LoginTypeEnum {
fromLogin,
silentLogin,
silentWithOTP,
}
enum OTPTypeEnum { sms, whatsapp }
enum CountryEnum { saudiArabia, unitedArabEmirates }
@ -40,6 +34,51 @@ enum MaritalStatusTypeEnum { single, married, divorced, widowed }
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 {
/// Convert enum to int
int toInt() {

@ -4,8 +4,6 @@ import 'dart:io';
import 'dart:ui';
import 'package:device_calendar/device_calendar.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:manage_calendar_events/manage_calendar_events.dart' as ios;
import 'package:timezone/data/latest.dart' as tzl;
@ -137,14 +135,12 @@ class CalendarUtils {
print("catchError " + e.toString());
}).whenComplete(() {
print("whenComplete Calender ID " + eventId!);
// Utils.showToast(LocaleKeys.appoReminderSuccess.tr());
});
} else {
await _myPlugin.createEvent(calendarId: writableCalendars.id!, event: iosCalEvent).catchError((e) {
print("catchError " + e.toString());
}).whenComplete(() {
print("whenComplete Calender ID iOS " + eventId!);
// Utils.showToast(LocaleKeys.appoReminderSuccess.tr());
});
}
}

@ -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/widget_extensions.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/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart';
@ -59,14 +60,13 @@ class Utils {
// ));
return !isAddHours
? 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")
.format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add(
Duration(
hours: isAddHours ? 3 : 0,
),
));
;
.format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add(
Duration(
hours: isAddHours ? 3 : 0,
),
));
}
static String convertStringToDateTime(String dateTimeString) {
@ -209,12 +209,12 @@ class Utils {
builder: (BuildContext context) => LoadingDialog(),
)
.then((value) {
_isLoadingVisible = false;
})
_isLoadingVisible = false;
})
.catchError((e) {})
.onError(
(error, stackTrace) {},
);
);
}
static void hideLoading() {
@ -236,11 +236,12 @@ class Utils {
showDialog(
barrierDismissible: false,
context: context,
builder: (cxt) => ConfirmDialog(
title: title!,
message: message!,
onTap: onTap,
),
builder: (cxt) =>
ConfirmDialog(
title: title!,
message: message!,
onTap: onTap,
),
);
}
@ -371,7 +372,9 @@ class Utils {
static String formatHijriDateToDisplay(String hijriDateString) {
try {
// 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('-');
if (parts.length != 3) return "";
@ -429,8 +432,14 @@ class Utils {
void Function(LottieComposition)? onLoaded,
}) {
return Lottie.asset(assetPath,
height: height ?? MediaQuery.of(context).size.height * 0.26,
width: width ?? MediaQuery.of(context).size.width,
height: height ?? MediaQuery
.of(context)
.size
.height * 0.26,
width: width ?? MediaQuery
.of(context)
.size
.width,
fit: fit,
alignment: alignment,
repeat: repeat,
@ -487,9 +496,13 @@ class Utils {
],
);
}
static Future<bool> isGoogleServicesAvailable() async {
GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability();
String status = availability.toString().split('.').last;
String status = availability
.toString()
.split('.')
.last;
if (status == "success") {
return true;
}
@ -510,3 +523,26 @@ class Utils {
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 {
double get screenHeight => MediaQuery.of(this).size.height;
double get screenWidth => MediaQuery.of(this).size.width;
ThemeData get theme => Theme.of(this);
TextTheme get textTheme => theme.textTheme;
// TextStyle get headline1 => textTheme.headline1!;
// TextStyle get headline2 => textTheme.headline2!;
// TextStyle get headline3 => textTheme.headline3!;
// TextStyle get headline4 => textTheme.headline4!;
// TextStyle get headline5 => textTheme.headline5!;
// TextStyle get headline6 => textTheme.headline6!;
// TextStyle get bodyText1 => textTheme.bodyText1!;
// TextStyle get bodyText2 => textTheme.bodyText2!;
// TextStyle get headline1 => textTheme.headline1!;
// TextStyle get headline2 => textTheme.headline2!;
// TextStyle get headline3 => textTheme.headline3!;
// TextStyle get headline4 => textTheme.headline4!;
// TextStyle get headline5 => textTheme.headline5!;
// TextStyle get headline6 => textTheme.headline6!;
// TextStyle get bodyText1 => textTheme.bodyText1!;
// 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:auto_size_text/auto_size_text.dart';
import 'package:flutter/cupertino.dart';
@ -11,16 +13,13 @@ extension CapExtension on String {
String get allInCaps => this.toUpperCase();
String get capitalizeFirstofEach =>
this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : "";
String get capitalizeFirstofEach => this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : "";
}
extension EmailValidator on String {
Widget get toWidget => Text(this);
Widget toText8(
{Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) =>
Text(
Widget toText8({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
this,
maxLines: maxlines,
overflow: textOverflow,
@ -33,14 +32,7 @@ extension EmailValidator on String {
),
);
Widget toText10(
{Color? color,
bool isBold = false,
bool isUnderLine = false,
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow}) =>
Text(
Widget toText10({Color? color, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
this,
maxLines: maxlines,
overflow: textOverflow,
@ -54,15 +46,7 @@ extension EmailValidator on String {
decorationColor: color ?? AppColors.blackColor),
);
Widget toText11(
{Color? color,
FontWeight? weight,
bool isUnderLine = false,
bool isCenter = false,
bool isBold = false,
int maxLine = 0,
double letterSpacing = 0.64}) =>
Text(
Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0.64}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
@ -76,20 +60,11 @@ extension EmailValidator on String {
),
);
Widget toText12(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
FontWeight? fontWeight,
bool isCenter = false,
double? height,
int maxLine = 0}) =>
Text(
Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, int maxLine = 0}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor,
@ -100,9 +75,7 @@ extension EmailValidator on String {
),
);
Widget toText12Auto(
{Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) =>
AutoSizeText(
Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => AutoSizeText(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
@ -163,14 +136,7 @@ extension EmailValidator on String {
decoration: isUnderLine ? TextDecoration.underline : null),
);
Widget toText14(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
FontWeight? weight,
int? maxlines}) =>
Text(
Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines,
@ -182,14 +148,7 @@ extension EmailValidator on String {
decoration: isUnderLine ? TextDecoration.underline : null),
);
Widget toText15(
{Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
FontWeight? weight,
int? maxlines}) =>
Text(
Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines,
@ -225,93 +184,68 @@ extension EmailValidator on String {
Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
color: color ?? AppColors.blackColor,
fontSize: 17.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(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(
maxLines: maxlines,
textAlign: isCenter ? TextAlign.center : null,
this,
style: TextStyle(
fontSize: 18.fSize,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
style: TextStyle(fontSize: 18.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
Widget toText19({Color? color, bool isBold = false}) => Text(
this,
style: TextStyle(
fontSize: 19.fSize,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
style: TextStyle(fontSize: 19.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
Widget toText20({Color? color, bool isBold = false}) => Text(
this,
style: TextStyle(
fontSize: 20.fSize,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: color ?? AppColors.blackColor,
letterSpacing: -0.4),
style: TextStyle(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(
this,
maxLines: maxlines,
style: TextStyle(
color: color ?? AppColors.blackColor,
fontSize: 21.fSize,
letterSpacing: -0.4,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
style: TextStyle(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(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 1,
color: color ?? AppColors.blackColor,
fontSize: 22.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(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(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 23 / 24,
color: color ?? AppColors.blackColor,
fontSize: 24.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(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(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
height: 32 / 32,
color: color ?? AppColors.blackColor,
fontSize: 32.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(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(
this,
style: TextStyle(
height: 32 / 32,
color: color ?? AppColors.blackColor,
fontSize: 44.fSize,
letterSpacing: -0.4,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(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 = ""}) {
@ -347,9 +281,7 @@ extension EmailValidator on String {
}
bool isValidEmail() {
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);
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);
}
String toFormattedDate() {
@ -451,3 +383,155 @@ class FontUtils {
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,
});
Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister(
{required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID});
Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister({required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID});
}
class AuthenticationRepoImp implements AuthenticationRepo {
@ -114,8 +113,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister(
{required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}) async {
Future<Either<Failure, GenericApiModel<dynamic>>> sendActivationCodeRegister({required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}) async {
int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1;
//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: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/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/models/request_models/check_patient_authentication_request_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@ -20,12 +25,67 @@ class AuthenticationViewModel extends ChangeNotifier {
required this.dialogService,
});
final TextEditingController nationalIdController = TextEditingController();
final TextEditingController phoneNumberController = TextEditingController();
final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = 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 {
String firebaseToken =
"dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc";
String firebaseToken = "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc";
final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken);
result.fold(
@ -40,96 +100,96 @@ class AuthenticationViewModel extends ChangeNotifier {
);
}
// Future<void> checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome(
// phoneNumber: '0567184134',
// otpTypeEnum: OTPTypeEnum.sms,
// deviceToken: 'dummyDeviceToken123',
// patientOutSA: true,
// loginTokenID: 'dummyLoginToken456',
// registeredData: null,
// patientId: 12345,
// nationIdText: '1234567890',
// countryCode: 'SA',
// );
//
// final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
// result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
// (apiResponse) {
// if (apiResponse.data['isSMSSent']) {
// // TODO: set this in AppState
// // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']);
// // loginTokenID = value['LogInTokenID'],
// // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request),
// sendActivationCode(type);
// } else {
// if (apiResponse.data['IsAuthenticated']) {
// checkActivationCode(onWrongActivationCode: (String? message) {});
// }
// }
// },
// );
// }
// Future<void> sendActivationCode({required OTPTypeEnum otpTypeEnum}) async {
// var request = RequestUtils.getCommonRequestAuthProvider(
// otpTypeEnum: otpTypeEnum,
// registeredData: null,
// deviceToken: "dummyLoginToken456",
// mobileNumber: "0567184134",
// zipCode: "SA",
// patientOutSA: true,
// loginTokenID: "dummyLoginToken456",
// selectedOption: selectedOption,
// patientId: 12345,
// );
//
// request.sMSSignature = await SMSOTP.getSignature();
// selectedOption = type;
// // GifLoaderDialogUtils.showMyDialog(context);
// if (healthId != null || isDubai) {
// if (!isDubai) {
// request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob));
// }
// request.healthId = healthId;
// request.isHijri = isHijri;
// await this.apiClient.sendActivationCodeRegister(request).then((result) {
// // GifLoaderDialogUtils.hideDialog(context);
// if (result != null && result['isSMSSent'] == true) {
// this.startSMSService(type);
// }
// }).catchError((r) {
// GifLoaderDialogUtils.hideDialog(context);
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: r.toString(),
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ));
// // AppToast.showErrorToast(message: r);
// });
// } else {
// request.dob = "";
// request.healthId = "";
// request.isHijri = 0;
// await this.authService.sendActivationCode(request).then((result) {
// GifLoaderDialogUtils.hideDialog(context);
// if (result != null && result['isSMSSent'] == true) {
// this.startSMSService(type);
// }
// }).catchError((r) {
// GifLoaderDialogUtils.hideDialog(context);
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: r.toString(),
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ));
// // AppToast.showErrorToast(message: r.toString());
// });
// }
// }
// Future<void> checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome(
// phoneNumber: '0567184134',
// otpTypeEnum: OTPTypeEnum.sms,
// deviceToken: 'dummyDeviceToken123',
// patientOutSA: true,
// loginTokenID: 'dummyLoginToken456',
// registeredData: null,
// patientId: 12345,
// nationIdText: '1234567890',
// countryCode: 'SA',
// );
//
// final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
// result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
// (apiResponse) {
// if (apiResponse.data['isSMSSent']) {
// // TODO: set this in AppState
// // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']);
// // loginTokenID = value['LogInTokenID'],
// // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request),
// sendActivationCode(type);
// } else {
// if (apiResponse.data['IsAuthenticated']) {
// checkActivationCode(onWrongActivationCode: (String? message) {});
// }
// }
// },
// );
// }
// Future<void> sendActivationCode({required OTPTypeEnum otpTypeEnum}) async {
// var request = RequestUtils.getCommonRequestAuthProvider(
// otpTypeEnum: otpTypeEnum,
// registeredData: null,
// deviceToken: "dummyLoginToken456",
// mobileNumber: "0567184134",
// zipCode: "SA",
// patientOutSA: true,
// loginTokenID: "dummyLoginToken456",
// selectedOption: selectedOption,
// patientId: 12345,
// );
//
// request.sMSSignature = await SMSOTP.getSignature();
// selectedOption = type;
// // GifLoaderDialogUtils.showMyDialog(context);
// if (healthId != null || isDubai) {
// if (!isDubai) {
// request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob));
// }
// request.healthId = healthId;
// request.isHijri = isHijri;
// await this.apiClient.sendActivationCodeRegister(request).then((result) {
// // GifLoaderDialogUtils.hideDialog(context);
// if (result != null && result['isSMSSent'] == true) {
// this.startSMSService(type);
// }
// }).catchError((r) {
// GifLoaderDialogUtils.hideDialog(context);
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: r.toString(),
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ));
// // AppToast.showErrorToast(message: r);
// });
// } else {
// request.dob = "";
// request.healthId = "";
// request.isHijri = 0;
// await this.authService.sendActivationCode(request).then((result) {
// GifLoaderDialogUtils.hideDialog(context);
// if (result != null && result['isSMSSent'] == true) {
// this.startSMSService(type);
// }
// }).catchError((r) {
// GifLoaderDialogUtils.hideDialog(context);
// context.showBottomSheet(
// child: ExceptionBottomSheet(
// message: r.toString(),
// onOkPressed: () {
// Navigator.of(context).pop();
// },
// ));
// // 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;
}
}

@ -32,6 +32,7 @@ class PrescriptionDetailResponseModel {
String? sKU;
num? scaleOffset;
String? startDate;
bool? hasReminder;
PrescriptionDetailResponseModel(
{this.address,
@ -66,7 +67,8 @@ class PrescriptionDetailResponseModel {
this.route,
this.sKU,
this.scaleOffset,
this.startDate});
this.startDate,
this.hasReminder = false});
PrescriptionDetailResponseModel.fromJson(Map<String, dynamic> json) {
address = json['Address'];
@ -102,6 +104,7 @@ class PrescriptionDetailResponseModel {
sKU = json['SKU'];
scaleOffset = json['ScaleOffset'];
startDate = json['StartDate'];
hasReminder = false;
}
Map<String, dynamic> toJson() {

@ -43,6 +43,14 @@ class PrescriptionsViewModel extends ChangeNotifier {
notifyListeners();
}
setPrescriptionItemReminder(bool value, PrescriptionDetailResponseModel item) {
int index = prescriptionDetailsList.indexOf(item);
if (index != -1) {
prescriptionDetailsList[index].hasReminder = value;
notifyListeners();
}
}
setIsSortByClinic(bool value) {
isSortByClinic = value;
if (isSortByClinic) {

@ -781,6 +781,29 @@ abstract class LocaleKeys {
static const resultsAvailable = 'resultsAvailable';
static const viewReport = 'viewReport';
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 readInstructions = 'readInstructions';
static const searchLabReport = 'searchLabReport';
}

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.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/insurance/insurance_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/radiology/radiology_view_model.dart';
@ -71,6 +72,12 @@ void main() async {
errorHandlerService: getIt(),
),
),
ChangeNotifierProvider<InsuranceViewModel>(
create: (_) => InsuranceViewModel(
insuranceRepo: getIt(),
errorHandlerService: getIt(),
),
),
ChangeNotifierProvider<AuthenticationViewModel>(
create: (_) => AuthenticationViewModel(
authenticationRepo: getIt(),

@ -1,17 +1,21 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/cupertino.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/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/presentation/authentication/register.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/input_widget.dart';
import 'package:sizer/sizer.dart'; // Import sizer
import 'package:provider/provider.dart';
class LoginScreen extends StatefulWidget {
@override
@ -31,99 +35,165 @@ class _LoginScreen extends State<LoginScreen> {
@override
Widget build(BuildContext context) {
return Sizer(// Wrap with Sizer
builder: (context, orientation, deviceType) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
appBar: CustomAppBar(
onBackPressed: () {
},
onLanguageChanged: (String value) {
print(value);
context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US'));
},
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(); // 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(
child: Padding(
padding: EdgeInsets.only(left: 6.w, right: 6.w),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 45.w, height: 22.h, repeat: true, fit: BoxFit.cover),
SizedBox(height: 19.h), // Adjusted to sizer unit
LocaleKeys.welcomeToDrSulaiman.tr().toText22(isBold: true, color: AppColors.textColor),
// Text(
// LocaleKeys.welcomeToDrSulaiman.tr(),
// style: context.dynamicTextStyle(
// fontSize: 22,
// fontWeight: FontWeight.w600,
// color: AppColors.textColor,
// letterSpacing: -0.4,
// height: 40 / 28,
// ),
// ),
SizedBox(height: 4.h), // Adjusted to sizer unit (approx 32px)
TextInputWidget(
labelText: "${LocaleKeys.nationalId.tr()} / ${LocaleKeys.fileNo.tr()}",
hintText: "xxxxxxxxx",
controller: TextEditingController(),
keyboardType: TextInputType.number,
isEnable: true,
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: () {},
),
);
}
void showLoginModel({required BuildContext context, required AuthenticationViewModel authVM}) {
context.showBottomSheet(
isScrollControlled: true,
isDismissible: false,
useSafeArea: true,
backgroundColor: Colors.transparent,
child: StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).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: EdgeInsets.only(bottom: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () {},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: AppColors.whiteColor,
icon: AppAssets.message,
),
),
SizedBox(height: 1.8.h), // Adjusted to sizer unit (approx 14px)
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: context.dynamicTextStyle(
color: Colors.black,
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 = () {},
),
],
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.h),
child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor),
),
).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/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/home/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/large_service_card.dart';
@ -49,7 +50,8 @@ class _LandingPageState extends State<LandingPage> {
CustomButton(
text: LocaleKeys.loginOrRegister.tr(context: context),
onPressed: () async {
await authenticationViewModel.selectDeviceImei();
// await authenticationViewModel.selectDeviceImei();
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => LoginScreen()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
@ -178,10 +180,7 @@ class _LandingPageState extends State<LandingPage> {
)
: Container(
height: 127.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(

@ -20,22 +20,19 @@ class _LandingNavigationState extends State<LandingNavigation> {
body: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: const [
LandingPage(),
children: [
const LandingPage(),
MedicalFilePage(),
LandingPage(),
LandingPage(),
LandingPage(),
const LandingPage(),
const LandingPage(),
const LandingPage(),
],
),
bottomNavigationBar: BottomNavigation(
currentIndex: _currentIndex,
onTap: (index) {
setState(() => _currentIndex = index);
_pageController.animateToPage(
index,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut);
_pageController.animateToPage(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/extensions/string_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/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
@ -78,6 +79,11 @@ class SmallServiceCard extends StatelessWidget {
);
break;
case "insurance_update":
Navigator.of(context).push(
FadePage(
page: InsuranceHomePage(),
),
);
break;
default:
// 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);
}
}

@ -275,6 +275,13 @@ class _LabOrdersPageState extends State<LabOrdersPage> {
return "";
}
}
getLabSuggestions(LabViewModel model) {
if(model.patientLabOrders.isEmpty){
return [];
}
return model.patientLabOrders.map((m) => m.testDetails).toList();
}
}

@ -1,20 +1,28 @@
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/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/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/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:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart';
import 'package:provider/provider.dart';
class MedicalFilePage extends StatelessWidget {
const MedicalFilePage({super.key});
MedicalFilePage({super.key});
late InsuranceViewModel insuranceViewModel;
@override
Widget build(BuildContext context) {
insuranceViewModel = Provider.of<InsuranceViewModel>(context);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
appBar: AppBar(
@ -41,7 +49,6 @@ class MedicalFilePage extends StatelessWidget {
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
leadingIcon: AppAssets.student_card,
hasError: true,
),
SizedBox(height: 16.h),
Container(
@ -162,101 +169,13 @@ class MedicalFilePage extends StatelessWidget {
),
SizedBox(height: 16.h),
//Insurance Tab Data
Container(
// height: 150.h,
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: 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,
),
],
),
),
),
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return insuranceVM.isInsuranceLoading
? const MoviesShimmerWidget()
: PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo)));
}),
SizedBox(height: 10.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13),

@ -29,6 +29,8 @@ class PrescriptionDetailPage extends StatefulWidget {
class _PrescriptionDetailPageState extends State<PrescriptionDetailPage> {
late PrescriptionsViewModel prescriptionsViewModel;
bool _isSwitched = false; // Initial state of the switch
@override
void initState() {
scheduleMicrotask(() {
@ -46,266 +48,353 @@ class _PrescriptionDetailPageState extends State<PrescriptionDetailPage> {
title: LocaleKeys.prescriptions.tr(context: context).toText18(),
backgroundColor: AppColors.bgScaffoldColor,
),
body: SingleChildScrollView(
child: Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 24.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
widget.prescriptionsResponseModel.doctorImageURL!,
width: 24.h,
height: 24.h,
fit: BoxFit.fill,
).circle(100),
SizedBox(width: 8.h),
Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)),
],
),
SizedBox(height: 16.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.h,
runSpacing: 6.h,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
icon: AppAssets.doctor_calendar_icon,
iconColor: AppColors.textColor,
iconSize: 13.h,
text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: widget.prescriptionsResponseModel.clinicDescription!,
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
icon: AppAssets.rating_icon,
iconColor: AppColors.ratingColorYellow,
iconSize: 13.h,
text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: widget.prescriptionsResponseModel.name!,
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
],
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 24.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
],
),
),
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length,
itemBuilder: (context, index) {
return prescriptionVM.isPrescriptionsDetailsLoading
? const MoviesShimmerWidget()
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
widget.prescriptionsResponseModel.doctorImageURL!,
width: 24.h,
height: 24.h,
fit: BoxFit.fill,
).circle(100),
SizedBox(width: 8.h),
Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)),
],
),
SizedBox(height: 16.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.h,
runSpacing: 6.h,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
icon: AppAssets.doctor_calendar_icon,
iconColor: AppColors.textColor,
iconSize: 13.h,
text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: widget.prescriptionsResponseModel.clinicDescription!,
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.network(
prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!,
width: 60.h,
height: 60.h,
fit: BoxFit.fill,
).circle(100),
SizedBox(width: 8.h),
Expanded(
child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2),
),
],
).paddingSymmetrical(16.h, 0.h),
SizedBox(height: 16.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.h,
runSpacing: 6.h,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
],
).paddingSymmetrical(16.h, 0.h),
SizedBox(height: 8.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h),
SizedBox(width: 9.h),
Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)),
],
).paddingSymmetrical(16.h, 0.h),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
SizedBox(height: 16.h),
],
)
CustomButton(
icon: AppAssets.rating_icon,
iconColor: AppColors.ratingColorYellow,
iconSize: 13.h,
text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: widget.prescriptionsResponseModel.name!,
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
],
),
),
);
},
).paddingSymmetrical(24.h, 0.h),
],
);
}),
],
),
),
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length,
itemBuilder: (context, index) {
return prescriptionVM.isPrescriptionsDetailsLoading
? const MoviesShimmerWidget()
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.network(
prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!,
width: 60.h,
height: 60.h,
fit: BoxFit.fill,
).circle(100),
SizedBox(width: 8.h),
Expanded(
child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2),
),
],
).paddingSymmetrical(16.h, 0.h),
SizedBox(height: 16.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.h,
runSpacing: 6.h,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
],
),
],
).paddingSymmetrical(16.h, 0.h),
SizedBox(height: 8.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h),
SizedBox(width: 9.h),
Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)),
],
).paddingSymmetrical(16.h, 0.h),
SizedBox(height: 14.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
SizedBox(height: 14.h),
Row(
mainAxisSize: MainAxisSize.max,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h),
SizedBox(width: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.setReminder.tr(context: context).toText13(isBold: true),
"Notify me before the consumption time".toText10(color: AppColors.textColorLight),
],
),
SizedBox(width: 12.h),
Switch(
activeColor: AppColors.successColor,
value: prescriptionVM.prescriptionDetailsList[index].hasReminder!,
onChanged: (newValue) {
setState(() {
prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]);
});
},
),
],
).paddingSymmetrical(16.h, 0.h),
SizedBox(height: 14.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.checkAvailability.tr(context: context),
onPressed: () {},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
textColor: AppColors.primaryRedColor,
fontSize: 13,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
),
SizedBox(width: 16.h),
Expanded(
child: CustomButton(
text: LocaleKeys.readInstructions.tr(context: context),
onPressed: () {},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 13,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
),
],
).paddingSymmetrical(16.h, 16.h),
],
)
],
),
),
),
),
);
},
).paddingSymmetrical(24.h, 0.h),
],
);
}),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: CustomButton(
text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context),
onPressed: () {},
backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color,
borderColor: AppColors.successColor.withOpacity(0.01),
textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35),
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.prescription_refill_icon,
iconColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35),
iconSize: 20.h,
).paddingSymmetrical(24.h, 24.h),
),
],
),
);
}

@ -196,7 +196,7 @@ class _PrescriptionsListPageState extends State<PrescriptionsListPage> {
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 12,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
@ -209,7 +209,7 @@ class _PrescriptionsListPageState extends State<PrescriptionsListPage> {
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 12,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),

@ -9,7 +9,6 @@ class AppColors {
static const buttonColor = Color(0xFF6A46F5);
static const splashBgColor = Color(0xFF3C355D);
static const blackColor = Color(0xFF000000);
static const lightGray = Color(0xFFF4F5F7);
static const lightPurple = Color(0xFFB7A3E6);
static const scaffoldBgColor = Color(0xFFF8F8F8);
@ -29,18 +28,27 @@ class AppColors {
static const Color bgRedLightColor = Color(0xFFFEE9EA);
static const Color bgGreenColor = Color(0xFF18C273);
static const Color textColor = Color(0xFF2E3039);
static const Color textColorLight = Color(0xFF5E5E5E);
static const Color borderOnlyColor = Color(0xFF2E3039);
static const Color dividerColor = Color(0xFFD2D2D2);
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);
//Chips
static const Color successColor = Color(0xff18C273);
static const Color errorColor = Color(0xFFED1C2B);
static const Color alertColor = Color(0xFFD48D05);
static const Color infoColor = Color(0xFF0B85F7);
static const Color warningColor = Color(0xFFFFCC00);
static const Color greyColor = Color(0xFFEFEFF0);
static const Color successColor = Color(0xff18C273);
static const Color errorColor = Color(0xFFED1C2B);
static const Color alertColor = Color(0xFFD48D05);
static const Color infoColor = Color(0xFF0B85F7);
static const Color warningColor = Color(0xFFFFCC00);
static const Color greyColor = Color(0xFFEFEFF0);
static const Color successLightColor = Color(0xFF18C273);
static const Color errorLightColor = Color(0xFFED1C2B);

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

@ -2,24 +2,29 @@ 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/presentation/lab/search_lab_report.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}) {
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: const BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 24.h),
child: ButtonSheetContent(
title: title!,
isCloseButtonVisible: isCloseButtonVisible,
isFullScreen: isFullScreen,
child: child,
),
);
@ -32,51 +37,52 @@ 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});
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),
),
),
),
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
? Padding(
padding: EdgeInsets.symmetric(horizontal: 16), child: Utils.buildSvgWithAssets( icon: AppAssets.closeBottomNav,
width: 32,
height: 32).onPress((){
Navigator.of(context).pop();
}),
)
: SizedBox(),
SizedBox(height: 20,),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
title,
style: TextStyle(fontSize: 27.h, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 16),
// 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(),
Expanded(child: child)
],
);
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: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/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
// 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 hasError;
final String? errorMessage;
Function(CountryEnum)? onCountryChange;
SelectionTypeEnum? selectionType;
// final List<Country> countryList;
// final Function(Country)? onCountryChange;
const TextInputWidget({
TextInputWidget({
Key? key,
required this.labelText,
required this.hintText,
@ -48,15 +59,15 @@ class TextInputWidget extends StatelessWidget {
this.isCountryDropDown = false,
this.hasError = false,
this.errorMessage,
this.onCountryChange,
this.selectionType,
// this.countryList = const [],
// this.onCountryChange,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// Assuming AppColors.bgRedColor exists, otherwise using Colors.red
final errorColor = Colors.red; // Replace with AppColors.bgRedColor if available
final errorColor = AppColors.primaryRedColor;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
@ -66,34 +77,45 @@ class TextInputWidget extends StatelessWidget {
alignment: Alignment.center,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
borderRadius: isAllowRadius ? 15 : null,
borderRadius: isAllowRadius ? 12 : null,
side: isBorderAllowed ? BorderSide(color: hasError ? errorColor : const Color(0xffefefef), width: 1) : null,
),
child: Row(
textDirection: Directionality.of(context),
children: [
if (isAllowLeadingIcon && leadingIcon != null) _buildLeadingIcon(context),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabelText(),
_buildTextField(context),
],
),
),
if (isAllowLeadingIcon && leadingIcon != null && !isCountryDropDown) _buildLeadingIcon(context),
isCountryDropDown
? CustomCountryDropdown(
countryList: CountryEnum.values,
onCountryChange: onCountryChange,
isRtl: Directionality.of(context) == TextDirection.rtl,
isFromBottomSheet: isCountryDropDown,
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)
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(
errorMessage!,
style: TextStyle(
color: errorColor,
fontSize: 12,
fontSize: 12.fSize,
),
),
),
@ -103,21 +125,56 @@ class TextInputWidget extends StatelessWidget {
Widget _buildLeadingIcon(BuildContext context) {
return Container(
height: 40,
width: 40,
margin: const EdgeInsets.only(right: 10),
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(color: Color(0xFFEFEFF0), borderRadius: BorderRadius.all(Radius.circular(10))),
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 _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() {
return Text(
labelText,
style: const TextStyle(
fontSize: 12,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
color: Color(0xff898A8D),
color: AppColors.inputLabelTextColor,
letterSpacing: -0.2,
height: 18 / 12,
),
@ -137,30 +194,18 @@ class TextInputWidget extends StatelessWidget {
onChanged: onChange,
focusNode: focusNode,
autofocus: autoFocus,
style: const TextStyle(
fontSize: 14,
height: 21 / 14,
fontWeight: FontWeight.w500,
color: Color(0xff2E3039),
letterSpacing: -0.2,
),
style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2),
decoration: InputDecoration(
isDense: true,
hintText: hintText,
hintStyle: const TextStyle(
fontSize: 14,
height: 21 / 16,
fontWeight: FontWeight.w500,
color: Color(0xff898A8D),
letterSpacing: -0.2,
),
prefixIconConstraints: const BoxConstraints(minWidth: 45),
hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2),
prefixIconConstraints: BoxConstraints(minWidth: 45.h),
prefixIcon: prefix == null
? null
: Text(
"+" + prefix!,
style: const TextStyle(
fontSize: 14,
style: TextStyle(
fontSize: 14.fSize,
height: 21 / 14,
fontWeight: FontWeight.w500,
color: Color(0xff2E303A),

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