Merge pull request 'dev_aamir' (#35) from dev_aamir into master

Reviewed-on: #35
pull/39/head
Haroon6138 1 month ago
commit 2cc331c1c8

@ -814,5 +814,7 @@
"notNow": "ليس الآن", "notNow": "ليس الآن",
"pendingActivation": "في انتظار التنشيط", "pendingActivation": "في انتظار التنشيط",
"awaitingApproval": "انتظر القبول", "awaitingApproval": "انتظر القبول",
"ready": "جاهز" "ready": "جاهز",
"enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح",
"enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح"
} }

@ -810,6 +810,7 @@
"notNow": "Not Now", "notNow": "Not Now",
"pendingActivation": "Pending Activation", "pendingActivation": "Pending Activation",
"awaitingApproval": "Awaiting Approval", "awaitingApproval": "Awaiting Approval",
"ready": "Ready" "ready": "Ready",
"enterValidNationalId": "Please enter a valid national ID or file number",
"enterValidPhoneNumber": "Please enter a valid phone number"
} }

@ -222,7 +222,12 @@ class ApiClientImp implements ApiClient {
} }
if (parsed['ErrorType'] == 2) { if (parsed['ErrorType'] == 2) {
// todo: handle Logout // todo: handle Logout
logApiEndpointError(endPoint, "session logged out", statusCode); onFailure(
parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'],
statusCode,
failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']),
);
// logApiEndpointError(endPoint, "session logged out", statusCode);
} }
if (isAllowAny) { if (isAllowAny) {
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);

@ -726,7 +726,7 @@ const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_In
class ApiConsts { class ApiConsts {
static const maxSmallScreen = 660; static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT

@ -5,6 +5,9 @@
// unverified, // unverified,
// } // }
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
enum AuthMethodTypesEnum { enum AuthMethodTypesEnum {
sms, sms,
whatsApp, whatsApp,
@ -90,15 +93,17 @@ extension LoginTypeExtension on LoginTypeEnum {
} }
String get displayName { String get displayName {
AppState appState = getIt.get<AppState>();
bool isArabic = appState.getLanguageID() == "ar";
switch (this) { switch (this) {
case LoginTypeEnum.sms: case LoginTypeEnum.sms:
return 'SMS'; return isArabic ? 'رسالة نصية' : 'SMS';
case LoginTypeEnum.whatsapp: case LoginTypeEnum.whatsapp:
return 'WhatsApp'; return isArabic ? 'واتساب' : 'WhatsApp';
case LoginTypeEnum.face: case LoginTypeEnum.face:
return 'Face Recognition'; return isArabic ? 'القياسات الحيوية' : 'Biometric'; // Or 'بصمة الوجه'
case LoginTypeEnum.fingerprint: case LoginTypeEnum.fingerprint:
return 'Fingerprint'; return isArabic ? 'بصمة الإصبع' : 'Fingerprint';
} }
} }
@ -190,4 +195,3 @@ extension ServiceTypeEnumExt on ServiceTypeEnum {
} }
} }
} }

@ -1,8 +1,11 @@
import 'dart:developer'; import 'dart:developer';
import 'package:easy_localization/easy_localization.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/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.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/dialog_service.dart';
class ValidationUtils { class ValidationUtils {
@ -12,12 +15,12 @@ class ValidationUtils {
log("phoneNumber: $phoneNumber"); log("phoneNumber: $phoneNumber");
log("nationalId: $nationalId"); log("nationalId: $nationalId");
if (nationalId == null || nationalId.isEmpty) { if (nationalId == null || nationalId.isEmpty) {
_dialogService.showErrorBottomSheet(message: "Please enter a valid national ID or file number", onOkPressed: () {}); _dialogService.showErrorBottomSheet(message: LocaleKeys.enterValidNationalId.tr(), onOkPressed: () {});
return false; return false;
} }
if (phoneNumber == null || phoneNumber.isEmpty) { if (phoneNumber == null || phoneNumber.isEmpty) {
_dialogService.showErrorBottomSheet(message: "Please enter a valid phone number", onOkPressed: () {}); _dialogService.showErrorBottomSheet(message: LocaleKeys.enterValidPhoneNumber.tr(), onOkPressed: () {});
return false; return false;
} }
return true; return true;
@ -33,7 +36,7 @@ class ValidationUtils {
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 national ID", onOkPressed: onOkPress); _dialogService.showExceptionBottomSheet(message: "Please enter a valid Iqama ID", onOkPressed: onOkPress);
return false; return false;
} }
} }
@ -98,4 +101,43 @@ class ValidationUtils {
final regex = RegExp(r'^784\d{4}\d{7}\d{1}$'); final regex = RegExp(r'^784\d{4}\d{7}\d{1}$');
return regex.hasMatch(id); return regex.hasMatch(id);
} }
static bool validateUaeRegistration({String? name, GenderTypeEnum? gender, NationalityCountries? country, MaritalStatusTypeEnum? maritalStatus, required Function() onOkPress}) {
if (name == null || name.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid name", onOkPressed: onOkPress);
return false;
}
if (gender == null) {
_dialogService.showExceptionBottomSheet(message: "Please select a gender", onOkPressed: onOkPress);
return false;
}
if (maritalStatus == null) {
_dialogService.showExceptionBottomSheet(message: "Please select a marital status", onOkPressed: onOkPress);
return false;
}
if (country == null) {
_dialogService.showExceptionBottomSheet(message: "Please select a country", onOkPressed: onOkPress);
return false;
}
return true;
}
static bool isValidateEmail({String? email, required Function() onOkPress}) {
if (email == null || email.isEmpty) {
_dialogService.showExceptionBottomSheet(message: "Please enter email", onOkPressed: onOkPress);
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);
if (!emailIsValid) {
_dialogService.showExceptionBottomSheet(message: "Please enter a valid email format", onOkPressed: onOkPress);
return false;
}
return true;
}
} }

