otp screen & register Uae & resend Activation Code.

pull/35/head
aamir-csol 1 month ago
parent 21a9cc8c89
commit 4e17ff9c82

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

@ -810,5 +810,7 @@
"notNow": "Not Now",
"pendingActivation": "Pending Activation",
"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) {
// 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) {
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 {
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

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

@ -1,9 +1,11 @@
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/enums.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';
class ValidationUtils {
@ -13,12 +15,12 @@ class ValidationUtils {
log("phoneNumber: $phoneNumber");
log("nationalId: $nationalId");
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;
}
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 true;
@ -111,7 +113,6 @@ class ValidationUtils {
return false;
}
if (maritalStatus == null) {
_dialogService.showExceptionBottomSheet(message: "Please select a marital status", onOkPressed: onOkPress);
return false;

@ -524,7 +524,5 @@ class AuthenticationRepoImp implements AuthenticationRepo {
} catch (e) {
return Future.value(Left(UnknownFailure(e.toString())));
}
}
}

@ -283,7 +283,9 @@ class AuthenticationViewModel extends ChangeNotifier {
patientOutSA: false,
otpTypeEnum: otpTypeEnum,
patientId: 0,
zipCode: selectedCountrySignup.countryCode,
zipCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: selectedCountrySignup.countryCode,
calenderType: calenderType);
final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
@ -315,7 +317,14 @@ class AuthenticationViewModel extends ChangeNotifier {
if (apiResponse.data['IsAuthenticated']) {
await checkActivationCode(
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..
);
}
@ -398,7 +407,7 @@ class AuthenticationViewModel extends ChangeNotifier {
deviceToken: _appState.deviceToken,
// patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false,
patientOutSA: isForRegister
? _appState.getUserRegistrationPayload.projectOutSa == 1
? _appState.getUserRegistrationPayload.projectOutSa == true
? true
: false
: _appState.getSelectDeviceByImeiRespModelElement != null
@ -409,7 +418,9 @@ class AuthenticationViewModel extends ChangeNotifier {
loginTokenID: _appState.appAuthToken,
registeredData: isForRegister ? _appState.getUserRegistrationPayload : null,
nationIdText: nationalIdController.text,
countryCode: selectedCountrySignup.countryCode,
countryCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: selectedCountrySignup.countryCode,
loginType: loginTypeEnum.toInt)
.toJson();
LoaderBottomSheet.showLoader();
@ -459,14 +470,16 @@ class AuthenticationViewModel extends ChangeNotifier {
LoaderBottomSheet.hideLoader();
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>);
if (activation.errorCode == '699') {
// Todo: Hide Loader
LoaderBottomSheet.hideLoader();
onWrongActivationCode(activation.errorEndUserMessage);
return;
} else if (activation.messageStatus == 2) {
LoaderBottomSheet.hideLoader();
@ -592,9 +605,7 @@ class AuthenticationViewModel extends ChangeNotifier {
loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint);
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
// await getPatientDeviceData(loginTypeEnum.toInt);
await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {});
await insertPatientIMEIData(loginTypeEnum.toInt);
await getPatientDeviceData(loginTypeEnum.toInt);
} else {
// authenticated = true;
await insertPatientIMEIData(loginTypeEnum.toInt);
@ -839,7 +850,25 @@ class AuthenticationViewModel extends ChangeNotifier {
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
if (apiResponse.messageStatus == 1) {
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();
});
}
});
}

