dev_sultan #197

Merged
Haroon6138 merged 7 commits from dev_sultan into master 1 day ago

@ -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",

@ -194,6 +194,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'] = 809289;
// body['PatientTypeID'] = 1;
@ -207,7 +212,7 @@ class ApiClientImp implements ApiClient {
// body['PatientID'] = 809289;
}
if(!url.contains("/paymentApi")) {
if (!url.contains("/paymentApi")) {
body['IsNewFlutterApp'] = true;
}
@ -261,10 +266,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,8 +276,7 @@ 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);
}

@ -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,
};
}

@ -918,7 +918,10 @@ class HmgServicesRepoImp implements HmgServicesRepo {
@override
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign() async {
Map<String, dynamic> requestBody = {};
Map<String, dynamic> requestBody = {
};
try {
GenericApiModel<List<VitalSignResModel>>? apiResponse;
@ -942,12 +945,29 @@ class HmgServicesRepoImp implements HmgServicesRepo {
if (vitalSignJson is Map<String, dynamic>) {
final vitalSign = VitalSignResModel.fromJson(vitalSignJson);
// Only add records where BOTH height AND weight are greater than 0
// Debug logging for blood pressure
print('=== Repository Blood Pressure Check ===');
print('bloodPressureHigher: ${vitalSign.bloodPressureHigher}');
print('bloodPressureLower: ${vitalSign.bloodPressureLower}');
print('weightKg: ${vitalSign.weightKg}');
print('heightCm: ${vitalSign.heightCm}');
// Check if the record has at least one valid vital sign measurement
final hasValidWeight = _isValidValue(vitalSign.weightKg);
final hasValidHeight = _isValidValue(vitalSign.heightCm);
// Only add if both height and weight are valid (> 0)
if (hasValidWeight && hasValidHeight) {
final hasValidBMI = _isValidValue(vitalSign.bodyMassIndex);
final hasValidTemperature = _isValidValue(vitalSign.temperatureCelcius);
final hasValidHeartRate = _isValidValue(vitalSign.heartRate) || _isValidValue(vitalSign.pulseBeatPerMinute);
final hasValidRespiration = _isValidValue(vitalSign.respirationBeatPerMinute);
final hasValidBloodPressure = _isValidValue(vitalSign.bloodPressureHigher) || _isValidValue(vitalSign.bloodPressureLower);
print('hasValidBloodPressure: $hasValidBloodPressure');
print('Will add to list: ${hasValidWeight || hasValidHeight || hasValidBMI || hasValidTemperature || hasValidHeartRate || hasValidRespiration || hasValidBloodPressure}');
print('=====================================');
// Add if ANY vital sign has valid data
if (hasValidWeight || hasValidHeight || hasValidBMI || hasValidTemperature ||
hasValidHeartRate || hasValidRespiration || hasValidBloodPressure) {
vitalSignList.add(vitalSign);
}
}

@ -32,10 +32,14 @@ class InsuranceViewModel extends ChangeNotifier {
InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService});
initInsuranceProvider() {
debugPrint("InsuranceViewModel: initInsuranceProvider called, isInsuranceDataToBeLoaded=$isInsuranceDataToBeLoaded");
if (isInsuranceDataToBeLoaded) {
patientInsuranceList.clear();
isInsuranceLoading = true;
debugPrint("InsuranceViewModel: Calling getPatientInsuranceDetails API");
getPatientInsuranceDetails();
} else {
debugPrint("InsuranceViewModel: Skipping API call, isInsuranceDataToBeLoaded is false");
}
patientInsuranceCardHistoryList.clear();
isInsuranceHistoryLoading = true;
@ -71,13 +75,18 @@ class InsuranceViewModel extends ChangeNotifier {
}
Future<void> getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isInsuranceDataToBeLoaded) return;
if (!isInsuranceDataToBeLoaded) {
debugPrint("InsuranceViewModel: getPatientInsuranceDetails - skipping, flag is false");
return;
}
debugPrint("InsuranceViewModel: getPatientInsuranceDetails - making API call");
final result = await insuranceRepo.getPatientInsuranceDetails();
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
debugPrint("InsuranceViewModel: API call failed - ${failure.toString()}");
isInsuranceLoading = false;
isInsuranceDataToBeLoaded = false;
isInsuranceExpired = false;
@ -86,16 +95,21 @@ class InsuranceViewModel extends ChangeNotifier {
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
debugPrint("InsuranceViewModel: API returned error status");
isInsuranceDataToBeLoaded = false;
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
debugPrint("InsuranceViewModel: API success - received ${apiResponse.data?.length ?? 0} insurance records");
patientInsuranceList = apiResponse.data!;
isInsuranceLoading = false;
isInsuranceDataToBeLoaded = false;
isInsuranceExpired = DateTime.now().isAfter(
DateUtil.convertStringToDate(patientInsuranceList.first.cardValidTo),
);
if (patientInsuranceList.isNotEmpty) {
isInsuranceExpired = DateTime.now().isAfter(
DateUtil.convertStringToDate(patientInsuranceList.first.cardValidTo),
);
debugPrint("InsuranceViewModel: Insurance card expired: $isInsuranceExpired");
}
isInsuranceActive = patientInsuranceList.first.isActive ?? false;
// isInsuranceActive = true;

@ -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,
@ -168,41 +168,47 @@ class LoginScreenState extends State<LoginScreen> {
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(
@ -210,6 +216,7 @@ class LoginScreenState extends State<LoginScreen> {
onPressed: () async {
if (ValidationUtils.isValidatePhone(
phoneNumber: phoneNumberController!.text,
selectedCountry: authViewModel.selectedCountrySignup,
onOkPress: () {
Navigator.of(context).pop();
})) {

@ -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),

@ -5,12 +5,15 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.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/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
@ -28,11 +31,22 @@ class RegisterNewStep2 extends StatefulWidget {
class _RegisterNew extends State<RegisterNewStep2> {
AuthenticationViewModel? authVM;
InsuranceViewModel? insuranceVM;
@override
void initState() {
super.initState();
authVM = context.read<AuthenticationViewModel>();
insuranceVM = context.read<InsuranceViewModel>();
// Call insurance API to fetch data
WidgetsBinding.instance.addPostFrameCallback((_) {
debugPrint("Registration Step 2: Calling insurance API");
// Reset the flag to ensure API gets called
insuranceVM?.setIsInsuranceDataToBeLoaded(true);
insuranceVM?.initInsuranceProvider();
debugPrint("Registration Step 2: Insurance API call initiated");
});
}
@override
@ -59,6 +73,59 @@ class _RegisterNew extends State<RegisterNewStep2> {
onLanguageChanged: (lang) {},
hideLogoAndLang: true,
),
bottomSheet: Container(
// height: 200.h,
width: MediaQuery.of(context).size.width,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)),
hasShadow: true,
),
child: Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(),
icon: AppAssets.cancel,
onPressed: () {
Navigator.of(context).pop();
// authVM!.clearDefaultInputValues();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
iconColor: AppColors.primaryRedColor,
),
),
SizedBox(width: 16.w),
Expanded(
child: CustomButton(
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
text: LocaleKeys.confirm.tr(),
icon: AppAssets.confirm,
iconColor: AppColors.whiteColor,
onPressed: () {
if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) {
if (ValidationUtils.validateUaeRegistration(
name: authVM!.nameController.text,
gender: authVM!.genderType,
country: authVM!.pickedCountryByUAEUser,
maritalStatus: authVM!.maritalStatus,
onOkPress: () {
Navigator.of(context).pop();
})) {
showModel(context: context);
}
} else {
showModel(context: context);
}
},
),
)
],
).paddingAll(16.h),),
body: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
@ -75,6 +142,29 @@ class _RegisterNew extends State<RegisterNewStep2> {
children: <Widget>[
LocaleKeys.personalDetailsVerification.tr(context: context).toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2),
SizedBox(height: 24.h),
// Insurance Card Section
Consumer<InsuranceViewModel>(
builder: (context, insuranceVM, child) {
// Only show if insurance data is available and not loading
if (!insuranceVM.isInsuranceLoading && insuranceVM.patientInsuranceList.isNotEmpty) {
return Column(
children: [
PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isAfter(
DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo),
),
),
SizedBox(height: 24.h),
],
);
}
// Don't show anything if loading or no data
return SizedBox.shrink();
},
),
Container(
decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(24)),
padding: EdgeInsets.only(left: 16.h, right: 16.h),
@ -97,7 +187,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isReadOnly: authVM!.isUserFromUAE() ? false : true,
leadingIcon: AppAssets.user_circle,
labelColor: AppColors.textColor,
).paddingSymmetrical(0.h, 16.h),
).paddingSymmetrical(0.h, 8.h),
Divider(height: 1.h, color: AppColors.greyColor),
TextInputWidget(
labelText: LocaleKeys.nationalIdNumber.tr(context: context),
@ -111,7 +201,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isReadOnly: true,
labelColor: AppColors.textColor,
leadingIcon: AppAssets.student_card)
.paddingSymmetrical(0.h, 16.h),
.paddingSymmetrical(0.h, 8.h),
Divider(height: 1, color: AppColors.greyColor),
authVM!.isUserFromUAE()
? Selector<AuthenticationViewModel, GenderTypeEnum?>(
@ -148,7 +238,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
leadingIcon: AppAssets.user_full,
labelColor: AppColors.textColor,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
.paddingSymmetrical(0.h, 8.h),
Divider(height: 1, color: AppColors.greyColor),
authVM!.isUserFromUAE()
? Selector<AuthenticationViewModel, MaritalStatusTypeEnum?>(
@ -187,7 +277,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
labelColor: AppColors.textColor,
leadingIcon: AppAssets.smart_phone,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
.paddingSymmetrical(0.h, 8.h),
Divider(height: 1.h, color: AppColors.greyColor),
authVM!.isUserFromUAE()
? Selector<AuthenticationViewModel, ({List<NationalityCountries>? countriesList, NationalityCountries? selectedCountry, bool isArabic})>(
@ -237,14 +327,14 @@ class _RegisterNew extends State<RegisterNewStep2> {
labelColor: AppColors.textColor,
leadingIcon: AppAssets.globe,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
.paddingSymmetrical(0.h, 8.h),
Divider(
height: 1,
color: AppColors.greyColor,
),
TextInputWidget(
labelText: LocaleKeys.mobileNumber.tr(context: context),
hintText: (appState.getUserRegistrationPayload.patientMobileNumber.toString() ?? ""),
hintText: appState.getUserRegistrationPayload.patientMobileNumber.toString(),
controller: null,
isEnable: false,
prefix: null,
@ -254,7 +344,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
labelColor: AppColors.textColor,
isReadOnly: true,
leadingIcon: AppAssets.call)
.paddingSymmetrical(0.h, 16.h),
.paddingSymmetrical(0.h, 8.h),
Divider(
height: 1.h,
color: AppColors.greyColor,
@ -271,56 +361,56 @@ class _RegisterNew extends State<RegisterNewStep2> {
labelColor: AppColors.textColor,
leadingIcon: AppAssets.birthday_cake,
selectionType: null,
).paddingSymmetrical(0.h, 16.h),
).paddingSymmetrical(0.h, 8.h),
],
),
),
SizedBox(height: 50.h),
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.cancel.tr(context: context),
icon: AppAssets.cancel,
onPressed: () {
Navigator.of(context).pop();
// authVM!.clearDefaultInputValues();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
iconColor: AppColors.primaryRedColor,
),
),
SizedBox(width: 16.w),
Expanded(
child: CustomButton(
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
text: LocaleKeys.confirm.tr(context: context),
icon: AppAssets.confirm,
iconColor: AppColors.whiteColor,
onPressed: () {
if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) {
if (ValidationUtils.validateUaeRegistration(
name: authVM!.nameController.text,
gender: authVM!.genderType,
country: authVM!.pickedCountryByUAEUser,
maritalStatus: authVM!.maritalStatus,
onOkPress: () {
Navigator.of(context).pop();
})) {
showModel(context: context);
}
} else {
showModel(context: context);
}
},
),
)
],
),
// Row(
// children: [
// Expanded(
// child: CustomButton(
// text: LocaleKeys.cancel.tr(context: context),
// icon: AppAssets.cancel,
// onPressed: () {
// Navigator.of(context).pop();
// // authVM!.clearDefaultInputValues();
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// iconColor: AppColors.primaryRedColor,
// ),
// ),
// SizedBox(width: 16.w),
// Expanded(
// child: CustomButton(
// backgroundColor: AppColors.primaryRedColor,
// borderColor: AppColors.primaryRedColor,
// textColor: AppColors.whiteColor,
// text: LocaleKeys.confirm.tr(context: context),
// icon: AppAssets.confirm,
// iconColor: AppColors.whiteColor,
// onPressed: () {
// if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) {
// if (ValidationUtils.validateUaeRegistration(
// name: authVM!.nameController.text,
// gender: authVM!.genderType,
// country: authVM!.pickedCountryByUAEUser,
// maritalStatus: authVM!.maritalStatus,
// onOkPress: () {
// Navigator.of(context).pop();
// })) {
// showModel(context: context);
// }
// } else {
// showModel(context: context);
// }
// },
// ),
// )
// ],
// ),
],
),
),