@ -516,7 +516,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
failure = DataParsingFailure(e.toString()); failure = DataParsingFailure(e.toString());
} }
}, },
).then( (_) { ).then((_) {
if (failure != null) return Left(failure!); if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error")); if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!); return Right(apiResponse!);
@ -524,7 +524,5 @@ class AuthenticationRepoImp implements AuthenticationRepo {
} catch (e) { } catch (e) {
return Future.value(Left(UnknownFailure(e.toString()))); return Future.value(Left(UnknownFailure(e.toString())));
} }
} }
} }

@ -108,6 +108,7 @@ class AuthenticationViewModel extends ChangeNotifier {
Future<void> clearDefaultInputValues() async { Future<void> clearDefaultInputValues() async {
nationalIdController.clear(); nationalIdController.clear();
phoneNumberController.clear(); phoneNumberController.clear();
emailController.clear();
dobController.clear(); dobController.clear();
maritalStatus = null; maritalStatus = null;
genderType = null; genderType = null;
@ -282,7 +283,9 @@ class AuthenticationViewModel extends ChangeNotifier {
patientOutSA: false, patientOutSA: false,
otpTypeEnum: otpTypeEnum, otpTypeEnum: otpTypeEnum,
patientId: 0, patientId: 0,
zipCode: selectedCountrySignup.countryCode, zipCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: selectedCountrySignup.countryCode,
calenderType: calenderType); calenderType: calenderType);
final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
@ -314,7 +317,14 @@ class AuthenticationViewModel extends ChangeNotifier {
if (apiResponse.data['IsAuthenticated']) { if (apiResponse.data['IsAuthenticated']) {
await checkActivationCode( await checkActivationCode(
otpTypeEnum: otpTypeEnum, otpTypeEnum: otpTypeEnum,
onWrongActivationCode: (String? message) {}, onWrongActivationCode: (String? message) async {
await _dialogService.showCommonBottomSheetWithoutH(
message: message ?? "",
label: LocaleKeys.notice.tr(),
onOkPressed: () {
_navigationService.pop();
});
},
activationCode: null, //todo silent login case halded on the repo itself.. activationCode: null, //todo silent login case halded on the repo itself..
); );
} }
@ -324,7 +334,8 @@ class AuthenticationViewModel extends ChangeNotifier {
); );
} }
Future<void> sendActivationCode({required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, required bool isForRegister, dynamic payload}) async { Future<void> sendActivationCode(
{required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, required bool isForRegister, dynamic payload, bool isComingFromResendOTP = false}) async {
var request = RequestUtils.getCommonRequestSendActivationCode( var request = RequestUtils.getCommonRequestSendActivationCode(
otpTypeEnum: otpTypeEnum, otpTypeEnum: otpTypeEnum,
mobileNumber: phoneNumber, mobileNumber: phoneNumber,
@ -364,7 +375,7 @@ class AuthenticationViewModel extends ChangeNotifier {
} else { } else {
if (apiResponse.data != null && apiResponse.data['isSMSSent'] == true) { if (apiResponse.data != null && apiResponse.data['isSMSSent'] == true) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber); if (!isComingFromResendOTP) navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber, isComingFromRegister: checkIsUserComingForRegister(request: payload), payload: payload);
} else { } else {
// TODO: Handle isSMSSent false // TODO: Handle isSMSSent false
// navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber); // navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber);
@ -386,8 +397,9 @@ class AuthenticationViewModel extends ChangeNotifier {
required String? activationCode, required String? activationCode,
required OTPTypeEnum otpTypeEnum, required OTPTypeEnum otpTypeEnum,
required Function(String? message) onWrongActivationCode, required Function(String? message) onWrongActivationCode,
Function()? onResendActivation,
}) async { }) async {
bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1);
final request = RequestUtils.getCommonRequestWelcome( final request = RequestUtils.getCommonRequestWelcome(
phoneNumber: phoneNumberController.text, phoneNumber: phoneNumberController.text,
@ -395,7 +407,7 @@ class AuthenticationViewModel extends ChangeNotifier {
deviceToken: _appState.deviceToken, deviceToken: _appState.deviceToken,
// patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false, // patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false,
patientOutSA: isForRegister patientOutSA: isForRegister
? _appState.getUserRegistrationPayload.projectOutSa == 1 ? _appState.getUserRegistrationPayload.projectOutSa == true
? true ? true
: false : false
: _appState.getSelectDeviceByImeiRespModelElement != null : _appState.getSelectDeviceByImeiRespModelElement != null
@ -406,7 +418,9 @@ class AuthenticationViewModel extends ChangeNotifier {
loginTokenID: _appState.appAuthToken, loginTokenID: _appState.appAuthToken,
registeredData: isForRegister ? _appState.getUserRegistrationPayload : null, registeredData: isForRegister ? _appState.getUserRegistrationPayload : null,
nationIdText: nationalIdController.text, nationIdText: nationalIdController.text,
countryCode: selectedCountrySignup.countryCode, countryCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: selectedCountrySignup.countryCode,
loginType: loginTypeEnum.toInt) loginType: loginTypeEnum.toInt)
.toJson(); .toJson();
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
@ -451,19 +465,21 @@ class AuthenticationViewModel extends ChangeNotifier {
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();
await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {}); await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {});
}, },
), (apiResponse) async { onMessageStatusFailure: (failure) async {
LoaderBottomSheet.hideLoader();
await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {});
}), (apiResponse) async {
final activation = CheckActivationCode.fromJson(apiResponse.data as Map<String, dynamic>); final activation = CheckActivationCode.fromJson(apiResponse.data as Map<String, dynamic>);
if (activation.errorCode == '699') { if (activation.errorCode == '699') {
// Todo: Hide Loader // Todo: Hide Loader
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
onWrongActivationCode(activation.errorEndUserMessage); onWrongActivationCode(activation.errorEndUserMessage);
return; return;
} else if (activation.messageStatus == 2) { } else if (activation.messageStatus == 2) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
@ -553,20 +569,27 @@ class AuthenticationViewModel extends ChangeNotifier {
_navigationService.pushAndReplace(AppRoutes.landingScreen); _navigationService.pushAndReplace(AppRoutes.landingScreen);
} }
Future<void> navigateToOTPScreen({required OTPTypeEnum otpTypeEnum, required String phoneNumber}) async { Future<void> navigateToOTPScreen({required OTPTypeEnum otpTypeEnum, required String phoneNumber, required bool isComingFromRegister, dynamic payload}) async {
_navigationService.pushToOtpScreen( _navigationService.pushToOtpScreen(
phoneNumber: phoneNumber, phoneNumber: phoneNumber,
checkActivationCode: (int activationCode) async { checkActivationCode: (int activationCode) async {
await checkActivationCode( await checkActivationCode(
activationCode: activationCode.toString(), activationCode: activationCode.toString(),
otpTypeEnum: otpTypeEnum,
onWrongActivationCode: (String? value) {
onWrongActivationCode(message: value);
},
);
},
onResendOTPPressed: (String phoneNumber) async {
await sendActivationCode(
otpTypeEnum: otpTypeEnum, otpTypeEnum: otpTypeEnum,
onWrongActivationCode: (String? value) { phoneNumber: phoneNumberController.text,
onWrongActivationCode(message: value); nationalIdOrFileNumber: nationalIdController.text,
}); isForRegister: isComingFromRegister,
isComingFromResendOTP: true,
// Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => RegisterNewStep2(null, {"nationalID": "12345678654321"}))); payload: payload);
}, },
onResendOTPPressed: (String phoneNumber) {},
); );
} }
@ -582,9 +605,7 @@ class AuthenticationViewModel extends ChangeNotifier {
loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint);
if (!_appState.isAuthenticated) { if (!_appState.isAuthenticated) {
//commenting this api to check either the same flow working or not because this api does not needed further if work fine we will remove this //commenting this api to check either the same flow working or not because this api does not needed further if work fine we will remove this
// await getPatientDeviceData(loginTypeEnum.toInt); await getPatientDeviceData(loginTypeEnum.toInt);
await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {});
await insertPatientIMEIData(loginTypeEnum.toInt);
} else { } else {
// authenticated = true; // authenticated = true;
await insertPatientIMEIData(loginTypeEnum.toInt); await insertPatientIMEIData(loginTypeEnum.toInt);
@ -649,19 +670,18 @@ class AuthenticationViewModel extends ChangeNotifier {
} }
Future<void> onRegistrationComplete() async { Future<void> onRegistrationComplete() async {
LoaderBottomSheet.showLoader();
var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus); var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus);
//
print("============= Final Payload ===============");
print(request);
print("=================== ====================");
final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request); final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request);
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
if (apiResponse.data is String) { if (apiResponse.data is String) {
//TODO: This Section Need to Be Testing.
_dialogService.showExceptionBottomSheet(message: apiResponse.data, onOkPressed: () {}, onCancelPressed: () {});
//TODO: Here We Need to Show a Dialog Of Something in the case of Fail With OK and Cancel and the Display Variable WIll be result. //TODO: Here We Need to Show a Dialog Of Something in the case of Fail With OK and Cancel and the Display Variable WIll be result.
} else { } else {
print(apiResponse.data as Map<String, dynamic>); print(apiResponse.data as Map<String, dynamic>);
if (apiResponse.data["MessageStatus"] == 1) { if (apiResponse.data["MessageStatus"] == 1) {
LoaderBottomSheet.hideLoader();
//TODO: Here We Need to Show a Dialog Of Something in the case of Success. //TODO: Here We Need to Show a Dialog Of Something in the case of Success.
await clearDefaultInputValues(); // This will Clear All Default Values Of User. await clearDefaultInputValues(); // This will Clear All Default Values Of User.
_navigationService.pushAndReplace(AppRoutes.loginScreen); _navigationService.pushAndReplace(AppRoutes.loginScreen);
@ -722,8 +742,6 @@ class AuthenticationViewModel extends ChangeNotifier {
} else { } else {
isOutSideSa = false; isOutSideSa = false;
} }
print(isOutSideSa);
return isOutSideSa; return isOutSideSa;
} }
@ -832,7 +850,25 @@ class AuthenticationViewModel extends ChangeNotifier {
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
if (apiResponse.messageStatus == 1) { if (apiResponse.messageStatus == 1) {
dynamic deviceInfo = apiResponse.data['List_MobileLoginInfo']; dynamic deviceInfo = apiResponse.data['List_MobileLoginInfo'];
getDeviceLastLogin = deviceInfo['LoginType']; getDeviceLastLogin = deviceInfo.first['LoginType'];
await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {});
await insertPatientIMEIData(loginTypeEnum.toInt);
}
if (apiResponse.messageStatus == 2) {
LoaderBottomSheet.hideLoader();
await _dialogService.showCommonBottomSheetWithoutH(
message: apiResponse.errorMessage ?? "",
label: LocaleKeys.notice.tr(),
onOkPressed: () {
_dialogService.showPhoneNumberPickerSheet(onSMSPress: () {
checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
}, onWhatsappPress: () {
checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
});
},
onCancelPressed: () {
_navigationService.pop();
});
} }
}); });
} }