@ -56,7 +56,7 @@ class OTPWidget extends StatefulWidget {
this.maxLength = 4,
this.controller,
this.pinBoxWidth = 70.0,
this.pinBoxHeight = 70.0,
this.pinBoxHeight = 100.0,
this.pinTextStyle,
this.onDone,
this.defaultBorderColor = Colors.black,
@ -102,7 +102,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
});
widget.controller?.text = text;
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(() {
text = text.substring(0, widget.maxLength);
currentIndex = text.length;
@ -236,7 +236,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
width: 0.0,
),
);
return Container(
return SizedBox(
width: _width,
height: widget.pinBoxHeight,
child: TextField(
@ -246,10 +246,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
controller: widget.controller,
keyboardType: widget.keyboardType,
inputFormatters: widget.keyboardType == TextInputType.number ? <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly] : null,
style: TextStyle(
height: 0.1,
color: Colors.transparent,
),
style: TextStyle(height: 0.1, color: Colors.transparent),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0),
focusedErrorBorder: transparentBorder,
@ -259,10 +256,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
focusedBorder: transparentBorder,
counterText: null,
counterStyle: null,
helperStyle: TextStyle(
height: 0.0,
color: Colors.transparent,
),
helperStyle: TextStyle(height: 0.0, color: Colors.transparent),
labelStyle: TextStyle(height: 0.1),
fillColor: Colors.transparent,
border: InputBorder.none,
@ -304,26 +298,25 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
return _buildPinCode(i, context);
});
return Row(
children: pinCodes,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: pinCodes,
);
}
Widget _buildPinCode(int i, BuildContext context) {
Color pinBoxColor = widget.pinBoxColor;
Color pinBoxColor;
if (widget.hasError) {
pinBoxColor = widget.errorBorderColor;
} else if (text.length == widget.maxLength) {
// Check for completion first, before individual box logic
pinBoxColor = AppColors.successColor;
} 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;
pinBoxColor = widget.pinBoxColor; // Default white color
}
EdgeInsets insets;
@ -352,16 +345,69 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0),
margin: insets,
child: _animatedTextBox(strList[i], i),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: pinBoxColor,
borderRadius: widget.pinBoxRadius,
),
width: widget.pinBoxWidth,
height: widget.pinBoxHeight,
child: _animatedTextBox(strList[i], i),
);
}
// 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) {
if (widget.pinTextAnimatedSwitcherTransition != null) {
return AnimatedSwitcher(
@ -453,8 +499,10 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
if (_resendTime == 0) {
setState(() {
_resendTime = 120;
_isVerifying = false; // Reset verification flag
_isVerifying = false;
_isOtpComplete = false;
});
_otpController.clear();
_startResendTimer();
// autoFillOtp("1234");
widget.onResendOTPPressed(widget.phoneNumber);

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

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

@ -67,7 +67,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
children: [
TextInputWidget(
labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(),
hintText: authVM!.isUserFromUAE() ? "Enter your full name" : ("${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,
isEnable: true,
prefix: null,
@ -290,24 +290,6 @@ class _RegisterNew extends State<RegisterNewStep2> {
} else {
showModel(context: context);
}
// if (isFromDubai) {
// if (name == null) {
// AppToast.showErrorToast(message: LocaleKeys.enterFullName);
// return;
// }
// if (!name!.contains(" ")) if (selectedGenderType == null) {
// AppToast.showErrorToast(message: TranslationBase.of(context).enterFullName);
// return;
// }
// if (selectedMaritalStatusType == null) {
// AppToast.showErrorToast(message: TranslationBase.of(context).chooseMaritalStatus);
// return;
// }
// if (selectedCountry == null) {
// AppToast.showErrorToast(message: TranslationBase.of(context).chooseCountry);
// return;
// }
// }
},
),
)

@ -56,7 +56,9 @@ class _SavedLogin extends State<SavedLogin> {
onBackPressed: () {
Navigator.of(context).pop();
},
onLanguageChanged: (lang) {},
onLanguageChanged: (value) {
context.setLocale(value == 'en' ? Locale('en', 'US') : Locale('ar', 'SA'));
},
),
body: SafeArea(
child: Padding(
@ -84,7 +86,7 @@ class _SavedLogin extends State<SavedLogin> {
children: [
// 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
? 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) {
authVm.loginWithFingerPrintFace(() {});
} else {
// int? val = loginType.toInt;
authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp);
}
},
@ -167,7 +168,6 @@ class _SavedLogin extends State<SavedLogin> {
onPressed: () {
Navigator.of(context).pop();
loginType = LoginTypeEnum.sms;
int? val = loginType.toInt;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
},
backgroundColor: AppColors.primaryRedColor,
@ -190,7 +190,6 @@ class _SavedLogin extends State<SavedLogin> {
onPressed: () {
Navigator.of(context).pop();
loginType = LoginTypeEnum.whatsapp;
int? val = loginType.toInt;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
},
backgroundColor: AppColors.transparent,
@ -230,7 +229,6 @@ class _SavedLogin extends State<SavedLogin> {
authVm.loginWithFingerPrintFace(() {});
} else {
loginType = LoginTypeEnum.whatsapp;
int? val = loginType.toInt;
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';
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 {
@ -24,7 +24,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService {
});
@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) {
loggerService.errorLogs("API Exception: ${failure.message}");
} else if (failure is ServerFailure) {
@ -51,6 +51,12 @@ class ErrorHandlerServiceImp implements ErrorHandlerService {
} else {
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 {
loggerService.errorLogs("Unhandled failure type: $failure");
await _showDialog(failure, title: "Error", onOkPressed: onOkPressed);

Loading…
Cancel
Save