Merge branch 'master' into haroon_dev

pull/49/head
haroon amjad 1 month ago
commit 928a056cbc

@ -827,4 +827,25 @@
"selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد", "selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد",
"iAcceptThe": "أوافق على", "iAcceptThe": "أوافق على",
"personalDetailsVerification": "التحقق من التفاصيل الشخصية", "personalDetailsVerification": "التحقق من التفاصيل الشخصية",
"otpVerification": "التحقق من OTP",
"weHaveSendOTP": "لقد أرسلنا OTP إلى",
"via": "عبر",
"forRegistrationVerification": "للتحقق من التسجيل",
"didntReceiveIt": "لم تستلمه؟",
"resendOTP": "إعادة إرسال",
"resendIn": "إعادة الإرسال في",
"pleaseEnterAnationalID": "يرجى إدخال رقم الهوية الوطنية",
"pleaseEnterAFileNumber": "يرجى إدخال رقم الملف",
"pleaseEnterAValidEmail": "يرجى إدخال بريد إلكتروني صالح",
"pleaseEnterFullName": "يرجى إدخال الاسم الكامل",
"pleaseAcceptTermsConditions": "يرجى قبول الشروط والأحكام",
"pleaseEnterAValidIqamaID": "يرجى إدخال رقم إقامة صالح",
"pleaseEnterAValidNationalID": "يرجى إدخال رقم هوية وطنية صالح",
"pleaseEnterAValidDateOfBirth": "يرجى إدخال تاريخ ميلاد صالح",
"pleaseEnterAValidName": "يرجى إدخال اسم صالح",
"pleaseSelectAGender": "يرجى اختيار الجنس",
"pleaseSelectAMaritalStatus": "يرجى اختيار الحالة الاجتماعية",
"pleaseSelectACountry": "يرجى اختيار الدولة",
"pleaseEnterEmail": "يرجى إدخال البريد الإلكتروني",
"pleaseEnterAValidEmailFormat": "يرجى إدخال تنسيق بريد إلكتروني صالح"
} }

@ -822,6 +822,27 @@
"selectHospitalSubTitle": "Please select the hospital for the appointment", "selectHospitalSubTitle": "Please select the hospital for the appointment",
"news": "News", "news": "News",
"iAcceptThe": "I Accept the", "iAcceptThe": "I Accept the",
"personalDetailsVerification": "Personal Details Verification" "personalDetailsVerification": "Personal Details Verification",
"otpVerification": "OTP Verification",
"weHaveSendOTP": "We have sent you the OTP code on",
"via": "via",
"forRegistrationVerification": "for registration verification",
"didntReceiveIt": "Didn't receive it?",
"resendOTP": "Resend",
"resendIn": "resend in",
"pleaseEnterAnationalID": "Please enter a national ID",
"pleaseEnterAFileNumber": "Please enter a file number",
"pleaseEnterAValidEmail": "Please enter a valid email",
"pleaseEnterFullName": "Please enter full name",
"pleaseAcceptTermsConditions": "Please accept the terms and conditions",
"pleaseEnterAValidIqamaID": "Please enter a valid Iqama ID",
"pleaseEnterAValidNationalID": "Please enter a valid national ID",
"pleaseEnterAValidDateOfBirth": "Please enter a valid date of birth",
"pleaseEnterAValidName": "Please enter a valid name",
"pleaseSelectAGender": "Please select a gender",
"pleaseSelectAMaritalStatus": "Please select a marital status",
"pleaseSelectACountry": "Please select a country",
"pleaseEnterEmail": "Please enter email",
"pleaseEnterAValidEmailFormat": "Please enter a valid email format"
} }