@ -6,6 +6,7 @@ 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: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/widget_extensions.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';
@ -50,7 +51,7 @@ class OTPWidget extends StatefulWidget {
final TextInputType keyboardType; final TextInputType keyboardType;
final EdgeInsets pinBoxOuterPadding; final EdgeInsets pinBoxOuterPadding;
OTPWidget({ const OTPWidget({
Key? key, Key? key,
this.maxLength = 4, this.maxLength = 4,
this.controller, this.controller,
@ -101,7 +102,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
}); });
widget.controller?.text = text; widget.controller?.text = text;
widget.controller?.selection = TextSelection.collapsed(offset: text.length); widget.controller?.selection = TextSelection.collapsed(offset: text.length);
} else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.length > 0 && text.length > widget.maxLength) { } else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.isNotEmpty && text.length > widget.maxLength) {
setState(() { setState(() {
text = text.substring(0, widget.maxLength); text = text.substring(0, widget.maxLength);
currentIndex = text.length; currentIndex = text.length;
@ -127,7 +128,9 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
_highlightAnimationController = AnimationController(vsync: this); _highlightAnimationController = AnimationController(vsync: this);
_initTextController(); _initTextController();
_calculateStrList(); _calculateStrList();
widget.controller!.addListener(_controllerListener); if (widget.controller != null) {
widget.controller!.addListener(_controllerListener);
}
focusNode.addListener(_focusListener); focusNode.addListener(_focusListener);
} }
@ -190,7 +193,9 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
focusNode.removeListener(_focusListener); focusNode.removeListener(_focusListener);
} }
_highlightAnimationController.dispose(); _highlightAnimationController.dispose();
widget.controller?.removeListener(_controllerListener); if (widget.controller != null) {
widget.controller!.removeListener(_controllerListener);
}
super.dispose(); super.dispose();
} }
@ -241,12 +246,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
controller: widget.controller, controller: widget.controller,
keyboardType: widget.keyboardType, keyboardType: widget.keyboardType,
inputFormatters: widget.keyboardType == TextInputType.number ? <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly] : null, inputFormatters: widget.keyboardType == TextInputType.number ? <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly] : null,
// Enable SMS autofill style: TextStyle(height: 0.1, color: Colors.transparent),
autofillHints: const [AutofillHints.oneTimeCode],
style: TextStyle(
height: 0.1,
color: Colors.transparent,
),
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: EdgeInsets.all(0), contentPadding: EdgeInsets.all(0),
focusedErrorBorder: transparentBorder, focusedErrorBorder: transparentBorder,
@ -256,10 +256,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
focusedBorder: transparentBorder, focusedBorder: transparentBorder,
counterText: null, counterText: null,
counterStyle: null, counterStyle: null,
helperStyle: TextStyle( helperStyle: TextStyle(height: 0.0, color: Colors.transparent),
height: 0.0,
color: Colors.transparent,
),
labelStyle: TextStyle(height: 0.1), labelStyle: TextStyle(height: 0.1),
fillColor: Colors.transparent, fillColor: Colors.transparent,
border: InputBorder.none, border: InputBorder.none,
@ -307,25 +304,19 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
); );
} }
Widget _buildPinCode(int i, BuildContext context) { Widget _buildPinCode(int i, BuildContext context) {
Color borderColor;
Color pinBoxColor; Color pinBoxColor;
// Determine if OTP is complete
bool isComplete = text.length == widget.maxLength;
if (widget.hasError) { if (widget.hasError) {
borderColor = widget.errorBorderColor; pinBoxColor = widget.errorBorderColor;
pinBoxColor = widget.pinBoxColor; } else if (text.length == widget.maxLength) {
} else if (isComplete) { // Check for completion first, before individual box logic
borderColor = Colors.transparent;
pinBoxColor = AppColors.successColor; pinBoxColor = AppColors.successColor;
} else if (i < text.length) { } else if (i < text.length) {
borderColor = Colors.transparent; pinBoxColor = AppColors.blackBgColor; // Custom color for filled boxes
pinBoxColor = AppColors.blackBgColor;
} else { } else {
borderColor = Colors.transparent; pinBoxColor = widget.pinBoxColor; // Default white color
pinBoxColor = widget.pinBoxColor;
} }
EdgeInsets insets; EdgeInsets insets;
@ -346,18 +337,17 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
} else { } else {
insets = widget.pinBoxOuterPadding; insets = widget.pinBoxOuterPadding;
} }
return Container(
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
key: ValueKey<String>("container$i"), key: ValueKey<String>("container$i"),
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0), padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0),
margin: insets, margin: insets,
decoration: BoxDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
border: Border.all(
color: borderColor,
width: widget.pinBoxBorderWidth,
),
color: pinBoxColor, color: pinBoxColor,
borderRadius: BorderRadius.circular(widget.pinBoxRadius), borderRadius: widget.pinBoxRadius,
), ),
width: widget.pinBoxWidth, width: widget.pinBoxWidth,
height: widget.pinBoxHeight, height: widget.pinBoxHeight,
@ -365,6 +355,59 @@ 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(
@ -407,10 +450,12 @@ class OTPVerificationScreen extends StatefulWidget {
class _OTPVerificationScreenState extends State<OTPVerificationScreen> { class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
final int _otpLength = 4; final int _otpLength = 4;
late TextEditingController _otpController; late final TextEditingController _otpController;
Timer? _resendTimer; Timer? _resendTimer;
int _resendTime = 60; int _resendTime = 120;
bool _isOtpComplete = false;
bool _isVerifying = false; // Flag to prevent multiple verification calls
@override @override
void initState() { void initState() {
@ -437,30 +482,29 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
} }
void _onOtpChanged(String value) { void _onOtpChanged(String value) {
// Handle clipboard paste or programmatic input setState(() {
if (value.length >= 4) { _isOtpComplete = value.length == _otpLength;
_onOtpCompleted(value); });
// String? otp = _extractOtpFromText(value);
// if (otp != null) {
// autoFillOtp(otp);
// return;
// }
}
// The OTPWidget will automatically call onDone when complete
// This method can be used for any additional logic on text change
}
void _onOtpCompleted(String otp) { if (_isOtpComplete && !_isVerifying) {
debugPrint('OTP Completed: $otp'); _isVerifying = true;
widget.checkActivationCode(int.parse(otp)); _verifyOtp(value);
} else if (!_isOtpComplete) {
// Reset the flag when OTP is incomplete (user is editing)
_isVerifying = false;
}
} }
void _resendOtp() { void _resendOtp() {
if (_resendTime == 0) { if (_resendTime == 0) {
setState(() => _resendTime = 60); setState(() {
_resendTime = 120;
_isVerifying = false;
_isOtpComplete = false;
});
_otpController.clear();
_startResendTimer(); _startResendTimer();
autoFillOtp("1234"); // autoFillOtp("1234");
widget.onResendOTPPressed(widget.phoneNumber); widget.onResendOTPPressed(widget.phoneNumber);
} }
} }
@ -470,68 +514,6 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone; return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone;
} }
/// Extract OTP from text using multiple patterns
String? _extractOtpFromText(String text) {
// Pattern 1: Find 4-6 consecutive digits
RegExp digitPattern = RegExp(r'\b\d{4,6}\b');
Match? match = digitPattern.firstMatch(text);
if (match != null) {
String digits = match.group(0)!;
if (digits.length >= _otpLength) {
return digits.substring(0, _otpLength);
}
}
// Pattern 2: Find digits separated by spaces or special characters
String cleanedText = text.replaceAll(RegExp(r'[^\d]'), '');
if (cleanedText.length >= _otpLength) {
return cleanedText.substring(0, _otpLength);
}
return null;
}
/// Paste OTP from clipboard
Future<void> _pasteFromClipboard() async {
try {
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null) {
String clipboardText = data.text!;
String? otp = _extractOtpFromText(clipboardText);
if (otp != null) {
autoFillOtp(otp);
// Show feedback to user
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('OTP pasted: $otp'),
duration: const Duration(seconds: 2),
backgroundColor: AppColors.successColor,
),
);
} else {
// Show error if no valid OTP found
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No valid OTP found in clipboard'),
duration: Duration(seconds: 2),
),
);
}
}
} catch (e) {
debugPrint('Error pasting from clipboard: $e');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to paste from clipboard'),
duration: Duration(seconds: 2),
),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -561,35 +543,30 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
), ),
SizedBox(height: 40.h), SizedBox(height: 40.h),
// OTP Input Fields using new OTPWidget // OTP Input Fields using new OTP Widget
Center( Center(
child: AutofillGroup( child: OTPWidget(
child: OTPWidget( maxLength: _otpLength,
maxLength: _otpLength, controller: _otpController,
controller: _otpController, pinBoxWidth: 70.h,
pinBoxWidth: 75.h, pinBoxHeight: 100,
pinBoxHeight: 100.h, pinBoxRadius: 16,
autoFocus: true, pinBoxBorderWidth: 0,
pinBoxRadius: 16, pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h),
pinBoxBorderWidth: 0, defaultBorderColor: Colors.transparent,
pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h), textBorderColor: Colors.transparent,
defaultBorderColor: Colors.transparent, pinBoxColor: AppColors.whiteColor,
textBorderColor: Colors.transparent, autoFocus: true,
errorBorderColor: AppColors.primaryRedColor, onTextChanged: _onOtpChanged,
pinBoxColor: AppColors.whiteColor, pinTextStyle: TextStyle(
pinTextStyle: TextStyle( fontSize: 40.fSize,
fontSize: 50.fSize, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, color: AppColors.whiteColor,
color: AppColors.whiteColor,
),
onTextChanged: _onOtpChanged,
onDone: _onOtpCompleted,
), ),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 32),
// Resend OTP // Resend OTP
Row( Row(
@ -597,9 +574,18 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
children: [ children: [
const Text("Didn't receive it? "), const Text("Didn't receive it? "),
if (_resendTime > 0) if (_resendTime > 0)
Text( Builder(
'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ', // Use a Builder to easily calculate minutes and seconds inline
style: const TextStyle(color: Colors.grey), builder: (context) {
final minutes = (_resendTime ~/ 60)
.toString()
.padLeft(2, '0'); // Integer division for minutes final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds
final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds // <--- HERE IT IS
return Text(
'resend in ($minutes:$seconds). ',
style: const TextStyle(color: Colors.grey),
);
},
) )
else else
GestureDetector( GestureDetector(
@ -621,84 +607,16 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
); );
} }
void _verifyOtp(String otp) {
debugPrint('Verifying OTP: $otp');
widget.checkActivationCode(int.parse(otp));
}
/// Auto fill OTP into text fields /// Auto fill OTP into text fields
void autoFillOtp(String otp) { void autoFillOtp(String otp) {
if (!mounted) return; if (!mounted) return;
if (otp.length != _otpLength) return; if (otp.length != _otpLength) return;
_isVerifying = false;
try { _otpController.text = otp;
// Clear any existing text first
_otpController.clear();
// Use WidgetsBinding to ensure the widget tree is ready
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
try {
// Set the text first
_otpController.text = otp;
// Use a longer delay for iOS and add validation
Future.delayed(const Duration(milliseconds: 300), () {
if (!mounted) return;
try {
// Only attempt to set selection if conditions are met
if (_otpController.text == otp &&
_otpController.text.length == _otpLength &&
_otpController.text.length <= _otpController.text.length) {
final newSelection = TextSelection.fromPosition(
TextPosition(offset: _otpController.text.length),
);
// Validate selection before setting
if (newSelection.baseOffset <= _otpController.text.length &&
newSelection.extentOffset <= _otpController.text.length) {
_otpController.selection = newSelection;
}
}
} catch (selectionError) {
// Silently fail on selection - text is already set correctly
debugPrint('Selection error (non-critical): $selectionError');
}
});
} catch (textError) {
debugPrint('Error setting OTP text: $textError');
}
});
} catch (e) {
debugPrint('Error in autoFillOtp: $e');
}
}
/// Clear OTP fields
void clearOtp() {
_otpController.clear();
}
/// Get current OTP value
String getCurrentOtp() {
return _otpController.text;
}
/// Check if OTP is complete
bool isOtpComplete() {
return _otpController.text.length == _otpLength;
}
/// Simulate SMS received with OTP (for testing purposes)
void simulateSMSReceived(String otp) {
if (otp.length == _otpLength && RegExp(r'^\d+$').hasMatch(otp)) {
autoFillOtp(otp);
// Show a brief indicator that SMS was detected
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('OTP detected from SMS: $otp'),
duration: const Duration(seconds: 2),
backgroundColor: AppColors.successColor,
),
);
}
} }
} }

