other country login done.

dev_sultan
Sultan khan 18 hours ago
parent 80adf1bd2f
commit 4f303d8496

@ -814,6 +814,7 @@
"ready": "جاهز",
"enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح",
"enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح",
"cannotEnterSaudiOrUAENumber": "لا يمكنك إدخال أرقام هواتف السعودية (00966) أو الإمارات (00971) عند اختيار دولة 'أخرى'",
"medicalCentersWithCount": "{count} مراكز طبية",
"medicalCenters": "مراكز طبية",
"hospitalsWithCount": "{count} مستشفيات",

@ -804,6 +804,7 @@
"awaitingApproval": "Awaiting Approval",
"enterValidNationalId": "Please enter a valid national ID or file number",
"enterValidPhoneNumber": "Please enter a valid phone number",
"cannotEnterSaudiOrUAENumber": "You cannot enter Saudi Arabia (00966) or UAE (00971) phone numbers when 'Others' country is selected",
"ready": "Ready",
"medicalCentersWithCount": "{count} Medical Centers",
"medicalCenters": " Medical Centers",

@ -129,8 +129,7 @@ class ApiClientImp implements ApiClient {
} else {}
if (body.containsKey('isDentalAllowedBackend')) {
body['isDentalAllowedBackend'] =
body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND;
body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND;
}
if (!body.containsKey('IsPublicRequest')) {
@ -194,6 +193,11 @@ class ApiClientImp implements ApiClient {
body['TokenID'] = "@dm!n";
}
if (url == 'https://uat.hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo') {
url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo";
body['TokenID'] = "@dm!n";
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 4773715;
// body['PatientTypeID'] = 1;
@ -207,7 +211,7 @@ class ApiClientImp implements ApiClient {
// body['PatientID'] = 809289;
}
if(!url.contains("/paymentApi")) {
if (!url.contains("/paymentApi")) {
body['IsNewFlutterApp'] = true;
}
@ -261,10 +265,9 @@ class ApiClientImp implements ApiClient {
ApiClient._navigationService.pushAndRemoveUntil(
CustomPageRoute(
page: AppUpdatePage(
appUpdateMessage: parsed['ErrorEndUserMessage'],
)),
ModalRoute.withName("/appUpdate")
);
appUpdateMessage: parsed['ErrorEndUserMessage'],
)),
ModalRoute.withName("/appUpdate"));
logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode);
}
if (parsed['ErrorType'] == 2) {
@ -272,24 +275,19 @@ class ApiClientImp implements ApiClient {
onFailure(
parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'],
statusCode,
failureType:
UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url),
failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url),
);
// logApiEndpointError(endPoint, "session logged out", statusCode);
}
if (isAllowAny) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else if (parsed['IsAuthenticated'] == null) {
if (parsed['isSMSSent'] == true) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else if (parsed['MessageStatus'] == 1) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else if (parsed['Result'] == 'OK') {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else {
onFailure(
parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'],
@ -299,19 +297,16 @@ class ApiClientImp implements ApiClient {
logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode);
}
} else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else if (parsed['IsAuthenticated'] == false) {
onFailure(
"User is not Authenticated",
statusCode,
failureType:
UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url),
failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url),
);
} else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) {
if (parsed['SameClinicApptList'] != null) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else {
if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) {
if (parsed['ErrorSearchMsg'] == null) {
@ -340,8 +335,7 @@ class ApiClientImp implements ApiClient {
}
} else {
if (parsed['SameClinicApptList'] != null) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else {
if (parsed['message'] != null) {
onFailure(

@ -112,10 +112,13 @@ class ApiConsts {
static final String selectDeviceImei = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI';
static final String checkPatientAuth = 'Services/Authentication.svc/REST/CheckPatientAuthentication';
static final String checkPatientAuthOthers = 'Services/Authentication.svc/REST/CheckPatientAuthentication_Others';
static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType';
static final String sendActivationCodeOthers = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType_Others';
static final String sendActivationCodeRegister = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationTypeForRegistration';
static final String checkActivationCode = 'Services/Authentication.svc/REST/CheckActivationCode';
static final String checkActivationCodeOthers = 'Services/Authentication.svc/REST/CheckActivationCode_Others';
static final String checkActivationCodeRegister = 'Services/Authentication.svc/REST/CheckActivationCodeForRegistration';
static final String checkUsageAgreement = 'Services/Patients.svc/REST/CheckForUsageAgreement';
static final String getUserAgreementContent = 'Services/Patients.svc/REST/GetUsageAgreementText';
@ -124,6 +127,7 @@ class ApiConsts {
static final String checkUserStatus = 'Services/NHIC.svc/REST/GetPatientInfo';
static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI';
static final String insertPatientDeviceIMEIDataOthers = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI_Others';
static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo';
static final String getPatientMobileData = '/Services/Authentication.svc/REST/GetMobileLoginInfo';
static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege';

@ -16,6 +16,7 @@ class AppAssets {
static const String call = '$svgBasePath/call.svg';
static const String email = '$svgBasePath/email.svg';
static const String globe = '$svgBasePath/globe.svg';
static const String globeOther = '$svgBasePath/globe_other.svg';
static const String cancel = '$svgBasePath/cancel.svg';
static const String bell = '$svgBasePath/bell.svg';
static const String login1 = '$svgBasePath/login1.svg';

@ -116,6 +116,7 @@ class AppState {
set setIsAuthenticated(v) => isAuthenticated = v;
String deviceTypeID = "";
set setDeviceTypeID(v) => deviceTypeID = v;

@ -12,7 +12,7 @@ enum AuthMethodTypesEnum { sms, whatsApp, fingerPrint, faceID, moreOptions }
enum ViewStateEnum { hide, idle, busy, error, busyLocal, errorLocal }
enum CountryEnum { saudiArabia, unitedArabEmirates }
enum CountryEnum { saudiArabia, unitedArabEmirates, others }
enum CalenderEnum { gregorian, hijri }

@ -27,14 +27,21 @@ class RequestUtils {
}) {
bool fileNo = false;
if (nationId.isNotEmpty) {
fileNo = nationId.length < 10;
final numericRegex = RegExp(r'^[0-9]+$');
fileNo = nationId.length < 10 && numericRegex.hasMatch(nationId);
//fileNo = nationId.length < 10 && nationId.isNumericOnly() ;
if (fileNo) {
patientId = int.tryParse(nationId);
}
}
var request = SendActivationRequest();
if (phoneNumber.isNotEmpty) {
request.patientMobileNumber = int.parse(phoneNumber);
// If zipCode is "0", it means "Others" country is selected
if (zipCode == "0") {
request.patientMobileNumberOthers = phoneNumber;
} else {
request.patientMobileNumber = int.parse(phoneNumber);
}
}
request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed
request.zipCode = zipCode; // or countryCode if defined elsewhere
@ -53,9 +60,9 @@ class RequestUtils {
request.patientIdentificationID = request.nationalID;
request.searchType = 2;
} else {
request.patientID = 0;
request.patientID = 0;
request.searchType = 1;
request.patientIdentificationID = request.nationalID = nationId.isNotEmpty ? int.parse(nationId) : 0;
request.patientIdentificationID = request.nationalID = nationId.isNotEmpty ? nationId : '0';
}
request.isRegister = false;
}
@ -75,11 +82,18 @@ class RequestUtils {
required int loginType}) {
bool fileNo = false;
if (nationIdText.isNotEmpty) {
fileNo = nationIdText.length < 10;
final numericRegex = RegExp(r'^[0-9]+$');
fileNo = nationIdText.length < 10 && numericRegex.hasMatch(nationIdText);
}
var request = SendActivationRequest();
request.patientMobileNumber = int.parse(phoneNumber);
request.mobileNo = '0$phoneNumber';
// If countryCode is "0", it means "Others" country is selected
if (countryCode == "0") {
request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber;
} else {
request.patientMobileNumber = int.parse(phoneNumber);
request.mobileNo = '0$phoneNumber';
}
request.deviceToken = deviceToken;
request.projectOutSA = patientOutSA;
request.loginType = loginType;
@ -102,14 +116,14 @@ class RequestUtils {
} else {
if (fileNo) {
request.patientID = patientId ?? int.parse(nationIdText);
request.patientIdentificationID = request.nationalID = 0;
request.patientIdentificationID = request.nationalID = '0';
request.searchType = 2;
//TODO: Issue HEre is Not Login
} else {
request.patientID = 0;
request.searchType = 1;
//TODO: Issue HEre is Not Login
request.patientIdentificationID = request.nationalID = (nationIdText.isNotEmpty ? int.parse(nationIdText) : 0);
request.patientIdentificationID = request.nationalID = (nationIdText.isNotEmpty ? nationIdText : '0');
}
request.isRegister = false;
}
@ -123,7 +137,7 @@ class RequestUtils {
required String mobileNumber,
required String zipCode,
required int? patientId,
required int? nationalId,
required dynamic nationalId,
required bool patientOutSA,
required int selectedLoginType,
required bool isForRegister,
@ -136,9 +150,15 @@ class RequestUtils {
AppState _appState = getIt.get<AppState>();
var request = SendActivationRequest();
if (mobileNumber.isNotEmpty) {
request.patientMobileNumber = int.parse(mobileNumber);
// If zipCode is "0", it means "Others" country is selected
if (zipCode == "0") {
request.patientMobileNumberOthers = mobileNumber;
request.mobileNo = mobileNumber;
} else {
request.patientMobileNumber = int.parse(mobileNumber);
request.mobileNo = '0$mobileNumber';
}
}
request.mobileNo = '0$mobileNumber';
request.projectOutSA = patientOutSA;
request.loginType = selectedLoginType;
request.oTPSendType = otpTypeEnum.toInt(); //this.selectedOption == 1 ? 1 : 2;
@ -159,7 +179,7 @@ class RequestUtils {
request.searchType = isFileNo ? 2 : 1;
request.patientID = patientId ?? 0;
request.nationalID = nationalId ?? 0;
request.patientIdentificationID = (nationalId ?? '0') as int?;
request.patientIdentificationID = (nationalId ?? '0');
request.isRegister = false;
}
request.deviceTypeID = request.searchType;

@ -60,11 +60,23 @@ class ValidationUtils {
return isCorrectID;
}
static bool isValidatePhone({String? phoneNumber, required Function() onOkPress}) {
static bool isValidatePhone({String? phoneNumber, required Function() onOkPress, CountryEnum? selectedCountry}) {
if (phoneNumber == null || phoneNumber.isEmpty) {
_dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidPhoneNumber.tr(), onOkPressed: onOkPress);
return false;
}
// Check if "Others" country is selected and phone number starts with restricted codes
if (selectedCountry == CountryEnum.others) {
if (phoneNumber.startsWith('00966') || phoneNumber.startsWith('00971')) {
_dialogService.showExceptionBottomSheet(
message: LocaleKeys.cannotEnterSaudiOrUAENumber.tr(),
onOkPressed: onOkPress
);
return false;
}
}
return true;
}

@ -470,6 +470,8 @@ extension CountryExtension on CountryEnum {
return "Kingdom Of Saudi Arabia";
case CountryEnum.unitedArabEmirates:
return "United Arab Emirates";
case CountryEnum.others:
return "Others";
}
}
@ -479,6 +481,8 @@ extension CountryExtension on CountryEnum {
return "المملكة العربية السعودية";
case CountryEnum.unitedArabEmirates:
return "الإمارات العربية المتحدة";
case CountryEnum.others:
return "آخرون";
}
}
@ -488,6 +492,8 @@ extension CountryExtension on CountryEnum {
return AppAssets.ksa;
case CountryEnum.unitedArabEmirates:
return AppAssets.uae;
case CountryEnum.others:
return AppAssets.globeOther;
}
}
@ -497,6 +503,8 @@ extension CountryExtension on CountryEnum {
return "966";
case CountryEnum.unitedArabEmirates:
return "971";
case CountryEnum.others:
return "0";
}
}
@ -508,6 +516,9 @@ extension CountryExtension on CountryEnum {
case "United Arab Emirates":
case "الإمارات العربية المتحدة":
return CountryEnum.unitedArabEmirates;
case "Others":
case "آخرون":
return CountryEnum.others;
default:
throw Exception("Invalid country name");
}

@ -102,11 +102,16 @@ class AuthenticationRepoImp implements AuthenticationRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq, String? languageID}) async {
int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1;
checkPatientAuthenticationReq.patientOutSA = isOutKsa;
// Determine which API to use based on zipCode
final isOthersCountry = checkPatientAuthenticationReq.zipCode == '0';
final apiEndpoint = isOthersCountry ? ApiConsts.checkPatientAuthOthers : ApiConsts.checkPatientAuth;
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.checkPatientAuth,
apiEndpoint,
body: checkPatientAuthenticationReq.toJson(),
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
@ -150,16 +155,25 @@ class AuthenticationRepoImp implements AuthenticationRepo {
// payload.remove("ResponseID");
}
// Determine which API to use based on zipCode
final isOthersCountry = sendActivationCodeReq.zipCode == '0';
String apiEndpoint;
if (isFormFamilyFile) {
apiEndpoint = ApiConsts.sendFamilyFileActivation;
} else if (isRegister) {
apiEndpoint = ApiConsts.sendActivationCodeRegister;
} else if (isOthersCountry) {
apiEndpoint = ApiConsts.sendActivationCodeOthers;
} else {
apiEndpoint = ApiConsts.sendActivationCode;
}
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
isFormFamilyFile
? ApiConsts.sendFamilyFileActivation
: isRegister
? ApiConsts.sendActivationCodeRegister
: ApiConsts.sendActivationCode,
apiEndpoint,
body: isFormFamilyFile ? payload : sendActivationCodeReq.toJson(),
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
@ -186,6 +200,8 @@ class AuthenticationRepoImp implements AuthenticationRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> checkActivationCodeRepo(
{required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq
@ -304,11 +320,16 @@ class AuthenticationRepoImp implements AuthenticationRepo {
}
}
// Determine which API to use based on zipCode
final zipCode = _getZipCodeFromRequest(newRequest);
final isOthersCountry = zipCode == '0';
final endpoint = isFormFamilyFile
? ApiConsts.checkActivationCodeForFamily
: isRegister
? ApiConsts.checkActivationCodeRegister
: ApiConsts.checkActivationCode;
: isOthersCountry
? ApiConsts.checkActivationCodeOthers
: ApiConsts.checkActivationCode;
try {
GenericApiModel<dynamic>? apiResponse;
@ -521,8 +542,14 @@ class AuthenticationRepoImp implements AuthenticationRepo {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
// Determine which API to use based on zipCode
final zipCode = patientIMEIDataRequest['ZipCode'];
final isOthersCountry = zipCode == '0';
final apiEndpoint = isOthersCountry ? ApiConsts.insertPatientDeviceIMEIDataOthers : ApiConsts.insertPatientDeviceIMEIData;
return apiClient.post(
ApiConsts.insertPatientDeviceIMEIData,
apiEndpoint,
body: patientIMEIDataRequest,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
@ -648,4 +675,17 @@ class AuthenticationRepoImp implements AuthenticationRepo {
return Future.value(Left(UnknownFailure(e.toString())));
}
}
/// Helper method to safely get zipCode from dynamic request (Map or Object)
String _getZipCodeFromRequest(dynamic request) {
if (request is Map) {
return request['ZipCode']?.toString() ?? request['zipCode']?.toString() ?? '';
} else {
try {
return request.zipCode?.toString() ?? '';
} catch (e) {
return '';
}
}
}
}

@ -70,7 +70,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_errorHandlerService = errorHandlerService,
_appState = appState,
_authenticationRepo = authenticationRepo,
_localAuthService = localAuthService;
_localAuthService = localAuthService {}
final TextEditingController nationalIdController = TextEditingController(),
phoneNumberController = TextEditingController(),
dobController = TextEditingController(),
@ -182,6 +182,17 @@ class AuthenticationViewModel extends ChangeNotifier {
void onPhoneNumberChange(String? phoneNumber) {
phoneNumberController.text = phoneNumber!;
// Real-time validation for Others country - prevent entering Saudi/UAE numbers
if (selectedCountrySignup == CountryEnum.others) {
if (phoneNumber.startsWith('00966') || phoneNumber.startsWith('00971')) {
// Remove the restricted prefix
phoneNumberController.text = phoneNumber.replaceAll(RegExp(r'^(00966|00971)'), '');
phoneNumberController.selection = TextSelection.fromPosition(
TextPosition(offset: phoneNumberController.text.length),
);
}
}
}
void onTermAccepted() {
@ -301,9 +312,11 @@ class AuthenticationViewModel extends ChangeNotifier {
patientOutSA: false,
otpTypeEnum: otpTypeEnum,
patientId: 0,
zipCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: selectedCountrySignup.countryCode,
zipCode: selectedCountrySignup == CountryEnum.others
? "0"
: (_appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: selectedCountrySignup.countryCode),
calenderType: calenderType);
final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
@ -368,7 +381,7 @@ class AuthenticationViewModel extends ChangeNotifier {
mobileNumber: phoneNumber,
selectedLoginType: otpTypeEnum.toInt(),
zipCode: selectedCountrySignup.countryCode,
nationalId: int.parse(nationalIdOrFileNumber),
nationalId:nationalIdOrFileNumber,
isFileNo: isForRegister ? isPatientHasFile(request: payload) : false,
patientId: isFormFamilyFile ? _appState.getAuthenticatedUser()!.patientId : 0,
isForRegister: isForRegister,
@ -607,6 +620,7 @@ class AuthenticationViewModel extends ChangeNotifier {
activation.list!.first.isParentUser = true;
}
activation.list!.first.bloodGroup = activation.patientBlodType;
activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode;
_appState.setAuthenticatedUser(activation.list!.first);
_appState.setPrivilegeModelList(activation.list!.first.listPrivilege!);
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
@ -854,7 +868,9 @@ class AuthenticationViewModel extends ChangeNotifier {
await clearDefaultInputValues(); // This will Clear All Default Values Of User.
Future.delayed(Duration(seconds: 1), () {
LoadingUtils.hideFullScreenLoader();
_navigationService.pushAndReplace(AppRoutes.loginScreen);
// _navigationService.pushAndReplace(AppRoutes.loginScreen);
_navigationService.pushAndRemoveUntil(CustomPageRoute(page: LandingNavigation()), (r) => false);
_navigationService.push(CustomPageRoute(page: LoginScreen()));
});
}
}
@ -948,22 +964,24 @@ class AuthenticationViewModel extends ChangeNotifier {
Future<void> insertPatientIMEIData(int loginType) async {
final resultEither = await _authenticationRepo.insertPatientIMEIData(
patientIMEIDataRequest: PatientInsertDeviceImei(
imei: _appState.deviceToken,
deviceTypeId: _appState.getDeviceTypeID(),
patientId: _appState.getAuthenticatedUser()!.patientId!,
patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!,
identificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!,
firstName: _appState.getAuthenticatedUser()!.firstName!,
lastName: _appState.getAuthenticatedUser()!.lastName!,
patientTypeId: _appState.getAuthenticatedUser()!.patientType,
mobileNo: _appState.getAuthenticatedUser()!.mobileNumber!,
logInTypeId: loginType,
patientOutSa: _appState.getAuthenticatedUser()!.outSa!,
outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true : false,
biometricEnabled: loginType == 1 || loginType == 2 ? false : true,
firstNameN: _appState.getAuthenticatedUser()!.firstNameN,
lastNameN: _appState.getAuthenticatedUser()!.lastNameN,
).toJson());
imei: _appState.deviceToken,
deviceTypeId: _appState.getDeviceTypeID(),
patientId: _appState.getAuthenticatedUser()!.patientId!,
patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!,
identificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!,
firstName: _appState.getAuthenticatedUser()!.firstName!,
lastName: _appState.getAuthenticatedUser()!.lastName!,
patientTypeId: _appState.getAuthenticatedUser()!.patientType,
mobileNo: _appState.getAuthenticatedUser()!.mobileNumber!,
logInTypeId: loginType,
patientOutSa: _appState.getAuthenticatedUser()!.outSa!,
outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true : false,
biometricEnabled: loginType == 1 || loginType == 2 ? false : true,
firstNameN: _appState.getAuthenticatedUser()!.firstNameN,
lastNameN: _appState.getAuthenticatedUser()!.lastNameN,
zipCode: _appState.getAuthenticatedUser()!.zipCode //selectedCountrySignup == CountryEnum.others ? '0': selectedCountrySignup.countryCode,
)
.toJson());
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
if (apiResponse.messageStatus == 1) {
log("Insert IMEI Success");

@ -1,5 +1,6 @@
class CheckActivationCodeRegisterReq {
int? patientMobileNumber;
String? patientMobileNumberOthers;
String? mobileNo;
String? deviceToken;
bool? projectOutSA;
@ -9,8 +10,8 @@ class CheckActivationCodeRegisterReq {
String? logInTokenID;
int? searchType;
int? patientID;
int? nationalID;
int? patientIdentificationID;
dynamic nationalID;
dynamic patientIdentificationID;
String? activationCode;
bool? isSilentLogin;
double? versionID;
@ -29,6 +30,7 @@ class CheckActivationCodeRegisterReq {
CheckActivationCodeRegisterReq({
this.patientMobileNumber,
this.patientMobileNumberOthers,
this.mobileNo,
this.deviceToken,
this.projectOutSA,
@ -59,6 +61,7 @@ class CheckActivationCodeRegisterReq {
CheckActivationCodeRegisterReq.fromJson(Map<String, dynamic> json) {
patientMobileNumber = json['PatientMobileNumber'];
patientMobileNumberOthers = json['PatientMobileNumber_Others'];
mobileNo = json['MobileNo'];
deviceToken = json['DeviceToken'];
projectOutSA = json['ProjectOutSA'];
@ -89,9 +92,14 @@ class CheckActivationCodeRegisterReq {
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['PatientMobileNumber'] = patientMobileNumber;
data['MobileNo'] = mobileNo;
// Only send PatientMobileNumber_Others if it's set, otherwise send PatientMobileNumber
if (patientMobileNumberOthers != null) {
data['PatientMobileNumber_Others'] = patientMobileNumberOthers;
} else if (patientMobileNumber != null) {
data['PatientMobileNumber'] = patientMobileNumber;
}
data['DeviceToken'] = deviceToken;
data['MobileNo'] = mobileNo;
data['ProjectOutSA'] = projectOutSA;
data['LoginType'] = loginType;
data['ZipCode'] = zipCode;

@ -28,6 +28,7 @@ class PatientInsertDeviceImei {
int? patientOutSa;
String? sessionId;
String? lastName;
String? zipCode;
PatientInsertDeviceImei({
this.setupId,
@ -56,7 +57,8 @@ class PatientInsertDeviceImei {
this.patientTypeId,
this.patientOutSa,
this.sessionId,
this.lastName
this.lastName,
this.zipCode,
});
factory PatientInsertDeviceImei.fromRawJson(String str) => PatientInsertDeviceImei.fromJson(json.decode(str));
@ -91,6 +93,7 @@ class PatientInsertDeviceImei {
patientOutSa: json["PatientOutSA"],
sessionId: json["SessionID"],
lastName: json["LastName"],
zipCode: json["ZipCode"],
);
@ -122,5 +125,6 @@ class PatientInsertDeviceImei {
"PatientOutSA": patientOutSa,
"SessionID": sessionId,
"LastName": lastName,
"ZipCode": zipCode,
};
}

@ -1,5 +1,6 @@
class SendActivationRequest {
int? patientMobileNumber;
String? patientMobileNumberOthers;
String? mobileNo;
String? deviceToken;
bool? projectOutSA;
@ -9,8 +10,8 @@ class SendActivationRequest {
String? logInTokenID;
int? searchType;
int? patientID;
int? nationalID;
int? patientIdentificationID;
dynamic nationalID;
dynamic patientIdentificationID;
int? oTPSendType;
int? languageID;
double? versionID;
@ -32,6 +33,7 @@ class SendActivationRequest {
SendActivationRequest(
{this.patientMobileNumber,
this.patientMobileNumberOthers,
this.mobileNo,
this.deviceToken,
this.projectOutSA,
@ -64,6 +66,7 @@ class SendActivationRequest {
SendActivationRequest.fromJson(Map<String, dynamic> json) {
patientMobileNumber = json['PatientMobileNumber'];
patientMobileNumberOthers = json['PatientMobileNumber_Others'];
mobileNo = json['MobileNo'];
deviceToken = json['DeviceToken'];
projectOutSA = json['ProjectOutSA'];
@ -97,7 +100,12 @@ class SendActivationRequest {
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['PatientMobileNumber'] = patientMobileNumber;
// Only send PatientMobileNumber_Others if it's set, otherwise send PatientMobileNumber
if (patientMobileNumberOthers != null) {
data['PatientMobileNumber_Others'] = patientMobileNumberOthers;
} else if (patientMobileNumber != null) {
data['PatientMobileNumber'] = patientMobileNumber;
}
data['MobileNo'] = mobileNo;
data['DeviceToken'] = deviceToken;
data['ProjectOutSA'] = projectOutSA;

@ -19,7 +19,7 @@ class SelectDeviceByImeiRespModelElement {
bool? biometricEnabled;
int? patientType;
int? preferredLanguage;
bool? isOther;
SelectDeviceByImeiRespModelElement({
this.id,
this.imei,
@ -35,6 +35,7 @@ class SelectDeviceByImeiRespModelElement {
this.biometricEnabled,
this.patientType,
this.preferredLanguage,
this.isOther
});
factory SelectDeviceByImeiRespModelElement.fromJson(Map<String, dynamic> json) => SelectDeviceByImeiRespModelElement(
@ -52,6 +53,7 @@ class SelectDeviceByImeiRespModelElement {
biometricEnabled: json["BiometricEnabled"] as bool?,
patientType: json["PatientType"] as int?,
preferredLanguage: json["PreferredLanguage"] as int?,
isOther: json["IsOther"] as bool?
);
Map<String, dynamic> toJson() => {
@ -69,5 +71,6 @@ class SelectDeviceByImeiRespModelElement {
"BiometricEnabled": biometricEnabled,
"PatientType": patientType,
"PreferredLanguage": preferredLanguage,
"IsOther": isOther,
};
}

@ -812,6 +812,7 @@ abstract class LocaleKeys {
static const ready = 'ready';
static const enterValidNationalId = 'enterValidNationalId';
static const enterValidPhoneNumber = 'enterValidPhoneNumber';
static const cannotEnterSaudiOrUAENumber = 'cannotEnterSaudiOrUAENumber';
static const medicalCentersWithCount = 'medicalCentersWithCount';
static const medicalCenters = 'medicalCenters';
static const hospitalsWithCount = 'hospitalsWithCount';

@ -79,7 +79,7 @@ class LoginScreenState extends State<LoginScreen> {
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
focusNode: _nationalIdFocusNode,
keyboardType: TextInputType.number,
keyboardType: TextInputType.text,
isEnable: true,
prefix: null,
autoFocus: true,
@ -165,51 +165,60 @@ class LoginScreenState extends State<LoginScreen> {
child: SingleChildScrollView(
child: GenericBottomSheet(
countryCode: authViewModel.selectedCountrySignup.countryCode,
initialPhoneNumber: "",
textController: phoneNumberController,
isEnableCountryDropdown: true,
onCountryChange: authViewModel.onCountryChange,
onCountryChange: (country) {
authViewModel.onCountryChange(country);
setModalState(() {});
},
onChange: authViewModel.onPhoneNumberChange,
buttons: [
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(context: context),
onPressed: () async {
if (ValidationUtils.isValidatePhone(
phoneNumber: phoneNumberController!.text,
onOkPress: () {
Navigator.of(context).pop();
})) {
Navigator.of(context).pop();
appState.setSelectDeviceByImeiRespModelElement(null);
await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: Colors.white,
iconColor: Colors.white,
icon: AppAssets.message,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.h),
child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor),
if (authViewModel.selectedCountrySignup != CountryEnum.others)
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(context: context),
onPressed: () async {
if (ValidationUtils.isValidatePhone(
phoneNumber: phoneNumberController!.text,
selectedCountry: authViewModel.selectedCountrySignup,
onOkPress: () {
Navigator.of(context).pop();
})) {
Navigator.of(context).pop();
appState.setSelectDeviceByImeiRespModelElement(null);
await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
textColor: Colors.white,
iconColor: Colors.white,
icon: AppAssets.message,
),
],
),
),
if (authViewModel.selectedCountrySignup != CountryEnum.others)
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.h),
child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor),
),
],
),
Padding(
padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(context: context),
onPressed: () async {
if (ValidationUtils.isValidatePhone(
phoneNumber: phoneNumberController!.text,
selectedCountry: authViewModel.selectedCountrySignup,
onOkPress: () {
Navigator.of(context).pop();
})) {
@ -220,7 +229,7 @@ class LoginScreenState extends State<LoginScreen> {
},
backgroundColor: Colors.white,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.whiteColor,
textColor: AppColors.borderOnlyColor,
icon: AppAssets.whatsapp,
iconColor: null,
applyThemeColor: false,

@ -90,7 +90,7 @@ class _RegisterNew extends State<RegisterNew> {
child: Column(
children: [
CustomCountryDropdown(
countryList: CountryEnum.values,
countryList: CountryEnum.values.where((c) => c != CountryEnum.others).toList(),
onCountryChange: authVm.onCountryChange,
// isRtl: Directionality.of(context) == TextDirection.LTR,
).withVerticalPadding(8.h),

@ -31,7 +31,7 @@ class _SavedLogin extends State<SavedLogin> {
LoginTypeEnum loginType = LoginTypeEnum.sms;
late AuthenticationViewModel authVm;
late AppState appState;
bool? isOther;
@override
void initState() {
authVm = context.read<AuthenticationViewModel>();
@ -43,6 +43,15 @@ class _SavedLogin extends State<SavedLogin> {
: appState.getSelectDeviceByImeiRespModelElement!.mobile!;
authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!;
// Check if this is an "Others" country user
isOther = appState.getSelectDeviceByImeiRespModelElement!.isOther == true;
// Set country to Others if isOther flag is true - this ensures _Others APIs are called
if (isOther!) {
authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!;
authVm.selectedCountrySignup = CountryEnum.others;
}
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {});
}
@ -90,9 +99,9 @@ class _SavedLogin extends State<SavedLogin> {
),
child: Column(
children: [
// Last login info
// Last login info - show WhatsApp only if isOther AND loginType is SMS
("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}")
("${LocaleKeys.lastLoginBy.tr()} ${(isOther == true && loginType == LoginTypeEnum.sms) ? LoginTypeEnum.whatsapp.displayName : loginType.displayName}")
.toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1),
Directionality(
textDirection: ui.TextDirection.ltr,
@ -108,20 +117,27 @@ class _SavedLogin extends State<SavedLogin> {
? Container(
margin: EdgeInsets.all(16.h),
child: Utils.buildSvgWithAssets(
icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!),
icon: (isOther == true && loginType == LoginTypeEnum.sms) ? AppAssets.whatsapp : getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!),
height: 54.h,
width: 54.w,
iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor))
iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType.toInt == 4 ? null : AppColors.primaryRedColor))
: SizedBox(),
// Face ID login button
// Main login button - for isOther with SMS, show WhatsApp, otherwise keep original login type
CustomButton(
text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}",
text: (isOther == true && loginType == LoginTypeEnum.sms)
? "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}"
: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}",
onPressed: () {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {});
} else {
authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp);
// For isOther with SMS, use WhatsApp; otherwise use the original login type
authVm.checkUserAuthentication(
otpTypeEnum: (isOther == true && loginType == LoginTypeEnum.sms)
? OTPTypeEnum.whatsapp
: (loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp),
);
}
},
backgroundColor: AppColors.primaryRedColor,
@ -132,24 +148,25 @@ class _SavedLogin extends State<SavedLogin> {
borderRadius: 12.r,
height: 40.h,
padding: EdgeInsets.symmetric(vertical: 10.h),
icon: getTypeIcons(loginType.toInt),
//loginType == LoginTypeEnum.sms ? AppAssets.sms :AppAssets.whatsapp,
iconColor: loginType != LoginTypeEnum.whatsapp ? Colors.white : null,
icon: (isOther == true && loginType == LoginTypeEnum.sms) ? AppAssets.whatsapp : getTypeIcons(loginType.toInt),
iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType == LoginTypeEnum.whatsapp ? null : Colors.white,
),
],
),
),
SizedBox(height: 24.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text(
LocaleKeys.oR.tr(),
style: context.dynamicTextStyle(fontSize: 16.f, fontWeight: FontWeight.w500),
// Hide OR + OTP login options entirely when isOther is true
if (isOther != true) ...[
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text(
LocaleKeys.oR.tr(),
style: context.dynamicTextStyle(fontSize: 16.f, fontWeight: FontWeight.w500),
),
),
),
SizedBox(height: 24.h),
// OTP login button
loginType.toInt != 1
SizedBox(height: 24.h),
// OTP login button
loginType.toInt != 1
? Column(
children: [
loginType.toInt != 1
@ -256,9 +273,10 @@ class _SavedLogin extends State<SavedLogin> {
borderWidth: 2.w,
padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h),
),
],
const Spacer(flex: 2),
// OR divider
SizedBox(height: 24.h),
// Guest and Switch account
Row(

@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/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/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@ -1051,6 +1052,7 @@ class _LandingPageState extends State<LandingPage> {
isDone: isDone,
onPressed: () {
// sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true);
authVM.loginWithFingerPrintFace(() async {
isDone = true;
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true);

@ -158,7 +158,7 @@ class GenericBottomSheetState extends State<GenericBottomSheet> {
prefix: widget.isForEmail ? null : widget.countryCode,
isBorderAllowed: false,
isAllowLeadingIcon: true,
fontSize: 18.f,
fontSize: 14.f,
isCountryDropDown: widget.isEnableCountryDropdown,
leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone,
fontFamily: "Poppins",

@ -98,11 +98,12 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
selectedCountry!.countryCode,
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"),
),
SizedBox(width: 4.h),
if (selectedCountry != CountryEnum.others)
Text(
selectedCountry!.countryCode,
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"),
),
if (selectedCountry != CountryEnum.others) SizedBox(width: 4.h),
if (widget.isEnableTextField)
SizedBox(
height: 20.h,
@ -112,7 +113,14 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
child: TextField(
focusNode: textFocusNode,
style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"),
decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none, contentPadding: EdgeInsets.zero),
decoration: InputDecoration(
hintText: selectedCountry == CountryEnum.others ? "001*******" : "",
isDense: true,
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
keyboardType: TextInputType.phone,
onChanged: widget.onPhoneNumberChanged,
),

@ -44,7 +44,7 @@ class _FamilyFileAddWidgetState extends State<FamilyFileAddWidget> {
child: Column(
children: [
CustomCountryDropdown(
countryList: CountryEnum.values,
countryList: CountryEnum.values.where((c) => c != CountryEnum.others).toList(),
onCountryChange: authVm.onCountryChange,
).paddingOnly(top: 8.h, bottom: 16.h),
Divider(height: 1.h, color: AppColors.spacerLineColor),

Loading…
Cancel
Save