@ -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
@ -257,9 +274,10 @@ class _SavedLogin extends State<SavedLogin> {
padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h),
applyThemeColor: false,
),
],
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);

@ -229,12 +229,21 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
}
Widget _historyCard(BuildContext context, {required List<DataPoint> history}) {
// For graph display, use only the last 5 data points to avoid performance issues
final graphHistory = _isGraphVisible && history.length > 5
? history.sublist(history.length - 5)
: history;
// For blood pressure, we need both systolic and diastolic series
List<DataPoint>? secondaryHistory;
if (args.metric == VitalSignMetric.bloodPressure) {
secondaryHistory = _buildBloodPressureDiastolicSeries(
final fullSecondaryHistory = _buildBloodPressureDiastolicSeries(
context.read<HmgServicesViewModel>().vitalSignList,
);
// Also limit secondary history to last 5 for graph
secondaryHistory = _isGraphVisible && fullSecondaryHistory.length > 5
? fullSecondaryHistory.sublist(fullSecondaryHistory.length - 5)
: fullSecondaryHistory;
}
return Container(
@ -303,7 +312,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
if (history.isEmpty)
Utils.getNoDataWidget(context, noDataText: LocaleKeys.noHistoryAvailable.tr(context: context), isSmallWidget: true)
else if (_isGraphVisible)
_buildHistoryGraph(history, secondaryHistory: secondaryHistory)
_buildHistoryGraph(graphHistory, secondaryHistory: secondaryHistory)
else
_buildHistoryList(context, history),
],
@ -604,8 +613,13 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
double? _toDouble(dynamic v) {
if (v == null) return null;
if (v is num) return v.toDouble();
return double.tryParse(v.toString());
if (v is num) {
// Treat 0 as invalid for vital signs
return v == 0 ? null : v.toDouble();
}
final parsed = double.tryParse(v.toString());
// Treat 0 as invalid for vital signs
return (parsed == null || parsed == 0) ? null : parsed;
}
String _latestValueText(VitalSignResModel? latest) {

@ -28,6 +28,19 @@ class VitalSignPage extends StatefulWidget {
}
class _VitalSignPageState extends State<VitalSignPage> {
// Helper function to check if a value is valid (not null, not 0, not empty string)
bool _isValidValue(dynamic value) {
if (value == null) return false;
if (value is num) return value > 0;
if (value is String) {
if (value.trim().isEmpty) return false;
final parsed = double.tryParse(value);
return parsed != null && parsed > 0;
}
return false;
}
void _openDetails(VitalSignDetailsArgs args) {
Navigator.of(context).push(
CustomPageRoute(
@ -55,6 +68,18 @@ class _VitalSignPageState extends State<VitalSignPage> {
? viewModel.vitalSignList.first
: null;
// Debug logging for blood pressure
if (latestVitalSign != null) {
print('=== Blood Pressure Debug ===');
print('bloodPressureHigher: ${latestVitalSign.bloodPressureHigher}');
print('bloodPressureLower: ${latestVitalSign.bloodPressureLower}');
print('bloodPressureHigher type: ${latestVitalSign.bloodPressureHigher.runtimeType}');
print('bloodPressureLower type: ${latestVitalSign.bloodPressureLower.runtimeType}');
print('bloodPressureHigher == 0: ${latestVitalSign.bloodPressureHigher == 0}');
print('bloodPressureLower == 0: ${latestVitalSign.bloodPressureLower == 0}');
print('========================');
}
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -129,9 +154,9 @@ class _VitalSignPageState extends State<VitalSignPage> {
_buildVitalSignCard(
icon: AppAssets.bloodPressure,
label: LocaleKeys.bloodPressure.tr(context: context),
value: latestVitalSign != null &&
latestVitalSign.bloodPressureHigher != null &&
latestVitalSign.bloodPressureLower != null
value: (latestVitalSign != null &&
_isValidValue(latestVitalSign.bloodPressureHigher) &&
_isValidValue(latestVitalSign.bloodPressureLower))
? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}'
: '--',
unit: '',

@ -57,7 +57,7 @@ class _SplashScreenState extends State<SplashPage> {
await notificationService.initialize(onNotificationClick: (payload) {
// Handle notification click here
});
ZoomService().initializeZoomSDK();
//ZoomService().initializeZoomSDK();
if (isAppOpenedFromCall) {
navigateToTeleConsult();
} else {

@ -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",

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -52,7 +53,7 @@ class DatePickerWidget extends StatelessWidget {
Widget _buildLabelText() {
return Text(
labelText,
style: const TextStyle(
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w500,
color: Color(0xff898A8D),

@ -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),

@ -1,3 +1,4 @@
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:flutter/material.dart';
@ -99,7 +100,7 @@ class _PhoneNumberInputState extends State<PhoneNumberInput> {
letterSpacing: 2,
),
),
style: const TextStyle(
style: TextStyle(
fontSize: 18.f,
fontWeight: FontWeight.bold,
letterSpacing: 2,

Loading…
Cancel
Save