@ -813,5 +813,7 @@ abstract class LocaleKeys {
static const pendingActivation = 'pendingActivation'; static const pendingActivation = 'pendingActivation';
static const awaitingApproval = 'awaitingApproval'; static const awaitingApproval = 'awaitingApproval';
static const ready = 'ready'; static const ready = 'ready';
static const enterValidNationalId = 'enterValidNationalId';
static const enterValidPhoneNumber = 'enterValidPhoneNumber';
} }

@ -187,3 +187,4 @@ class MyApp extends StatelessWidget {
); );
} }
} }
// flutter pub run easy_localization:generate -S assets/langs -f keys -o locale_keys.g.dart

@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/common_models/nationality_country_model
import 'package:hmg_patient_app_new/core/dependencies.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/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/core/utils/validation_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
@ -66,7 +67,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: authVM!.isUserFromUAE() ? "" : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), hintText: 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,
@ -275,26 +276,20 @@ class _RegisterNew extends State<RegisterNewStep2> {
icon: AppAssets.confirm, icon: AppAssets.confirm,
iconColor: AppColors.textGreenColor, iconColor: AppColors.textGreenColor,
onPressed: () { onPressed: () {
// if (isFromDubai) { if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) {
// if (name == null) { if (ValidationUtils.validateUaeRegistration(
// AppToast.showErrorToast(message: LocaleKeys.enterFullName); name: authVM!.nameController.text,
// return; gender: authVM!.genderType,
// } country: authVM!.pickedCountryByUAEUser,
// if (!name!.contains(" ")) if (selectedGenderType == null) { maritalStatus: authVM!.maritalStatus,
// AppToast.showErrorToast(message: TranslationBase.of(context).enterFullName); onOkPress: () {
// return; Navigator.of(context).pop();
// } })) {
// if (selectedMaritalStatusType == null) { showModel(context: context);
// AppToast.showErrorToast(message: TranslationBase.of(context).chooseMaritalStatus); }
// return; } else {
// } showModel(context: context);
// if (selectedCountry == null) { }
// AppToast.showErrorToast(message: TranslationBase.of(context).chooseCountry);
// return;
// }
// }
showModel(context: context);
}, },
), ),
) )
@ -324,7 +319,13 @@ class _RegisterNew extends State<RegisterNewStep2> {
child: CustomButton( child: CustomButton(
text: LocaleKeys.submit, text: LocaleKeys.submit,
onPressed: () { onPressed: () {
authVM!.onRegistrationComplete(); if (ValidationUtils.isValidateEmail(
email: authVM!.emailController.text,
onOkPress: () {
Navigator.of(context).pop();
})) {
authVM!.onRegistrationComplete();
}
}, },
backgroundColor: AppColors.bgGreenColor, backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor,

@ -56,7 +56,9 @@ class _SavedLogin extends State<SavedLogin> {
onBackPressed: () { onBackPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
onLanguageChanged: (lang) {}, onLanguageChanged: (value) {
context.setLocale(value == 'en' ? Locale('en', 'US') : Locale('ar', 'SA'));
},
), ),
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
@ -84,7 +86,7 @@ class _SavedLogin extends State<SavedLogin> {
children: [ children: [
// Last login info // Last login info
("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}").toText14(isBold: true, color: AppColors.greyTextColor), ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}").toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1),
(appState.getSelectDeviceByImeiRespModelElement!.createdOn != null (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null
? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm")
: '--') : '--')
@ -103,7 +105,6 @@ class _SavedLogin extends State<SavedLogin> {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {}); authVm.loginWithFingerPrintFace(() {});
} else { } else {
// int? val = loginType.toInt;
authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp); authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp);
} }
}, },
@ -167,7 +168,6 @@ class _SavedLogin extends State<SavedLogin> {
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
loginType = LoginTypeEnum.sms; loginType = LoginTypeEnum.sms;
int? val = loginType.toInt;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
}, },
backgroundColor: AppColors.primaryRedColor, backgroundColor: AppColors.primaryRedColor,
@ -190,7 +190,6 @@ class _SavedLogin extends State<SavedLogin> {
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
loginType = LoginTypeEnum.whatsapp; loginType = LoginTypeEnum.whatsapp;
int? val = loginType.toInt;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
}, },
backgroundColor: AppColors.transparent, backgroundColor: AppColors.transparent,
@ -230,7 +229,6 @@ class _SavedLogin extends State<SavedLogin> {
authVm.loginWithFingerPrintFace(() {}); authVm.loginWithFingerPrintFace(() {});
} else { } else {
loginType = LoginTypeEnum.whatsapp; loginType = LoginTypeEnum.whatsapp;
int? val = loginType.toInt;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
} }
}, },