@ -94,7 +94,7 @@ extension LoginTypeExtension on LoginTypeEnum {
String get displayName { String get displayName {
AppState appState = getIt.get<AppState>(); AppState appState = getIt.get<AppState>();
bool isArabic = appState.getLanguageID() == "ar"; bool isArabic = appState.getLanguageID() == 1 ? true : false;
switch (this) { switch (this) {
case LoginTypeEnum.sms: case LoginTypeEnum.sms:
return isArabic ? 'رسالة نصية' : 'SMS'; return isArabic ? 'رسالة نصية' : 'SMS';

@ -7,10 +7,9 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
class DoctorMapper{ class DoctorMapper{
static Future<RegionList> getMappedDoctor(List<DoctorList> doctorList, static Future<RegionList> getMappedDoctor(List<DoctorList> doctorList,
{bool isArabic = false}) async { {bool isArabic = false,double lat = 0.0,double long = 0.0}) async {
RegionList regionList = RegionList(); RegionList regionList = RegionList();
final lat = await Utils.getNumFromPrefs(CacheConst.userLat);
final long = await Utils.getNumFromPrefs(CacheConst.userLat);
for (var element in doctorList) { for (var element in doctorList) {
String? region = element.getRegionName(isArabic); String? region = element.getRegionName(isArabic);
@ -126,11 +125,9 @@ class DoctorMapper{
static Future<RegionList> getMappedHospitals( static Future<RegionList> getMappedHospitals(
List<HospitalsModel> hospitalList, { List<HospitalsModel> hospitalList, {
bool isArabic = false, bool isArabic = false, double lat = 0.0,double lng = 0.0
}) async { }) async {
final regionList = RegionList(); final regionList = RegionList();
final lat = await Utils.getNumFromPrefs(CacheConst.userLat);
final long = await Utils.getNumFromPrefs(CacheConst.userLat);
for (final hospital in hospitalList) { for (final hospital in hospitalList) {
final region = hospital.getRegionName(isArabic); final region = hospital.getRegionName(isArabic);
if (region == null) continue; if (region == null) continue;
@ -173,13 +170,13 @@ class DoctorMapper{
} else if (distance < regionData.hmgDistance) { } else if (distance < regionData.hmgDistance) {
regionData.hmgDistance = distance; regionData.hmgDistance = distance;
} }
} else if ( lat != 0&& } else if ( lat != 0.0&&
long != 0 && lng != 0.0 &&
hospital.latitude != null && hospital.latitude != null &&
hospital.longitude != null) { hospital.longitude != null) {
double calculatedDistance = calculateDistance( double calculatedDistance = calculateDistance(
lat.toDouble(), lat,
long.toDouble(), lng,
double.parse(hospital.latitude!), double.parse(hospital.latitude!),
double.parse(hospital.longitude!), double.parse(hospital.longitude!),
).abs(); ).abs();

@ -29,32 +29,32 @@ class ValidationUtils {
static bool isValidatedId({String? nationalId, required Function() onOkPress, CountryEnum? selectedCountry, bool? isTermsAccepted, String? dob}) { static bool isValidatedId({String? nationalId, required Function() onOkPress, CountryEnum? selectedCountry, bool? isTermsAccepted, String? dob}) {
bool isCorrectID = true; bool isCorrectID = true;
if (nationalId == null || nationalId.isEmpty) { if (nationalId == null || nationalId.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter a national ID", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAnationalID.tr(), onOkPressed: onOkPress);
isCorrectID = false; isCorrectID = false;
} }
if (nationalId != null && nationalId.isNotEmpty && selectedCountry != null) { if (nationalId != null && nationalId.isNotEmpty && selectedCountry != null) {
if (selectedCountry == CountryEnum.saudiArabia) { if (selectedCountry == CountryEnum.saudiArabia) {
if (!validateIqama(nationalId)) { if (!validateIqama(nationalId)) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid Iqama ID", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidIqamaID.tr(), onOkPressed: onOkPress);
return false; return false;
} }
} }
if (selectedCountry == CountryEnum.unitedArabEmirates) { if (selectedCountry == CountryEnum.unitedArabEmirates) {
if (!validateUaeNationalId(nationalId)) { if (!validateUaeNationalId(nationalId)) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid national ID", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidNationalID.tr(), onOkPressed: onOkPress);
return false; return false;
} }
} }
if (dob == null || dob.isEmpty) { if (dob == null || dob.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid date of birth", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidDateOfBirth.tr(), onOkPressed: onOkPress);
return false; return false;
} }
if (isTermsAccepted != null && !isTermsAccepted) { if (isTermsAccepted != null && !isTermsAccepted) {
_dialogService.showExceptionBottomSheet(message: "Please accept the terms and conditions", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseAcceptTermsConditions.tr(), onOkPressed: onOkPress);
return false; return false;
} }
} }
@ -63,7 +63,7 @@ class ValidationUtils {
static bool isValidatePhone({String? phoneNumber, required Function() onOkPress}) { static bool isValidatePhone({String? phoneNumber, required Function() onOkPress}) {
if (phoneNumber == null || phoneNumber.isEmpty) { if (phoneNumber == null || phoneNumber.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid phone number", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidPhoneNumber.tr(), onOkPressed: onOkPress);
return false; return false;
} }
return true; return true;
@ -71,7 +71,7 @@ class ValidationUtils {
static bool isValidate({String? phoneNumber, required Function() onOkPress}) { static bool isValidate({String? phoneNumber, required Function() onOkPress}) {
if (phoneNumber == null || phoneNumber.isEmpty) { if (phoneNumber == null || phoneNumber.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid phone number", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidPhoneNumber.tr(), onOkPressed: onOkPress);
return false; return false;
} }
return true; return true;
@ -104,22 +104,22 @@ class ValidationUtils {
static bool validateUaeRegistration({String? name, GenderTypeEnum? gender, NationalityCountries? country, MaritalStatusTypeEnum? maritalStatus, required Function() onOkPress}) { static bool validateUaeRegistration({String? name, GenderTypeEnum? gender, NationalityCountries? country, MaritalStatusTypeEnum? maritalStatus, required Function() onOkPress}) {
if (name == null || name.isEmpty) { if (name == null || name.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid name", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidName.tr(), onOkPressed: onOkPress);
return false; return false;
} }
if (gender == null) { if (gender == null) {
_dialogService.showExceptionBottomSheet(message: "Please select a gender", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseSelectAGender.tr(), onOkPressed: onOkPress);
return false; return false;
} }
if (maritalStatus == null) { if (maritalStatus == null) {
_dialogService.showExceptionBottomSheet(message: "Please select a marital status", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseSelectAMaritalStatus.tr(), onOkPressed: onOkPress);
return false; return false;
} }
if (country == null) { if (country == null) {
_dialogService.showExceptionBottomSheet(message: "Please select a country", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseSelectACountry.tr(), onOkPressed: onOkPress);
return false; return false;
} }
@ -128,13 +128,13 @@ class ValidationUtils {
static bool isValidateEmail({String? email, required Function() onOkPress}) { static bool isValidateEmail({String? email, required Function() onOkPress}) {
if (email == null || email.isEmpty) { if (email == null || email.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter email", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterEmail.tr(), onOkPressed: onOkPress);
return false; return false;
} }
final bool emailIsValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}$").hasMatch(email); final bool emailIsValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}$").hasMatch(email);
if (!emailIsValid) { if (!emailIsValid) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid email format", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidEmailFormat.tr(), onOkPressed: onOkPress);
return false; return false;
} }

@ -381,7 +381,7 @@ class FontUtils {
/// Get the appropriate font family for a specific language /// Get the appropriate font family for a specific language
static String getFontFamilyForLanguage(bool isArabic) { static String getFontFamilyForLanguage(bool isArabic) {
return isArabic ? 'Cairo' : 'Poppins'; return isArabic ? 'GESSTwo' : 'Poppins';
} }
} }

@ -59,7 +59,8 @@ class AuthenticationViewModel extends ChangeNotifier {
required NavigationService navigationService, required NavigationService navigationService,
required CacheService cacheService, required CacheService cacheService,
required LocalAuthService localAuthService, required LocalAuthService localAuthService,
}) : _navigationService = navigationService, })
: _navigationService = navigationService,
_dialogService = dialogService, _dialogService = dialogService,
_errorHandlerService = errorHandlerService, _errorHandlerService = errorHandlerService,
_appState = appState, _appState = appState,
@ -300,7 +301,8 @@ class AuthenticationViewModel extends ChangeNotifier {
final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
result.fold( result.fold(
(failure) async => await _errorHandlerService.handleError( (failure) async =>
await _errorHandlerService.handleError(
failure: failure, failure: failure,
onUnHandledFailure: (failure) async { onUnHandledFailure: (failure) async {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
@ -443,7 +445,8 @@ class AuthenticationViewModel extends ChangeNotifier {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
resultEither.fold( resultEither.fold(
(failure) async => await _errorHandlerService.handleError( (failure) async =>
await _errorHandlerService.handleError(
failure: failure, failure: failure,
onUnHandledFailure: (failure) async { onUnHandledFailure: (failure) async {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
@ -470,7 +473,8 @@ class AuthenticationViewModel extends ChangeNotifier {
final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: CheckActivationCodeRegisterReq.fromJson(request), activationCode: activationCode, isRegister: false); final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: CheckActivationCodeRegisterReq.fromJson(request), activationCode: activationCode, isRegister: false);
resultEither.fold( resultEither.fold(
(failure) async => await _errorHandlerService.handleError( (failure) async =>
await _errorHandlerService.handleError(
failure: failure, failure: failure,
onUnHandledFailure: (failure) async { onUnHandledFailure: (failure) async {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
@ -881,7 +885,7 @@ class AuthenticationViewModel extends ChangeNotifier {
message: apiResponse.errorMessage ?? "", message: apiResponse.errorMessage ?? "",
label: LocaleKeys.notice.tr(), label: LocaleKeys.notice.tr(),
onOkPressed: () { onOkPressed: () {
_dialogService.showPhoneNumberPickerSheet(onSMSPress: () { _dialogService.showPhoneNumberPickerSheet(label:"Where would you like to receive OTP?", message:"Please select from the below options to receive OTP.", onSMSPress: () {
checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
}, onWhatsappPress: () { }, onWhatsappPress: () {
checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);

@ -1,15 +1,21 @@
import 'dart:async'; import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
@ -53,7 +59,8 @@ class OTPWidget extends StatefulWidget {
final FocusNode? focusNode; final FocusNode? focusNode;
final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition; final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition;
final Duration pinTextAnimatedSwitcherDuration; final Duration pinTextAnimatedSwitcherDuration;
final TextDirection textDirection;
// final TextDirection textDirection;
final TextInputType keyboardType; final TextInputType keyboardType;
final EdgeInsets pinBoxOuterPadding; final EdgeInsets pinBoxOuterPadding;
@ -74,7 +81,6 @@ class OTPWidget extends StatefulWidget {
this.onTextChanged, this.onTextChanged,
this.autoFocus = false, this.autoFocus = false,
this.focusNode, this.focusNode,
this.textDirection = TextDirection.ltr,
this.keyboardType = TextInputType.number, this.keyboardType = TextInputType.number,
this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0), this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0),
this.pinBoxColor = Colors.white, this.pinBoxColor = Colors.white,
@ -182,17 +188,9 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
widget.controller!.clear(); widget.controller!.clear();
} }
// Remove focus from the input
if (focusNode.hasFocus) { if (focusNode.hasFocus) {
focusNode.unfocus(); focusNode.unfocus();
} }
// Optionally refocus after a short delay to allow user to re-enter OTP
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted && widget.autoFocus) {
FocusScope.of(context).requestFocus(focusNode);
}
});
} }
} }
@ -402,58 +400,6 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
); );
} }
// Widget _buildPinCode(int i, BuildContext context) {
// Color pinBoxColor = widget.pinBoxColor;
//
// if (widget.hasError) {
// pinBoxColor = widget.errorBorderColor;
// } else if (i < text.length) {
// pinBoxColor = AppColors.blackBgColor; // Custom color for filled boxes
// } else {
// pinBoxColor = widget.pinBoxColor;
// }
//
// // Change color to success when all fields are complete
// if (text.length == widget.maxLength) {
// pinBoxColor = AppColors.successColor;
// }
//
// EdgeInsets insets;
// if (i == 0) {
// insets = EdgeInsets.only(
// left: 0,
// top: widget.pinBoxOuterPadding.top,
// right: widget.pinBoxOuterPadding.right,
// bottom: widget.pinBoxOuterPadding.bottom,
// );
// } else if (i == strList.length - 1) {
// insets = EdgeInsets.only(
// left: widget.pinBoxOuterPadding.left,
// top: widget.pinBoxOuterPadding.top,
// right: 0,
// bottom: widget.pinBoxOuterPadding.bottom,
// );
// } else {
// insets = widget.pinBoxOuterPadding;
// }
//
// return AnimatedContainer(
// duration: const Duration(milliseconds: 200),
// curve: Curves.easeInOut,
// key: ValueKey<String>("container$i"),
// alignment: Alignment.center,
// padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0),
// margin: insets,
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: pinBoxColor,
// borderRadius: widget.pinBoxRadius,
// ),
// width: widget.pinBoxWidth,
// height: widget.pinBoxHeight,
// child: _animatedTextBox(strList[i], i),
// );
// }
Widget _animatedTextBox(String text, int i) { Widget _animatedTextBox(String text, int i) {
if (widget.pinTextAnimatedSwitcherTransition != null) { if (widget.pinTextAnimatedSwitcherTransition != null) {
return AnimatedSwitcher( return AnimatedSwitcher(
@ -586,6 +532,7 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AuthenticationViewModel authVM = context.read<AuthenticationViewModel>();
return Scaffold( return Scaffold(
backgroundColor: AppColors.scaffoldBgColor, backgroundColor: AppColors.scaffoldBgColor,
appBar: CustomAppBar( appBar: CustomAppBar(
@ -601,19 +548,22 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 40.h), SizedBox(height: 10.h),
Text( LocaleKeys.otpVerification.tr().toText24(isBold: true),
'OTP Verification', SizedBox(height: 20.h),
style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold), Wrap(
), spacing: 4.h,
SizedBox(height: 16.h), runSpacing: 8.0,
Text( children: [
'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification', LocaleKeys.weHaveSendOTP.tr().toText16(color: AppColors.inputLabelTextColor),
style: TextStyle(fontSize: 16.fSize, color: Colors.grey), _getMaskedPhoneNumber().toText16(color: AppColors.inputLabelTextColor, isBold: true),
LocaleKeys.via.tr().toText16(color: AppColors.inputLabelTextColor),
authVM.loginTypeEnum.displayName.toText16(color: AppColors.inputLabelTextColor),
LocaleKeys.forRegistrationVerification.tr().toText16(color: AppColors.inputLabelTextColor),
],
), ),
SizedBox(height: 40.h),
// OTP Input Fields using new OTP Widget SizedBox(height: 16.h),
Center( Center(
child: OTPWidget( child: OTPWidget(
maxLength: _otpLength, maxLength: _otpLength,
@ -635,38 +585,32 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
), ),
), ),
), ),
SizedBox(height: 32.h),
const SizedBox(height: 32),
// Resend OTP // Resend OTP
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text("Didn't receive it? "), LocaleKeys.didntReceiveIt.tr().toText16(color: AppColors.inputLabelTextColor),
SizedBox(width: 5.h),
if (_resendTime > 0) if (_resendTime > 0)
Builder( Builder(
// Use a Builder to easily calculate minutes and seconds inline
builder: (context) { builder: (context) {
final minutes = (_resendTime ~/ 60) final minutes = (_resendTime ~/ 60).toString().padLeft(2, '0');
.toString() final seconds = (_resendTime % 60).toString().padLeft(2, '0');
.padLeft(2, '0'); // Integer division for minutes final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds return Row(
final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds // <--- HERE IT IS children: [
return Text( LocaleKeys.resendIn.tr().toText16(color: AppColors.inputLabelTextColor),
'resend in ($minutes:$seconds). ', SizedBox(width: 2.h),
style: const TextStyle(color: Colors.grey), ' ($minutes:$seconds). '.toText16(color: AppColors.inputLabelTextColor)
],
); );
}, },
) )
else else
GestureDetector( GestureDetector(
onTap: _resendOtp, onTap: _resendOtp,
child: const Text( child: LocaleKeys.resendOTP.tr().toText16(color: AppColors.primaryRedColor),
'Resend',
style: TextStyle(
color: AppColors.primaryRedColor,
fontWeight: FontWeight.bold,
),
),
), ),
], ],
), ),
@ -678,7 +622,6 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
} }
void _verifyOtp(String otp) { void _verifyOtp(String otp) {
debugPrint('Verifying OTP: $otp');
widget.checkActivationCode(int.parse(otp)); widget.checkActivationCode(int.parse(otp));
} }
@ -689,6 +632,6 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
_isVerifying = false; _isVerifying = false;
_otpController.text = otp; _otpController.text = otp;
setState(() {}); setState(() {});
_onOtpChanged(otp); // Ensure verification and color update _onOtpChanged(otp);
} }
} }

@ -425,7 +425,10 @@ class BookAppointmentsViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) { } else if (apiResponse.messageStatus == 1) {
var projectList = apiResponse.data!; var projectList = apiResponse.data!;
hospitalList = await DoctorMapper.getMappedHospitals(projectList, hospitalList = await DoctorMapper.getMappedHospitals(projectList,
isArabic: false); isArabic: false,
lat: _appState.userLat,
lng: _appState.userLong,
);
var lat = await Utils.getNumFromPrefs(CacheConst.userLat); var lat = await Utils.getNumFromPrefs(CacheConst.userLat);
var lng = await Utils.getNumFromPrefs(CacheConst.userLong); var lng = await Utils.getNumFromPrefs(CacheConst.userLong);

@ -825,5 +825,26 @@ abstract class LocaleKeys {
static const selectHospitalSubTitle = 'selectHospitalSubTitle'; static const selectHospitalSubTitle = 'selectHospitalSubTitle';
static const iAcceptThe = 'iAcceptThe'; static const iAcceptThe = 'iAcceptThe';
static const personalDetailsVerification = 'personalDetailsVerification'; static const personalDetailsVerification = 'personalDetailsVerification';
static const otpVerification = 'otpVerification';
static const weHaveSendOTP = 'weHaveSendOTP';
static const via = 'via';
static const forRegistrationVerification = 'forRegistrationVerification';
static const didntReceiveIt = 'didntReceiveIt';
static const resendOTP = 'resendOTP';
static const resendIn = 'resendIn';
static const pleaseEnterAnationalID = 'pleaseEnterAnationalID';
static const pleaseEnterAFileNumber = 'pleaseEnterAFileNumber';
static const pleaseEnterAValidEmail = 'pleaseEnterAValidEmail';
static const pleaseEnterFullName = 'pleaseEnterFullName';
static const pleaseAcceptTermsConditions = 'pleaseAcceptTermsConditions';
static const pleaseEnterAValidIqamaID = 'pleaseEnterAValidIqamaID';
static const pleaseEnterAValidNationalID = 'pleaseEnterAValidNationalID';
static const pleaseEnterAValidDateOfBirth = 'pleaseEnterAValidDateOfBirth';
static const pleaseEnterAValidName = 'pleaseEnterAValidName';
static const pleaseSelectAGender = 'pleaseSelectAGender';
static const pleaseSelectAMaritalStatus = 'pleaseSelectAMaritalStatus';
static const pleaseSelectACountry = 'pleaseSelectACountry';
static const pleaseEnterEmail = 'pleaseEnterEmail';
static const pleaseEnterAValidEmailFormat = 'pleaseEnterAValidEmailFormat';
} }

@ -93,7 +93,7 @@ class _RegisterNew extends State<RegisterNew> {
CustomCountryDropdown( CustomCountryDropdown(
countryList: CountryEnum.values, countryList: CountryEnum.values,
onCountryChange: authVm.onCountryChange, onCountryChange: authVm.onCountryChange,
isRtl: Directionality.of(context) == TextDirection.LTR, // isRtl: Directionality.of(context) == TextDirection.LTR,
).withVerticalPadding(8.h), ).withVerticalPadding(8.h),
Divider(height: 1.h), Divider(height: 1.h),
TextInputWidget( TextInputWidget(
@ -189,7 +189,7 @@ class _RegisterNew extends State<RegisterNew> {
), ),
SizedBox(height: 25.h), SizedBox(height: 25.h),
CustomButton( CustomButton(
text: "Register", text: LocaleKeys.registernow.tr(),
icon: AppAssets.note_edit, icon: AppAssets.note_edit,
onPressed: () { onPressed: () {
// Dismiss keyboard before proceeding // Dismiss keyboard before proceeding

@ -43,6 +43,9 @@ class _RegisterNew extends State<RegisterNewStep2> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>(); AppState appState = getIt.get<AppState>();
var name = appState.getLanguageCode() == "en"
? ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}")
: ("${appState.getNHICUserData.firstNameAr!.toUpperCase()} ${appState.getNHICUserData.lastNameAr!.toUpperCase()}");
return Scaffold( return Scaffold(
backgroundColor: AppColors.bgScaffoldColor, backgroundColor: AppColors.bgScaffoldColor,
appBar: CustomAppBar( appBar: CustomAppBar(
@ -71,8 +74,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
children: [ children: [
TextInputWidget( TextInputWidget(
labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(),
hintText: hintText: authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : (name),
authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"),
controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, controller: authVM!.isUserFromUAE() ? authVM!.nameController : null,
isEnable: true, isEnable: true,
prefix: null, prefix: null,
@ -332,7 +334,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
Padding( Padding(
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: CustomButton( child: CustomButton(
text: LocaleKeys.submit, text: LocaleKeys.submit.tr(),
onPressed: () { onPressed: () {
if (ValidationUtils.isValidateEmail( if (ValidationUtils.isValidateEmail(
email: authVM!.emailController.text, email: authVM!.emailController.text,

@ -39,7 +39,12 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
alignment: context.locale.languageCode == "ar" ? Alignment.centerRight : Alignment.centerLeft, alignment: context.locale.languageCode == "ar" ? Alignment.centerRight : Alignment.centerLeft,
child: GestureDetector( child: GestureDetector(
onTap: onBackPressed, onTap: onBackPressed,
child: context.locale.languageCode == "ar"
? RotatedBox(
quarterTurns: 90,
child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h), child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h),
)
: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h),
), ),
), ),
), ),

@ -78,7 +78,6 @@ class _GenericBottomSheetState extends State<GenericBottomSheet> {
bottom: Platform.isIOS ? false : true, bottom: Platform.isIOS ? false : true,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
// Dismiss keyboard and unfocus text field
_textFieldFocusNode.unfocus(); _textFieldFocusNode.unfocus();
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
@ -130,7 +129,7 @@ class _GenericBottomSheetState extends State<GenericBottomSheet> {
else ...[ else ...[
widget.textController != null widget.textController != null
? TextInputWidget( ? TextInputWidget(
labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber, labelText: widget.isForEmail ? LocaleKeys.email.tr() : LocaleKeys.phoneNumber.tr(),
hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx",
controller: widget.textController!, controller: widget.textController!,
focusNode: _textFieldFocusNode, focusNode: _textFieldFocusNode,

@ -109,11 +109,11 @@ void showCommonBottomSheetWithoutHeight(
required Widget child, required Widget child,
required VoidCallback callBackFunc, required VoidCallback callBackFunc,
String title = "", String title = "",
bool isCloseButtonVisible = true, bool isCloseButtonVisible = true,
bool isFullScreen = true, bool isFullScreen = true,
bool isDismissible = true, bool isDismissible = true,
Widget? titleWidget,}) { Widget? titleWidget,
}) {
showModalBottomSheet<String>( showModalBottomSheet<String>(
sheetAnimationStyle: AnimationStyle( sheetAnimationStyle: AnimationStyle(
duration: Duration(milliseconds: 500), // Custom animation duration duration: Duration(milliseconds: 500), // Custom animation duration
@ -141,7 +141,7 @@ void showCommonBottomSheetWithoutHeight(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
titleWidget ?? title.toText20(weight: FontWeight.w600), titleWidget ?? Expanded(child: title.toText20(weight: FontWeight.w600)),
Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() {
Navigator.of(context).pop(); Navigator.of(context).pop();
}), }),

@ -14,7 +14,6 @@ class CustomCountryDropdown extends StatefulWidget {
final List<CountryEnum> countryList; final List<CountryEnum> countryList;
final Function(CountryEnum)? onCountryChange; final Function(CountryEnum)? onCountryChange;
final Function(String)? onPhoneNumberChanged; final Function(String)? onPhoneNumberChanged;
final bool isRtl;
final bool isFromBottomSheet; final bool isFromBottomSheet;
final bool isEnableTextField; final bool isEnableTextField;
Widget? textField; Widget? textField;
@ -24,7 +23,6 @@ class CustomCountryDropdown extends StatefulWidget {
required this.countryList, required this.countryList,
this.onCountryChange, this.onCountryChange,
this.onPhoneNumberChanged, this.onPhoneNumberChanged,
required this.isRtl,
this.isFromBottomSheet = false, this.isFromBottomSheet = false,
this.isEnableTextField = false, this.isEnableTextField = false,
this.textField, this.textField,
@ -147,11 +145,23 @@ class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
void _openDropdown() { void _openDropdown() {
if (textFocusNode.hasFocus) { if (textFocusNode.hasFocus) {
textFocusNode.unfocus(); textFocusNode.unfocus();
// Wait for keyboard to close before calculating position
Future.delayed(Duration(milliseconds: 300), () {
_showDropdown();
});
} else {
_showDropdown();
}
} }
void _showDropdown() {
AppState appState = getIt.get<AppState>(); AppState appState = getIt.get<AppState>();
RenderBox renderBox = context.findRenderObject() as RenderBox; RenderBox renderBox = context.findRenderObject() as RenderBox;
Offset offset = renderBox.localToGlobal(Offset.zero); Offset offset = renderBox.localToGlobal(Offset.zero);
bool isRtl = appState.getLanguageCode() == "ar";
double leftPosition = isRtl ? offset.dx + 8 + renderBox.size.width - (!widget.isFromBottomSheet ? renderBox.size.width : 60.h) : offset.dx;
_overlayEntry = OverlayEntry( _overlayEntry = OverlayEntry(
builder: (context) => Stack( builder: (context) => Stack(
children: [ children: [
@ -164,7 +174,7 @@ class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
), ),
Positioned( Positioned(
top: offset.dy + renderBox.size.height, top: offset.dy + renderBox.size.height,
left: widget.isRtl ? offset.dx + 6.h : offset.dx - 6.h, left: leftPosition,
width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h, width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h,
child: Material( child: Material(
child: Container( child: Container(
@ -209,6 +219,71 @@ class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
}); });
} }
// void _openDropdown() {
// if (textFocusNode.hasFocus) {
// textFocusNode.unfocus();
// }
// AppState appState = getIt.get<AppState>();
// RenderBox renderBox = context.findRenderObject() as RenderBox;
// Offset offset = renderBox.localToGlobal(Offset.zero);
//
// _overlayEntry = OverlayEntry(
// builder: (context) => Stack(
// children: [
// Positioned.fill(
// child: GestureDetector(
// onTap: _closeDropdown,
// behavior: HitTestBehavior.translucent,
// child: Container(),
// ),
// ),
// Positioned(
// top: offset.dy + renderBox.size.height,
// left: offset.dx,
// width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h,
// 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(appState.getLanguageCode() == "ar" ? country.nameArabic : 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() { void _closeDropdown() {
_overlayEntry.remove(); _overlayEntry.remove();
setState(() { setState(() {

@ -126,7 +126,6 @@ class TextInputWidget extends StatelessWidget {
? CustomCountryDropdown( ? CustomCountryDropdown(
countryList: CountryEnum.values, countryList: CountryEnum.values,
onCountryChange: onCountryChange, onCountryChange: onCountryChange,
isRtl: Directionality.of(context) == TextDirection.rtl,
isFromBottomSheet: isCountryDropDown, isFromBottomSheet: isCountryDropDown,
isEnableTextField: true, isEnableTextField: true,
onPhoneNumberChanged: onChange, onPhoneNumberChanged: onChange,
@ -191,6 +190,7 @@ class TextInputWidget extends StatelessWidget {
switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h), switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h),
language: appState.getLanguageCode()!, language: appState.getLanguageCode()!,
initialDate: DateTime.now(), initialDate: DateTime.now(),
fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins",
okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), 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)), 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) { onCalendarTypeChanged: (bool value) {

Loading…
Cancel
Save