@ -9,7 +9,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';
abstract class ErrorHandlerService { abstract class ErrorHandlerService {
Future<void> handleError({required Failure failure, Function() onOkPressed, Function(Failure)? onUnHandledFailure}); Future<void> handleError({required Failure failure, Function() onOkPressed, Function(Failure)? onUnHandledFailure, Function(Failure)? onMessageStatusFailure});
} }
class ErrorHandlerServiceImp implements ErrorHandlerService { class ErrorHandlerServiceImp implements ErrorHandlerService {
@ -24,7 +24,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService {
}); });
@override @override
Future<void> handleError({required Failure failure, Function()? onOkPressed, Function(Failure)? onUnHandledFailure}) async { Future<void> handleError({required Failure failure, Function()? onOkPressed, Function(Failure)? onUnHandledFailure, Function(Failure)? onMessageStatusFailure}) async {
if (failure is APIException) { if (failure is APIException) {
loggerService.errorLogs("API Exception: ${failure.message}"); loggerService.errorLogs("API Exception: ${failure.message}");
} else if (failure is ServerFailure) { } else if (failure is ServerFailure) {
@ -51,6 +51,12 @@ class ErrorHandlerServiceImp implements ErrorHandlerService {
} else { } else {
await _showDialog(failure, title: "Error", onOkPressed: onOkPressed); await _showDialog(failure, title: "Error", onOkPressed: onOkPressed);
} }
} else if (failure is MessageStatusFailure) {
if (onMessageStatusFailure != null) {
onMessageStatusFailure(failure);
} else {
await _showDialog(failure, title: "Error", onOkPressed: onOkPressed);
}
} else { } else {
loggerService.errorLogs("Unhandled failure type: $failure"); loggerService.errorLogs("Unhandled failure type: $failure");
await _showDialog(failure, title: "Error", onOkPressed: onOkPressed); await _showDialog(failure, title: "Error", onOkPressed: onOkPressed);

@ -152,6 +152,7 @@ class _GenericBottomSheetState extends State<GenericBottomSheet> {
prefix: widget.isForEmail ? null : widget.countryCode, prefix: widget.isForEmail ? null : widget.countryCode,
isBorderAllowed: false, isBorderAllowed: false,
isAllowLeadingIcon: true, isAllowLeadingIcon: true,
fontSize: 12.h,
isCountryDropDown: widget.isEnableCountryDropdown, isCountryDropDown: widget.isEnableCountryDropdown,
leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone,
) )

@ -69,16 +69,21 @@ class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
child: Row( child: Row(
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
if (_isDropdownOpen) { if (_isDropdownOpen) {
_closeDropdown(); _closeDropdown();
} else { } else {
_openDropdown(); _openDropdown();
} }
}, },
child: Utils.buildSvgWithAssets(icon: selectedCountry != null ? selectedCountry!.iconPath : AppAssets.ksa, width: 40.h, height: 40.h)), child: Row(
SizedBox(width: 8.h), children: [
Utils.buildSvgWithAssets(icon: _isDropdownOpen ? AppAssets.dropdow_icon : AppAssets.dropdow_icon), 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), SizedBox(width: 4.h),
if (widget.isFromBottomSheet) if (widget.isFromBottomSheet)
GestureDetector( GestureDetector(
@ -100,19 +105,23 @@ class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
], ],
), ),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
selectedCountry!.countryCode, selectedCountry!.countryCode,
style: TextStyle(fontSize: 12.fSize, height: 21 / 18, fontWeight: FontWeight.w600, letterSpacing: -0.2), style: TextStyle(fontSize: 12.fSize, height: 23 / 18, fontWeight: FontWeight.w600, letterSpacing: -1),
), ),
SizedBox(width: 4.h), SizedBox(width: 4.h),
if (widget.isEnableTextField) if (widget.isEnableTextField)
SizedBox( SizedBox(
height: 18, height: 20,
width: 200, width: 200,
// color: Colors.red,
child: TextField( child: TextField(
focusNode: textFocusNode, focusNode: textFocusNode,
decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none), style: TextStyle(fontSize: 12.fSize, height: 23 / 18, fontWeight: FontWeight.w600, letterSpacing: -1),
decoration: InputDecoration(hintText: "", isDense: false, border: InputBorder.none),
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
onChanged: widget.onPhoneNumberChanged), onChanged: widget.onPhoneNumberChanged),
), ),
@ -146,7 +155,6 @@ class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
_overlayEntry = OverlayEntry( _overlayEntry = OverlayEntry(
builder: (context) => Stack( builder: (context) => Stack(
children: [ children: [
// Dismiss dropdown when tapping outside
Positioned.fill( Positioned.fill(
child: GestureDetector( child: GestureDetector(
onTap: _closeDropdown, onTap: _closeDropdown,
@ -157,7 +165,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: widget.isRtl ? offset.dx + 6.h : offset.dx - 6.h,
width: renderBox.size.width, width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h,
child: Material( child: Material(
child: Container( child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12),
@ -165,27 +173,26 @@ class _CustomCountryDropdownState extends State<CustomCountryDropdown> {
children: widget.countryList children: widget.countryList
.map( .map(
(country) => GestureDetector( (country) => GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
selectedCountry = country; selectedCountry = country;
}); });
widget.onCountryChange?.call(country); widget.onCountryChange?.call(country);
_closeDropdown(); _closeDropdown();
}, },
child: Container( child: Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h),
child: Row( child: Row(
children: [ children: [
Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h), Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h),
if (!widget.isFromBottomSheet) SizedBox(width: 12.h), if (!widget.isFromBottomSheet) SizedBox(width: 12.h),
if (!widget.isFromBottomSheet) if (!widget.isFromBottomSheet)
Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName, Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName,
style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)), style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)),
], ],
), ),
), )),
),
) )
.toList(), .toList(),
), ),

@ -232,7 +232,7 @@ class TextInputWidget extends StatelessWidget {
focusNode: focusNode ?? _focusNode, focusNode: focusNode ?? _focusNode,
autofocus: autoFocus, autofocus: autoFocus,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
cursorHeight: isWalletAmountInput! ? 40.h : 18.h, cursorHeight: isWalletAmountInput! ? 40.h : 20.h,
onTapOutside: (event) { onTapOutside: (event) {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
}, },
@ -240,27 +240,9 @@ class TextInputWidget extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
hintText: hintText, hintText: hintText,
hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2), hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -1),
prefixIconConstraints: BoxConstraints(minWidth: 45.h), prefixIconConstraints: BoxConstraints(minWidth: 30.h),
suffixIconConstraints: BoxConstraints(maxHeight: 20.h), prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500),
prefixIcon: prefix == null
? null
: Text(
"+" + prefix!,
style: TextStyle(
fontSize: 14.fSize,
height: 21 / 14,
fontWeight: FontWeight.w500,
color: Color(0xff2E303A),
letterSpacing: -0.2,
),
),
suffixIcon: suffix != null
? Padding(
padding: EdgeInsets.only(right: 0.h),
child: suffix,
)
: null,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
border: InputBorder.none, border: InputBorder.none,
focusedBorder: InputBorder.none, focusedBorder: InputBorder.none,

Loading…
Cancel
Save