no message

wala_plus
Sultan khan 3 hours ago
parent ca6ce85d7d
commit 9638d98b76

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.CAMERA" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

@ -381,7 +381,7 @@
"browseCategories": "تصفح الفئات",
"searchItems": "عناصر البحث",
"offerAndDiscounts": "العروض والخصومات",
"offerValid": "العرض صالح",
"offerValid": "العرض ساري",
"offerExpired": "انتهى العرض",
"whatAreYouOffering": "ما الذي تعرضه؟",
"selectCategory": "اختر الفئة",
@ -552,9 +552,6 @@
"pleaseClickButtonToJoinMarathon": "الرجاء الضغط على الزر أدناه للانضمام إلى الماراثون",
"youCannotJoinTheMarathon": "لا يمكنك الانضمام إلى الماراثون لأنك تجاوزت الحد الزمني",
"open": "يفتح",
"youCannotJoinTheMarathon": "لا يمكنك الانضمام إلى الماراثون لأنك تجاوزت الحد الزمني",
"pleaseClickButtonToJoinMarathon": "الرجاء الضغط على الزر أدناه للانضمام إلى الماراثون",
"generate": "يولد",
"paymentRequest": "طلب الدفع",
"paymentDetails": "تفاصيل الدفع",
"requestNo": "رقم الطلب",
@ -669,16 +666,117 @@
"incidentDetails": "تفاصيل الحادث",
"ticketNumber": "رقم التذكرة",
"ticketId": "معرف التذكرة",
"pleaseSelectProject": "الرجاء اختيار مشروع",
"viewAttachment" : "عرض المرفق",
"parkingQr": "وقوف السيارات QR",
"startNow": "ابدأ الآن",
"destination_type": "نوع الوجهة",
"source_organization_name": "اسم منظمة المصدر",
"destination_organization_name": "اسم منظمة الوجهة",
"pleaseSelectProject": "الرجاء اختيار مشروع",
"viewAttachment": "عرض المرفق",
"parkingQr": "وقوف السيارات QR",
"startNow": "ابدأ الآن",
"destination_type": "نوع الوجهة",
"source_organization_name": "اسم منظمة المصدر",
"destination_organization_name": "اسم منظمة الوجهة",
"costCenter": "مركز التكلفة",
"priceSAR": "السعر (ريال سعودي)",
"amountSAR": "المبلغ (ريال سعودي)",
"amuLastThreeMonths": "AMU (آخر 3 أشهر)"
"amuLastThreeMonths": "AMU (آخر 3 أشهر)",
"mazayaTitle": "مزايا",
"mazayaAccessGranted": "تم منح حق الوصول لمزايا",
"mazayaAccessGrantedDesc": "أصبح لدى {} حق وصول نشط الآن. شارك رابط التنزيل أدناه ليتمكن من تثبيت تطبيق مهم وإكمال الإعداد.",
"accessIsNowActive": "الوصول نشط الآن",
"accessIsNowActiveDesc": "يمكن لـ {} استخدام الرابط المرفق لتنزيل تطبيق مزايا ومتابعة الإعداد على جهازه.",
"dependentAccessActive": "وصول التابع نشط",
"shareableAppDownloadLink": "رابط تنزيل التطبيق للمشاركة",
"shareableAppDownloadLinkDesc": "أرسل هذا الرابط إلى {} ليتمكن من تنزيل تطبيق مزايا ومتابعة الإعداد.",
"downloadLink": "رابط التنزيل",
"shareDownloadLink": "مشاركة رابط التنزيل",
"copyLink": "نسخ الرابط",
"done": "تم",
"linkCopied": "تم نسخ الرابط بنجاح",
"mazayaAccessRevoked": "تم إلغاء الوصول لمزايا",
"mazayaAccessRevokedDesc": "قام الكفيل بإلغاء وصول التابع. لم يعد بالإمكان فتح العروض أو تسجيل الدخول بالبصمة.",
"contactYourSponsor": "تواصل مع الكفيل",
"contactYourSponsorDesc": "اطلب من كفيلك السماح بالوصول لمزايا مرة أخرى من تفاصيل التابعين. يمكن للكفيل فقط استعادة الوصول.",
"dependentAccessBlocked": "تم حظر وصول التابع",
"returnToSignIn": "العودة إلى تسجيل الدخول",
"allowMazayaAccess": "السماح بالوصول لمزايا",
"revokeMazayaAccess": "إلغاء الوصول لمزايا",
"active": "نشط",
"noAccess": "غير متاح",
"offerDetails": "تفاصيل العرض",
"offerIdNotAvailable": "معرف العرض غير متوفر",
"unableToRedeemOffer": "تعذر الحصول على رابط العرض، يرجى المحاولة لاحقاً",
"errorRedeemingOffer": "حدث خطأ أثناء استخدام العرض",
"useOffer": "استخدام العرض",
"online": "عبر الإنترنت",
"inStore": "في المتجر",
"ksa": "المملكة",
"riyadh": "الرياض",
"noInstructionsAvailable": "لا توجد طريقة استخدام محددة.",
"contactInformation": "معلومات التواصل:",
"humanResources": "الموارد البشرية",
"drSulaimanAlHabib": "سليمان الحبيب",
"allCategories": "الكل",
"noOffersAvailable": "لا توجد عروض متاحة",
"addDependents": "إضافة تابعين",
"myDependents": "التابعون",
"offDiscount": "خصم {}",
"usageLabel": "الاستخدام: ",
"areYouSureWantToLogOut": "هل أنت متأكد من تسجيل الخروج؟",
"searchDependents": "البحث في التابعين",
"searchFamilyMembers": "البحث في أفراد العائلة",
"addAnotherUser": "إضافة مستخدم آخر",
"joinMazaya": "انضم إلى مزايا",
"joinMazayaSubtitle": "احصل على أفضل العروض والصفقات الحصرية في المطاعم والتسوق ومكافآت نمط الحياة.",
"continueAsEmployee": "المتابعة كموظف",
"continueAsEmployeeSubtitle": "سجل الدخول باستخدام اسم المستخدم وكلمة المرور الخاصة بالموظف.",
"pleaseEnterNationalId": "الرجاء إدخال رقم الهوية الوطنية",
"pleaseSelectDob": "الرجاء تحديد تاريخ الميلاد",
"unableToRetrieveDependentInfo": "تعذر الحصول على معلومات التابع",
"failedToSendOtp": "فشل إرسال رمز التحقق",
"verificationCodeResent": "تم إعادة إرسال رمز التحقق",
"nationalId": "الهوية الوطنية",
"addDependent": "إضافة تابع",
"stepVerify": "التحقق",
"stepBasic": "الأساسية",
"stepAdditional": "إضافية",
"findYourDependent": "البحث عن تابعك",
"findYourDependentDesc": "أدخل التفاصيل تماماً كما تظهر في الهوية الوطنية. سنقوم باسترداد السجل بأمان من مركز المعلومات الوطني (NHIC).",
"enter10DigitNationalId": "أدخل رقم الهوية المكون من 10 أرقام",
"nationalIdMustBe10Digits": "يجب أن يتكون رقم الهوية من 10 أرقام",
"dateFormatPlaceholder": "يوم / شهر / سنة",
"searchUser": "بحث عن المستخدم",
"dependentNotFoundNhic": "لم يتم العثور على سجل لهذا التابع في مركز المعلومات الوطني",
"basicInfo": "المعلومات الأساسية",
"basicInfoDesc": "بيانات الهوية المطلوبة المستردة من مركز المعلومات الوطني.",
"additionalInfo": "معلومات إضافية",
"additionalInfoDesc": "إضافة أو مراجعة بيانات الكفالة والأهلية.",
"select": "اختر",
"enterFieldPlaceholder": "أدخل {}",
"requiredField": "الحقل المطلوب",
"pleaseFillField": "الرجاء إدخال {}",
"reviewAndSubmit": "المراجعة والإرسال",
"reviewAndSubmitDesc": "يرجى مراجعة تفاصيل التابع بعناية قبل تأكيد الإرسال.",
"arabicName": "الاسم بالعربية",
"relationship": "صلة القرابة",
"sponsor": "الكفيل",
"dependentSponsorDetail": "تفاصيل كفيل التابع",
"insuranceEligibility": "أهلية التأمين",
"relationshipStartDate": "تاريخ بدء العلاقة",
"dependentAddedSuccessfully": "تم إرسال الطلب بنجاح",
"genderMale": "ذكر",
"genderFemale": "أنثى",
"religionIslam": "الإسلام",
"back": "السابق",
"submitRequest": "إرسال الطلب",
"enterDependentDetailsToContinue": "أدخل تفاصيل التابع للمتابعة.",
"enterBasicDetailsDesc": "يرجى إدخال البيانات الأساسية للتابع.",
"locations": "الفروع والمواقع",
"availableOffers": "العروض المتاحة",
"selectOfferToRedeem": "اختر العرض الذي ترغب في استخدامه",
"viewOnMap": "عرض على الخريطة",
"showAllLocations": "عرض جميع الفروع ({})",
"showLess": "عرض أقل",
"searchBranches": "البحث عن الفروع أو المدن...",
"allCities": "الكل",
"noBranchesFound": "لم يتم العثور على فروع مطابقة",
"browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن",
"branchesCount": "{} فرع"
}

@ -570,7 +570,6 @@
"pleaseClickButtonToJoinMarathon": "Press the button below to join the Marathon.",
"youCannotJoinTheMarathon": "You cannot join the Marathon because you have exceeded the time limit.",
"requesterOperatingUnit": "Requester Operating Unit",
"generate": "Generate",
"paymentRequest": "Pay Request",
"paymentDetails": "Payment Details",
"requestNo": "Request No",
@ -667,15 +666,117 @@
"incidentDetails": "Incident Details",
"ticketNumber": "Ticket Number",
"ticketId": "Ticket ID",
"pleaseSelectProject": "Please select a project",
"viewAttachment" : "View Attachment",
"parkingQr": "Parking QR",
"startNow": "Start Now",
"destination_type": "Destination Type",
"source_organization_name": "Source Organization Name",
"pleaseSelectProject": "Please select a project",
"viewAttachment": "View Attachment",
"parkingQr": "Parking QR",
"startNow": "Start Now",
"destination_type": "Destination Type",
"source_organization_name": "Source Organization Name",
"destination_organization_name": "Destination Organization Name",
"costCenter": "Cost Center",
"priceSAR": "Price (SAR)",
"amountSAR": "Amount (SAR)",
"amuLastThreeMonths": "AMU (Last 3 months)"
}
"amuLastThreeMonths": "AMU (Last 3 months)",
"mazayaTitle": "Mazaya",
"mazayaAccessGranted": "Mazaya access granted",
"mazayaAccessGrantedDesc": "{} now has active access. Share the download link below so they can install the Mohem app and complete setup.",
"accessIsNowActive": "Access is now active",
"accessIsNowActiveDesc": "{} can use the provided link to download the Mazaya app and continue setup on their device.",
"dependentAccessActive": "Dependent access active",
"shareableAppDownloadLink": "Shareable app-download link",
"shareableAppDownloadLinkDesc": "Send this link to {} so they can download the Mazaya app and continue setup.",
"downloadLink": "Download link",
"shareDownloadLink": "Share Download Link",
"copyLink": "Copy Link",
"done": "Done",
"linkCopied": "Link copied to clipboard",
"mazayaAccessRevoked": "Mazaya access revoked",
"mazayaAccessRevokedDesc": "Your sponsor has revoked your dependent access. You can no longer open offers or sign in with biometrics.",
"contactYourSponsor": "Contact your sponsor",
"contactYourSponsorDesc": "Ask your sponsor to allow Mazaya access again from Dependent Details. Only the sponsor can restore access.",
"dependentAccessBlocked": "Dependent access blocked",
"returnToSignIn": "Return to Sign In",
"allowMazayaAccess": "Allow Mazaya Access",
"revokeMazayaAccess": "Revoke Mazaya Access",
"active": "Active",
"noAccess": "No access",
"offerDetails": "Offer Details",
"offerIdNotAvailable": "Offer ID not available",
"unableToRedeemOffer": "Unable to redeem offer, please try again later",
"errorRedeemingOffer": "An error occurred while redeeming offer",
"useOffer": "Use Offer",
"online": "Online",
"inStore": "In Store",
"ksa": "KSA",
"riyadh": "Riyadh",
"noInstructionsAvailable": "No instructions available.",
"contactInformation": "Contact Information:",
"humanResources": "Human Resources",
"drSulaimanAlHabib": "DR SULAIMAN AL HABIB",
"allCategories": "All",
"noOffersAvailable": "No offers available",
"addDependents": "Add Dependents",
"myDependents": "My Dependents",
"offDiscount": "{} Off",
"usageLabel": "Usage: ",
"areYouSureWantToLogOut": "Are you sure you want to log out?",
"searchDependents": "Search dependents",
"searchFamilyMembers": "Search family members",
"addAnotherUser": "Add another user",
"joinMazaya": "Join Mazaya",
"joinMazayaSubtitle": "Get the best offers and exclusive deals across dining, shopping, and lifestyle rewards.",
"continueAsEmployee": "Continue as an employee",
"continueAsEmployeeSubtitle": "Sign in with your employee username and password.",
"pleaseEnterNationalId": "Please enter National ID",
"pleaseSelectDob": "Please select Date of Birth",
"unableToRetrieveDependentInfo": "Unable to retrieve dependent information",
"failedToSendOtp": "Failed to send verification code",
"verificationCodeResent": "Verification code resent",
"nationalId": "National ID",
"addDependent": "Add Dependent",
"stepVerify": "Verify",
"stepBasic": "Basic",
"stepAdditional": "Additional",
"findYourDependent": "Find your dependent",
"findYourDependentDesc": "Enter the details exactly as they appear on the National ID. We'll securely retrieve the record from NHIC.",
"enter10DigitNationalId": "Enter 10-digit National ID",
"nationalIdMustBe10Digits": "National ID must be 10 digits",
"dateFormatPlaceholder": "DD / MM / YYYY",
"searchUser": "Search User",
"dependentNotFoundNhic": "Sorry, error while getting dependent info from NHIC",
"basicInfo": "Basic Info",
"basicInfoDesc": "Required identity details fetched from NHIC.",
"additionalInfo": "Additional Info",
"additionalInfoDesc": "Add or review sponsorship and eligibility details.",
"select": "Select",
"enterFieldPlaceholder": "Enter {}",
"requiredField": "Required field",
"pleaseFillField": "Please fill {}",
"reviewAndSubmit": "Review & Submit",
"reviewAndSubmitDesc": "Please review the dependent details carefully before submitting.",
"arabicName": "Arabic Name",
"relationship": "Relationship",
"sponsor": "Sponsor",
"dependentSponsorDetail": "Dependent Sponsor Detail",
"insuranceEligibility": "Insurance Eligibility",
"relationshipStartDate": "Start Date",
"dependentAddedSuccessfully": "Dependent added successfully",
"genderMale": "Male",
"genderFemale": "Female",
"religionIslam": "Islam",
"back": "Back",
"submitRequest": "Submit",
"enterDependentDetailsToContinue": "Enter the dependent details to continue.",
"enterBasicDetailsDesc": "Please enter the dependent's basic identity details.",
"locations": "Locations",
"availableOffers": "Available Offers",
"selectOfferToRedeem": "Select an offer to use",
"viewOnMap": "View on map",
"showAllLocations": "Show all {} locations",
"showLess": "Show less",
"searchBranches": "Search branches or cities...",
"allCities": "All",
"noBranchesFound": "No branches found",
"browseAllBranches": "Browse all {} branches across cities",
"branchesCount": "{} Branches"
}

@ -88,7 +88,7 @@ class ApiClient {
if (!kReleaseMode) {
print("Url:$url");
var bodyJson = json.encode(jsonObject);
print("body:$bodyJson");
print("body:$bodyJson" );
}
var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData);
try {

@ -1,13 +1,16 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/models/basic_member_information_model.dart';
import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/dependent/get_mobile_login_info_dependent_model.dart';
import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart';
import 'package:mohem_flutter_app/models/member_login_list_model.dart';
@ -30,6 +33,50 @@ class LoginApiClient {
}, url, postParams);
}
Future<GetMobileLoginInfoDependentModel?> getMobileLoginInfoNEWDependent(String deviceToken, String deviceType, {int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW_Dependent";
String effectiveDeviceToken = deviceToken;
if (effectiveDeviceToken.isEmpty) {
effectiveDeviceToken = AppState().getDeviceToken ?? "";
}
// if (effectiveDeviceToken.isEmpty && !AppState().getIsHuawei) {
// try {
// await Firebase.initializeApp();
// String? tok = await FirebaseMessaging.instance.getToken();
// if (tok != null && tok.isNotEmpty) {
// effectiveDeviceToken = tok;
// AppState().setDeviceToken = tok;
// }
// } catch (_) {}
// }
Map<String, dynamic> postParams = {};
postParams["DeviceToken"] = effectiveDeviceToken;
postParams["DeviceType"] = deviceType;
postParams["MemberType"] = memberType ?? MemberType.dependent.value;
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("Mohemm_GetMobileLoginInfo_NEW_Dependent Response: $json");
}
List? list = json['Mohemm_GetMobileLoginInfo_NEW_DependentList'] ??
json['Mohemm_GetMobileLoginInfo_NEW_Dependent'] ??
json['Mohemm_GetMobileLoginInfo_DependentList'] ??
json['Mohemm_GetMobileLoginInfoDependentList'] ??
json['Mohemm_GetMobileLoginInfoList'] ??
json['GetMobileLoginInfoList'] ??
json['Response'] ??
json['Data'];
if (list != null && list.isNotEmpty && list.first is Map<String, dynamic>) {
return GetMobileLoginInfoDependentModel.fromJson(list.first as Map<String, dynamic>);
}
if (json is Map<String, dynamic> &&
(json['DependentID'] != null || json['DepedentName'] != null || json['DOB'] != null || json['NationalID'] != null || json['LoginType'] != null)) {
return GetMobileLoginInfoDependentModel.fromJson(json);
}
return null;
}, url, postParams);
}
Future<GenericResponseModel?> insertMobileLoginInfoNEW(
String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String deviceToken, String deviceType,
{int? memberType}) async {
@ -56,6 +103,71 @@ class LoginApiClient {
}, url, postParams);
}
Future<GenericResponseModel?> insertMobileLoginInfoDependent({
required int loginType,
String? nationalId,
String? dob,
String? dependentName,
String? mobileNumber,
String? logInTokenID,
String? tokenID,
int? sessionId,
String? email,
String? deviceToken,
String? deviceType,
int? companyId,
int? versionId,
int? channel,
int? memberType,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo_Dependent";
String devType = deviceType ?? (Platform.isAndroid ? "android" : "ios");
String devToken = (deviceToken != null && deviceToken.isNotEmpty)
? deviceToken
: (AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? ""));
String effectiveToken = (tokenID != null && tokenID.isNotEmpty)
? tokenID
: (AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID ?? "");
Map<String, dynamic> postParams = {
"VersionID": versionId ?? 5,
"Channel": channel ?? 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MemberType": memberType ?? 1,
"MobileType": devType,
"MobileNumber": mobileNumber ?? "",
"UserName": nationalId ?? "",
"P_USER_NAME": nationalId ?? "",
"P_DEP_NATIONAL_ID": nationalId ?? "",
"P_DEP_BIRTH_DATE": dob ?? "",
"P_DEPENDENT_NAME": dependentName ?? "",
"EmployeeName": dependentName ?? "",
"LogInTokenID": logInTokenID ?? (AppState().postParamsObject?.logInTokenID ?? ""),
"CompanyID": companyId ?? 1,
"DeviceType": devType,
"DeviceToken": devToken,
"LoginType": loginType,
"P_SESSION_ID": sessionId ?? (AppState().postParamsObject?.pSessionId ?? 0),
"TokenID": effectiveToken,
"P_EMAIL_ADDRESS": email ?? "",
};
if (kDebugMode) {
print("Mohemm_InsertMobileLoginInfo (Dependent) Request: $postParams");
}
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("Mohemm_InsertMobileLoginInfo (Dependent) Response: $json");
}
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
AppState().setLogged = true;
return responseData;
}, url, postParams);
}
Future<CheckMobileAppVersionModel> checkMobileAppVersion() async {
String url = "${ApiConsts.utilitiesRest}CheckMobileAppVersion";
Map<String, dynamic> postParams = {};
@ -102,6 +214,7 @@ class LoginApiClient {
AppState().postParamsObject?.pUserName = AppState().getUserName;
AppState().postParamsObject?.pSelectedEmployeeNumber = AppState().getUserName;
AppState().postParamsObject?.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
AppState().postParamsInitConfig.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
AppState().postParamsObject?.setPayrollCodeStr = responseData.memberInformationList!.first.pAYROLLCODE;
AppState().setBusinessCardPrivilege = responseData.businessCardPrivilege ?? false;
return responseData;
@ -178,22 +291,62 @@ class LoginApiClient {
Future<GenericResponseModel?> memberLoginDependent({
required String nationalId,
required String birthDate,
String? deviceToken,
String? deviceType,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}MemberLogin_Dependent";
String devType = deviceType ?? (Platform.isAndroid ? "android" : "ios");
String devToken = deviceToken ?? "";
if (devToken.isEmpty) {
devToken = AppState().getIsHuawei
? AppState().getHuaweiPushToken
: (AppState().getDeviceToken ?? "");
}
if (devToken.isEmpty && !AppState().getIsHuawei) {
try {
String savedToken = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (savedToken.isNotEmpty) {
devToken = savedToken;
}
} catch (_) {}
}
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
"DeviceToken": devToken,
"DeviceType": devType,
"MobileType": devType,
"P_DEP_NATIONAL_ID": nationalId,
"P_DEP_BIRTH_DATE": birthDate,
};
if (kDebugMode) {
print("MemberLogin_Dependent Request: $postParams");
}
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("MemberLogin_Dependent Response: $json");
}
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
if (responseData.logInTokenID != null && responseData.logInTokenID!.isNotEmpty) {
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
}
String? authToken = (responseData.authenticationTokenID != null && responseData.authenticationTokenID!.trim().isNotEmpty)
? responseData.authenticationTokenID!.trim()
: null;
if (authToken != null && authToken.isNotEmpty) {
AppState().postParamsObject?.setTokenID = authToken;
AppState().setAuthenticationTokenID = authToken;
}
if (responseData.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = responseData.getWalaplusDepInfoModel;
if (responseData.getWalaplusDepInfoModel?.pDependentName != null &&
responseData.getWalaplusDepInfoModel!.pDependentName!.trim().isNotEmpty) {
AppState().setDependentName = responseData.getWalaplusDepInfoModel!.pDependentName!.trim();
}
}
AppState().setUserName = nationalId;
AppState().postParamsObject?.setUserName = nationalId;
AppState().postParamsObject?.setPUserName = nationalId;
@ -249,13 +402,12 @@ class LoginApiClient {
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
String effectiveToken = (responseData.tokenID != null && responseData.tokenID!.isNotEmpty)
? responseData.tokenID!
: ((responseData.logInTokenID != null && responseData.logInTokenID!.isNotEmpty)
? responseData.logInTokenID!
: logInTokenID);
String effectiveToken = (responseData.authenticationTokenID != null && responseData.authenticationTokenID!.trim().isNotEmpty)
? responseData.authenticationTokenID!.trim()
: (AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID ?? "");
AppState().postParamsObject?.setTokenID = effectiveToken;
AppState().setAuthenticationTokenID = effectiveToken;
AppState().postParamsObject?.setLogInTokenID = (responseData.logInTokenID != null && responseData.logInTokenID!.isNotEmpty)
? responseData.logInTokenID!
: logInTokenID;
@ -268,6 +420,14 @@ class LoginApiClient {
AppState().isAuthenticated = true;
AppState().setLogged = true;
if (responseData.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = responseData.getWalaplusDepInfoModel;
if (responseData.getWalaplusDepInfoModel?.pDependentName != null &&
responseData.getWalaplusDepInfoModel!.pDependentName!.trim().isNotEmpty) {
AppState().setDependentName = responseData.getWalaplusDepInfoModel!.pDependentName!.trim();
}
}
return responseData;
}, url, postParams);
}

@ -55,9 +55,9 @@ class MyAttendanceApiClient {
}, url, postParams);
}
Future<GenericResponseModel> getCEIDffStructure(String pFunctionName, String? empID) async {
Future<GenericResponseModel> getCEIDffStructure(String pFunctionName, String? empID, int? relationID) async {
String url = "${ApiConsts.erpRest}GET_CEI_DFF_STRUCTURE";
Map<String, dynamic> postParams = {"P_FUNCTION_NAME": pFunctionName};
Map<String, dynamic> postParams = {"P_FUNCTION_NAME": pFunctionName,"P_CONTACT_RELATIONSHIP_ID": relationID};
postParams.addAll(AppState().postParamsJson);
if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID;
return await ApiClient().postJsonForObject((json) {

@ -72,44 +72,116 @@ class OffersAndDiscountsApiClient {
);
}
Future<List<WalaPlusCategoryModel>> getWalaPlusCategories({BuildContext? context}) async {
String url = ApiConsts.walaPlusCategories;
String _resolveUserName() {
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = "2537367522";
pUserName = AppState().postParamsObject?.userName ??
AppState().postParamsObject?.pUserName ??
AppState().memberInformationList?.eMPLOYEENUMBER;
}
return pUserName ?? "";
}
String? tokenID = AppState().postParamsObject?.tokenID;
if (tokenID == null || tokenID.isEmpty || tokenID == "adm!n") {
tokenID = "@dm!n";
String _resolveDepNationalId() {
if (!ApiConsts.isDependetLogin) {
return "";
}
return AppState().getUserName ?? "";
}
String _resolveMobileNumber({String? overrideMobile}) {
if (overrideMobile != null && overrideMobile.trim().isNotEmpty) {
return overrideMobile.trim();
}
String? mobile = AppState().postParamsObject?.mobileNumber;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberInformationList?.eMPLOYEEMOBILENUMBER;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().walaplusDepInfoModel?.pEmpMobileNumber;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberLoginList?.pMOBILENUMBER;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberInformationList?.mobileNumberWithZipCode;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
return "";
}
String? logInTokenID = AppState().postParamsObject?.logInTokenID;
if (logInTokenID == null || logInTokenID.isEmpty) {
logInTokenID = "@dm!n";
Map<String, dynamic> _getBaseWalaPlusParams() {
Map<String, dynamic> base = {};
base.addAll(AppState().postParamsJson);
if (ApiConsts.isDependetLogin) {
String? token = AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID;
if (token != null && token.isNotEmpty) {
base["TokenID"] = token;
}
String? logInToken = AppState().postParamsObject?.logInTokenID;
if (logInToken != null && logInToken.isNotEmpty) {
base["LogInTokenID"] = logInToken;
}
}
_cleanWalaPlusParams(base);
return base;
}
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"LogInTokenID": logInTokenID,
"TokenID": tokenID,
"P_USER_NAME": pUserName,
};
void _cleanWalaPlusParams(Map<String, dynamic> map) {
map.remove("P_EMAIL_ADDRESS");
map.remove("P_SESSION_ID");
map.remove("PayrollCodeStr");
map.remove("LegislationCodeStr");
map.remove("P_SELECTED_EMPLOYEE_NUMBER");
map.removeWhere((key, value) => value == null);
}
Future<List<WalaPlusCategoryModel>> getWalaPlusCategories({BuildContext? context, String? mobileNumber}) async {
String url = ApiConsts.walaPlusCategories;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
_cleanWalaPlusParams(postParams);
return await ApiClient().postJsonForObject(
(response) {
List<WalaPlusCategoryModel> categories = [];
if (response != null && response['WalaPlus_Categories'] != null) {
var cats = response['WalaPlus_Categories'];
if (response != null) {
dynamic cats = response['WalaPlus_Categories'] ??
response['walaPlus_Categories'] ??
response['WalaPlusCategories'] ??
response['result'];
if (cats is String) {
cats = json.decode(cats);
try {
cats = json.decode(cats);
} catch (e) {
debugPrint("Error decoding WalaPlus_Categories: $e");
}
}
if (cats is List) {
cats.forEach((v) {
categories.add(WalaPlusCategoryModel.fromJson(v));
if (v is Map<String, dynamic>) {
categories.add(WalaPlusCategoryModel.fromJson(v));
} else if (v is Map) {
categories.add(WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(v)));
}
});
}
}
@ -126,51 +198,61 @@ class OffersAndDiscountsApiClient {
int perPage = 10,
String? geoLocation,
BuildContext? context,
String? mobileNumber,
}) async {
String url = ApiConsts.walaPlusRetailers;
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = "2537367522";
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
String effectiveCatId = (categoryId == "all" || categoryId == "0" || categoryId == null) ? "" : categoryId;
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
String? tokenID = AppState().postParamsObject?.tokenID;
if (tokenID == null || tokenID.isEmpty) {
tokenID = "adm!n";
}
String? logInTokenID = AppState().postParamsObject?.logInTokenID;
if (logInTokenID == null || logInTokenID.isEmpty) {
logInTokenID = "@dm!n";
}
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"LogInTokenID": logInTokenID,
"TokenID": tokenID,
"P_USER_NAME": pUserName,
"WP_CategoryID": (categoryId == "all" || categoryId == null) ? "" : categoryId,
"WP_Page": page,
"WP_Per_page": perPage,
};
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
postParams["WP_CategoryID"] = effectiveCatId;
postParams["WP_Page"] = page;
postParams["WP_Per_page"] = perPage;
if (geoLocation != null && geoLocation.isNotEmpty) {
postParams["WP_Geo_Location"] = geoLocation;
}
_cleanWalaPlusParams(postParams);
return await ApiClient().postJsonForObject(
(response) {
if (response != null && response['WalaPlus_Retailers'] != null) {
var val = response['WalaPlus_Retailers'];
if (response != null) {
dynamic val = response['WalaPlus_Retailers'] ??
response['walaPlus_Retailers'] ??
response['WalaPlusRetailers'] ??
response['WP_Retailers'] ??
response['result'];
if (val is String) {
val = json.decode(val);
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_Retailers: $e");
}
}
if (val is Map<String, dynamic>) {
return WalaPlusRetailersResponse.fromJson(val);
} else if (val is Map) {
return WalaPlusRetailersResponse.fromJson(Map<String, dynamic>.from(val));
} else if (val is List) {
List<WalaPlusRetailerModel> retailers = [];
for (var v in val) {
if (v is Map<String, dynamic>) {
retailers.add(WalaPlusRetailerModel.fromJson(v));
} else if (v is Map) {
retailers.add(WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(v)));
}
}
return WalaPlusRetailersResponse(retailers: retailers, page: 1, pagesCount: 1, perPage: retailers.length);
}
}
return null;
@ -183,48 +265,161 @@ class OffersAndDiscountsApiClient {
Future<WalaPlusRetailerModel?> getWalaPlusRetailerById({
required String retailerId,
BuildContext? context,
String? mobileNumber,
}) async {
String url = ApiConsts.walaPlusRetailersByID;
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = "2537367522";
}
String? tokenID = AppState().postParamsObject?.tokenID;
if (tokenID == null || tokenID.isEmpty) {
tokenID = "adm!n";
}
String? logInTokenID = AppState().postParamsObject?.logInTokenID;
if (logInTokenID == null || logInTokenID.isEmpty) {
logInTokenID = "@dm!n";
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["WP_RetailerID"] = retailerId;
postParams["RetailerID"] = retailerId;
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
_cleanWalaPlusParams(postParams);
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"LogInTokenID": logInTokenID,
"TokenID": tokenID,
"RetailerID": retailerId,
"P_USER_NAME": pUserName,
};
debugPrint("getWalaPlusRetailerById: calling $url with WP_RetailerID: $retailerId");
return await ApiClient().postJsonForObject(
(response) {
if (response != null && response['WalaPlus_RetailersByID'] != null) {
var val = response['WalaPlus_RetailersByID'];
if (response != null) {
debugPrint("getWalaPlusRetailerById response keys: ${response is Map ? response.keys.toList() : 'non-map'}");
dynamic val = response['WalaPlus_RetailersByID'] ??
response['walaPlus_RetailersByID'] ??
response['WalaPlusRetailersByID'] ??
response['WP_RetailersByID'] ??
response['result'];
if (val is String) {
val = json.decode(val);
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_RetailersByID JSON string: $e");
}
}
WalaPlusRetailerModel? model;
if (val is List && val.isNotEmpty) {
var item = val.first;
if (item is Map<String, dynamic>) {
return WalaPlusRetailerModel.fromJson(item);
model = WalaPlusRetailerModel.fromJson(item);
} else if (item is Map) {
return WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(item));
model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(item));
}
} else if (val is Map) {
Map<String, dynamic> map = (val is Map<String, dynamic>) ? val : Map<String, dynamic>.from(val);
if (map.containsKey('retailer')) {
var byIdResponse = WalaPlusRetailersByIDResponse.fromJson(map);
model = byIdResponse.primaryRetailer ?? WalaPlusRetailerModel.fromJson(map);
} else {
model = WalaPlusRetailerModel.fromJson(map);
}
if ((model?.offers == null || model!.offers!.isEmpty) && (map.containsKey('offers') || map.containsKey('Offers'))) {
model?.offers = WalaPlusRetailerModel.parseOffersList(map['offers'] ?? map['Offers']);
}
if ((model?.locations == null || model!.locations!.isEmpty) &&
(map.containsKey('locations') || map.containsKey('Locations') || map.containsKey('location') || map.containsKey('Location'))) {
model?.locations = WalaPlusRetailerModel.parseLocationsList(
map['locations'] ?? map['Locations'] ?? map['location'] ?? map['Location'],
);
}
} else if (response is Map<String, dynamic> && response.containsKey('name')) {
model = WalaPlusRetailerModel.fromJson(response);
}
if (model != null) {
if (response is Map) {
if ((model.offers == null || model.offers!.isEmpty) && (response.containsKey('offers') || response.containsKey('Offers'))) {
model.offers = WalaPlusRetailerModel.parseOffersList(response['offers'] ?? response['Offers']);
}
if ((model.locations == null || model.locations!.isEmpty) &&
(response.containsKey('locations') || response.containsKey('Locations') || response.containsKey('location') || response.containsKey('Location'))) {
model.locations = WalaPlusRetailerModel.parseLocationsList(
response['locations'] ?? response['Locations'] ?? response['location'] ?? response['Location'],
);
}
}
debugPrint("getWalaPlusRetailerById: parsed retailer '${model.name}', offers: ${model.offers?.length ?? 0}, locations: ${model.locations?.length ?? 0}");
return model;
}
}
return null;
},
url,
postParams,
);
}
String _resolveUserReference({String? overrideUserReference}) {
if (overrideUserReference != null && overrideUserReference.trim().isNotEmpty) {
return overrideUserReference.trim();
}
String? ref = AppState().memberInformationList?.eMPLOYEENUMBER?.toString();
if (ref != null && ref.trim().isNotEmpty) {
return ref.trim();
}
String? pUserName = AppState().getUserName;
if (pUserName != null && pUserName.trim().isNotEmpty) {
return pUserName.trim();
}
return "";
}
Future<Map<String, dynamic>?> redeemWalaPlusOffer({
required String offerId,
String? offerType,
String? userReference,
BuildContext? context,
}) async {
String url = ApiConsts.walaPlusOfferRedeem;
String pUserName = _resolveUserName();
String pUserReference = _resolveUserReference(overrideUserReference: userReference);
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["WP_User_Reference"] = pUserReference;
postParams["WP_Offer_Id"] = offerId;
postParams["WP_Offer_Type"] = (offerType != null && offerType.trim().isNotEmpty) ? offerType.trim() : "promo";
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
if (ApiConsts.isDependetLogin) {
String pDepNationalId = _resolveDepNationalId();
if (pDepNationalId.isNotEmpty) {
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
}
postParams["MemberType"] = 2;
} else {
postParams["P_DEP_NATIONAL_ID"] = "";
postParams["MemberType"] = 1;
}
_cleanWalaPlusParams(postParams);
debugPrint("redeemWalaPlusOffer calling $url with params: $postParams");
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
debugPrint("redeemWalaPlusOffer response: $response");
dynamic val = response['WalaPlus_OfferRedeem'] ??
response['walaPlus_OfferRedeem'] ??
response['WalaPlusOfferRedeem'] ??
response['result'];
if (val is String) {
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_OfferRedeem: $e");
}
}
if (val is Map<String, dynamic>) {
return val;
} else if (val is Map) {
return Map<String, dynamic>.from(val);
}
}
return null;

@ -24,6 +24,8 @@ import 'package:mohem_flutter_app/models/profile/submit_address_transaction.dart
import 'package:mohem_flutter_app/models/profile/submit_basic_details_transaction_model.dart';
import 'package:mohem_flutter_app/models/profile/submit_contact_transaction_list_model.dart';
import 'package:mohem_flutter_app/models/profile/submit_phone_transactions.dart';
import 'package:mohem_flutter_app/models/profile/patient_info_model.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/models/start_phone_approval_process_model.dart';
class ProfileApiClient {
@ -417,4 +419,62 @@ class ProfileApiClient {
return responseData.getPerformanceAppraisalList ?? [];
}, url, postParams);
}
Future<PatientInfoModel?> getPatientInfo({
required String nationalId,
required String dob,
String? logInTokenID,
}) async {
// NHIC Service GetPatientInfo only works on Production (hmgwebservices.com)
// String url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo";
String url = "${ApiConsts.erpRest}GetMemberInfo";
String token = logInTokenID ??
AppState().postParamsObject?.logInTokenID ??
"";
Map<String, dynamic> postParams = {
"PatientIdentificationID": int.tryParse(nationalId.trim()) ?? nationalId.trim(),
"PatientOutSA": 0,
"DOB": dob.trim(),
"LogInTokenID": token,
"PatientMobileNumber":AppState().postParamsObject?.mobileNumber, // added this as per e-services changed
"TokenID":AppState().postParamsObject?.tokenID,// added this as per e-services changed
"LanguageID":AppState().postParamsObject?.languageID,// added this as per e-services changed
"Channel":AppState().postParamsObject?.channel,
"VersionID": AppState().postParamsObject?.versionID,
"UserName":AppState().postParamsObject?.userName
};
print(jsonEncode(postParams));
return await ApiClient().postJsonForObject((json) {
return PatientInfoModel.fromJson(json);
}, url, postParams);
}
Future<GenericResponseModel?> processContactMazayaAccess({
required String accessAction,
int? contactRelationshipId,
int? contactPersonId,
int? personId,
}) async {
String url = "${ApiConsts.erpRest}PROCESS_CONTACT_MAZAYA_ACCESS";
String action = accessAction.trim().toLowerCase() == "revoke" ? "Revoke" : "Grant";
Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"P_ACCESS_ACTION": action,
};
if (contactRelationshipId != null) {
postParams["P_CONTACT_RELATIONSHIP_ID"] = contactRelationshipId;
}
if (contactPersonId != null) {
postParams["P_CONTACT_PERSON_ID"] = contactPersonId;
}
if (personId != null) {
postParams["P_PERSON_ID"] = personId;
}
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject((json) {
return GenericResponseModel.fromJson(json);
}, url, postParams);
}
}

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart';
import 'package:mohem_flutter_app/models/itg_forms_models/request_detail_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/member_information_list_model.dart';
import 'package:mohem_flutter_app/models/member_login_list_model.dart';
import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list.dart';
@ -92,11 +93,58 @@ class AppState {
final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 31, versionID: 10.1, mobileType: Platform.isAndroid ? "android" : "ios");
PostParamsModel get postParamsInitConfig {
if (_postParamsInitConfig.pLegislationCode == null) {
if (_postParams?.pLegislationCode != null) {
_postParamsInitConfig.pLegislationCode = _postParams?.pLegislationCode;
} else if (_memberLoginList?.pLEGISLATIONCODE != null) {
_postParamsInitConfig.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE;
}
}
return _postParamsInitConfig;
}
GetWalaplusDepInfoModel? _walaplusDepInfoModel;
GetWalaplusDepInfoModel? get walaplusDepInfoModel => _walaplusDepInfoModel;
set setWalaplusDepInfoModel(GetWalaplusDepInfoModel? model) => _walaplusDepInfoModel = model;
String? _dependentName;
String? get dependentName => _dependentName ?? _walaplusDepInfoModel?.pDependentName;
set setDependentName(String? name) => _dependentName = name;
int? _dependentLoginType;
int? get dependentLoginType => _dependentLoginType;
set setDependentLoginType(int? type) => _dependentLoginType = type;
String? _dependentDob;
String? get dependentDob => _dependentDob;
set setDependentDob(String? dob) => _dependentDob = dob;
String? _authenticationTokenID;
String? get authenticationTokenID => _authenticationTokenID;
set setAuthenticationTokenID(String? token) => _authenticationTokenID = token;
void setPostParamsInitConfig() {
isAuthenticated = false;
isLogged = false;
_memberLoginList = null;
_memberInformationList = null;
_walaplusDepInfoModel = null;
_dependentName = null;
_dependentLoginType = null;
_dependentDob = null;
_authenticationTokenID = null;
_postParamsInitConfig.pLegislationCode = null;
_postParams = _postParamsInitConfig;
}
@ -143,7 +191,13 @@ class AppState {
MemberLoginListModel? get memberLoginList => _memberLoginList;
set setMemberLoginListModel(MemberLoginListModel? _memberLoginList) => this._memberLoginList = _memberLoginList;
set setMemberLoginListModel(MemberLoginListModel? _memberLoginList) {
this._memberLoginList = _memberLoginList;
if (_memberLoginList?.pLEGISLATIONCODE != null) {
_postParamsInitConfig.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE?.trim().toUpperCase();
_postParams?.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE?.trim().toUpperCase();
}
}
MemberInformationListModel? _memberInformationList;

@ -1,7 +1,7 @@
import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart';
class ApiConsts {
// static String baseUrl = "http://10.200.204.11"; // Local server
// static String baseUrl = "http://10.201.214.101:2027"; // Local server
// static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server
// static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
@ -9,10 +9,10 @@ class ApiConsts {
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://hmgwebservices.com"; // Live server
// static String baseUrl = "https://mohemm.hmg.com"; // New Live server
static String baseUrl = "https://mohemm.hmg.com"; // New Live server
static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server
//
static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "http://10.20.200.111:1010/";
// static String baseUrl = "https://mohemm.hmg.com"; // New Live server
//static String eLearningBaseUrl = "https://elearning.hmg.com/moodle_dev/api/"; // Elearning server UAT
@ -43,6 +43,8 @@ class ApiConsts {
static String walaPlusCategories = erpRest + "WalaPlus_Categories";
static String walaPlusRetailers = erpRest + "WalaPlus_Retailers";
static String walaPlusRetailersByID = erpRest + "WalaPlus_RetailersByID";
static String walaPlusOfferRedeem = erpRest + "WalaPlus_OfferRedeem";
static String processContactMazayaAccess = erpRest + "PROCESS_CONTACT_MAZAYA_ACCESS";
static String ssoAuthRedirection = ssoBaseUrl + "/api/auth/connect";

@ -1,6 +1,7 @@
import 'dart:developer';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
class MyInAppBrowser extends InAppBrowser {
final Function onExitCallback;
@ -30,4 +31,21 @@ class MyInAppBrowser extends InAppBrowser {
void onLoadError(Uri? url, int code, String message) {
log("Can't load $url.. Error: $message");
}
@override
Future<PermissionResponse?> onPermissionRequest(PermissionRequest permissionRequest) async {
log("onPermissionRequest: ${permissionRequest.resources}");
try {
var status = await Permission.camera.status;
if (!status.isGranted) {
await Permission.camera.request();
}
} catch (e) {
log("Error checking/requesting camera permission: $e");
}
return PermissionResponse(
resources: permissionRequest.resources,
action: PermissionResponseAction.GRANT,
);
}
}

@ -393,7 +393,11 @@ class Utils {
AppState().isLogged = false;
AppState().setPostParamsInitConfig();
if (chatData != null) {
chatData.disposeData();
try {
chatData.disposeData();
} catch (e) {
}
}
// SharedPreferences prefs = await SharedPreferences.getInstance();
// await prefs.clear();

@ -48,6 +48,8 @@ import 'package:mohem_flutter_app/ui/my_team/view_attendance.dart';
import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart';
import 'package:mohem_flutter_app/ui/payslip/monthly_pay_slip_screen.dart';
import 'package:mohem_flutter_app/ui/performance/performance_appraisal_screen.dart';
import 'package:mohem_flutter_app/ui/profile/add_dependent_verify_screen.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart';
import 'package:mohem_flutter_app/ui/profile/basic_details.dart';
import 'package:mohem_flutter_app/ui/profile/contact_details.dart';
@ -85,6 +87,7 @@ import 'package:mohem_flutter_app/ui/work_list/itg_detail_screen.dart';
import 'package:mohem_flutter_app/ui/work_list/work_list_screen.dart';
import 'package:mohem_flutter_app/ui/work_list/worklist_detail_screen.dart';
import 'package:mohem_flutter_app/ui/work_list/worklist_settings.dart';
import 'package:mohem_flutter_app/widgets/app_in_app_webview_screen.dart';
class AppRoutes {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@ -146,6 +149,8 @@ class AppRoutes {
static const String familyMembers = "/familyMembers";
static const String deleteFamilyMember = "/deleteFamilyMember";
static const String addUpdateFamilyMember = "/addUpdateFamilyMember";
static const String addDependentVerify = "/addDependentVerify";
static const String mazayaAccessResult = "/mazayaAccessResult";
// Mowadhafhi
static const String mowadhafhi = "/mowadhafhi";
@ -229,6 +234,7 @@ class AppRoutes {
//Parking QR
static const String parkingQr = "/parkingQrScreen";
static const String appInAppWebView = "/appInAppWebView";
static final Map<String, WidgetBuilder> routes = {
login: (BuildContext context) => LoginScreen(),
@ -237,6 +243,16 @@ class AppRoutes {
dashboard: (BuildContext context) => DashboardScreen(),
dashboardScreenForDependet: (BuildContext context) => const DashboardScreenForDependet(),
walaPlusRetailerDetails: (BuildContext context) => const WalaPlusRetailerDetailsScreen(),
appInAppWebView: (BuildContext context) {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is Map<String, dynamic>) {
return AppInAppWebViewScreen(
url: args['url'] ?? "",
title: args['title'],
);
}
return const Scaffold();
},
survey: (BuildContext context) => SurveyScreen(),
advertisement: (BuildContext context) => ITGAdsScreen(),
@ -287,6 +303,8 @@ class AppRoutes {
deleteFamilyMember: (BuildContext context) => DeleteFamilyMember(ModalRoute.of(context)!.settings.arguments as int),
requestSubmitScreen: (BuildContext context) => RequestSubmitScreen(),
addUpdateFamilyMember: (BuildContext context) => AddUpdateFamilyMember(),
addDependentVerify: (BuildContext context) => const AddDependentVerifyScreen(),
mazayaAccessResult: (BuildContext context) => const MazayaAccessResultScreen(),
//mowadhafhi
mowadhafhi: (BuildContext context) => MowadhafhiHome(),
@ -358,7 +376,6 @@ class AppRoutes {
etqanOvr: (BuildContext context) => EtqanOvrHome(),
etqanCreateRequest: (BuildContext context) => EtqanOvrCreateRequest(),
etqanGetIncidientRequest: (BuildContext context) => EtqanOvrRequestDetailed(),
activityScreen:(BuildContext context) => const EventActivityScreen(),
//Parking QR
parkingQr: (BuildContext context) => const ParkingQrScreen(),

@ -134,14 +134,16 @@ extension EmailValidator on String {
),
);
Widget toText13({Color? color, bool isUnderLine = false}) => Text(
Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, int? maxlines, TextOverflow? overflow}) => Text(
this,
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null),
maxLines: maxlines,
overflow: overflow,
style: TextStyle(fontSize: 13, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null),
);
Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines, TextAlign? textAlign, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : (textAlign ?? TextAlign.left),
textAlign: isCenter ? TextAlign.center : textAlign,
maxLines: maxlines,
style: TextStyle(
color: color ?? MyColors.darkTextColor,

@ -336,6 +336,8 @@ class CodegenLoader extends AssetLoader{
"createdFor": "انشاء لأجل",
"requestCreatedSuccessfully": "تم انشاء الطلب بنجاح",
"search": "بحث",
"filterEnterPrDetailsHere": "ادخل تفاصيل طلب الشراء هنا",
"filterEnterPoDetailsHere": "ادخل تفاصيل أمر الشراء هنا",
"wantToReject": "هل انت متأكد تريد الرفض",
"requestType": "نوع الطلب",
"employeeDigitalID": "هويةالموظف الرقمية",
@ -395,7 +397,7 @@ class CodegenLoader extends AssetLoader{
"browseCategories": "تصفح الفئات",
"searchItems": "عناصر البحث",
"offerAndDiscounts": "العروض والخصومات",
"offerValid": "العرض صالح",
"offerValid": "العرض ساري",
"offerExpired": "انتهى العرض",
"whatAreYouOffering": "ما الذي تعرضه؟",
"selectCategory": "اختر الفئة",
@ -690,7 +692,109 @@ class CodegenLoader extends AssetLoader{
"costCenter": "مركز التكلفة",
"priceSAR": "السعر (ريال سعودي)",
"amountSAR": "المبلغ (ريال سعودي)",
"amuLastThreeMonths": "AMU (آخر 3 أشهر)"
"amuLastThreeMonths": "AMU (آخر 3 أشهر)",
"mazayaTitle": "مزايا",
"mazayaAccessGranted": "تم منح حق الوصول لمزايا",
"mazayaAccessGrantedDesc": "أصبح لدى {} حق وصول نشط الآن. شارك رابط التنزيل أدناه ليتمكن من تثبيت تطبيق مهم وإكمال الإعداد.",
"accessIsNowActive": "الوصول نشط الآن",
"accessIsNowActiveDesc": "يمكن لـ {} استخدام الرابط المرفق لتنزيل تطبيق مزايا ومتابعة الإعداد على جهازه.",
"dependentAccessActive": "وصول التابع نشط",
"shareableAppDownloadLink": "رابط تنزيل التطبيق للمشاركة",
"shareableAppDownloadLinkDesc": "أرسل هذا الرابط إلى {} ليتمكن من تنزيل تطبيق مزايا ومتابعة الإعداد.",
"downloadLink": "رابط التنزيل",
"shareDownloadLink": "مشاركة رابط التنزيل",
"copyLink": "نسخ الرابط",
"done": "تم",
"linkCopied": "تم نسخ الرابط بنجاح",
"mazayaAccessRevoked": "تم إلغاء الوصول لمزايا",
"mazayaAccessRevokedDesc": "قام الكفيل بإلغاء وصول التابع. لم يعد بالإمكان فتح العروض أو تسجيل الدخول بالبصمة.",
"contactYourSponsor": "تواصل مع الكفيل",
"contactYourSponsorDesc": "اطلب من كفيلك السماح بالوصول لمزايا مرة أخرى من تفاصيل التابعين. يمكن للكفيل فقط استعادة الوصول.",
"dependentAccessBlocked": "تم حظر وصول التابع",
"returnToSignIn": "العودة إلى تسجيل الدخول",
"allowMazayaAccess": "السماح بالوصول لمزايا",
"revokeMazayaAccess": "إلغاء الوصول لمزايا",
"active": "نشط",
"noAccess": "غير متاح",
"offerDetails": "تفاصيل العرض",
"offerIdNotAvailable": "معرف العرض غير متوفر",
"unableToRedeemOffer": "تعذر الحصول على رابط العرض، يرجى المحاولة لاحقاً",
"errorRedeemingOffer": "حدث خطأ أثناء استخدام العرض",
"useOffer": "استخدام العرض",
"online": "عبر الإنترنت",
"inStore": "في المتجر",
"ksa": "المملكة",
"riyadh": "الرياض",
"noInstructionsAvailable": "لا توجد طريقة استخدام محددة.",
"contactInformation": "معلومات التواصل:",
"humanResources": "الموارد البشرية",
"drSulaimanAlHabib": "سليمان الحبيب",
"allCategories": "الكل",
"noOffersAvailable": "لا توجد عروض متاحة",
"addDependents": "إضافة تابعين",
"myDependents": "التابعون",
"offDiscount": "خصم {}",
"usageLabel": "الاستخدام: ",
"areYouSureWantToLogOut": "هل أنت متأكد من تسجيل الخروج؟",
"searchDependents": "البحث في التابعين",
"searchFamilyMembers": "البحث في أفراد العائلة",
"addAnotherUser": "إضافة مستخدم آخر",
"joinMazaya": "انضم إلى مزايا",
"joinMazayaSubtitle": "احصل على أفضل العروض والصفقات الحصرية في المطاعم والتسوق ومكافآت نمط الحياة.",
"continueAsEmployee": "المتابعة كموظف",
"continueAsEmployeeSubtitle": "سجل الدخول باستخدام اسم المستخدم وكلمة المرور الخاصة بالموظف.",
"pleaseEnterNationalId": "الرجاء إدخال رقم الهوية الوطنية",
"pleaseSelectDob": "الرجاء تحديد تاريخ الميلاد",
"unableToRetrieveDependentInfo": "تعذر الحصول على معلومات التابع",
"failedToSendOtp": "فشل إرسال رمز التحقق",
"verificationCodeResent": "تم إعادة إرسال رمز التحقق",
"nationalId": "الهوية الوطنية",
"addDependent": "إضافة تابع",
"stepVerify": "التحقق",
"stepBasic": "الأساسية",
"stepAdditional": "إضافية",
"findYourDependent": "البحث عن تابعك",
"findYourDependentDesc": "أدخل التفاصيل تماماً كما تظهر في الهوية الوطنية. سنقوم باسترداد السجل بأمان من مركز المعلومات الوطني (NHIC).",
"enter10DigitNationalId": "أدخل رقم الهوية المكون من 10 أرقام",
"nationalIdMustBe10Digits": "يجب أن يتكون رقم الهوية من 10 أرقام",
"dateFormatPlaceholder": "يوم / شهر / سنة",
"searchUser": "بحث عن المستخدم",
"dependentNotFoundNhic": "لم يتم العثور على سجل لهذا التابع في مركز المعلومات الوطني",
"basicInfo": "المعلومات الأساسية",
"basicInfoDesc": "بيانات الهوية المطلوبة المستردة من مركز المعلومات الوطني.",
"additionalInfo": "معلومات إضافية",
"additionalInfoDesc": "إضافة أو مراجعة بيانات الكفالة والأهلية.",
"select": "اختر",
"enterFieldPlaceholder": "أدخل {}",
"requiredField": "الحقل المطلوب",
"pleaseFillField": "الرجاء إدخال {}",
"reviewAndSubmit": "المراجعة والإرسال",
"reviewAndSubmitDesc": "يرجى مراجعة تفاصيل التابع بعناية قبل تأكيد الإرسال.",
"arabicName": "الاسم بالعربية",
"relationship": "صلة القرابة",
"sponsor": "الكفيل",
"dependentSponsorDetail": "تفاصيل كفيل التابع",
"insuranceEligibility": "أهلية التأمين",
"relationshipStartDate": "تاريخ بدء العلاقة",
"dependentAddedSuccessfully": "تم إرسال الطلب بنجاح",
"genderMale": "ذكر",
"genderFemale": "أنثى",
"religionIslam": "الإسلام",
"back": "السابق",
"submitRequest": "إرسال الطلب",
"enterDependentDetailsToContinue": "أدخل تفاصيل التابع للمتابعة.",
"enterBasicDetailsDesc": "يرجى إدخال البيانات الأساسية للتابع.",
"locations": "الفروع والمواقع",
"availableOffers": "العروض المتاحة",
"selectOfferToRedeem": "اختر العرض الذي ترغب في استخدامه",
"viewOnMap": "عرض على الخريطة",
"showAllLocations": "عرض جميع الفروع ({})",
"showLess": "عرض أقل",
"searchBranches": "البحث عن الفروع أو المدن...",
"allCities": "الكل",
"noBranchesFound": "لم يتم العثور على فروع مطابقة",
"browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن",
"branchesCount": "{} فرع"
};
static const Map<String,dynamic> _en_US = {
"mohemm": "Mohemm",
@ -1009,6 +1113,8 @@ static const Map<String,dynamic> _en_US = {
"requestType": "Request Type",
"requestCreatedSuccessfully": "Request created successfully",
"search": "Search",
"filterEnterPrDetailsHere": "Enter PR Details here",
"filterEnterPoDetailsHere": "Enter PO Details here",
"wantToReject": "Are you sure want to reject?",
"employeeDigitalID": "Employee Digital ID",
"businessCard": "Business Card",
@ -1368,7 +1474,109 @@ static const Map<String,dynamic> _en_US = {
"costCenter": "Cost Center",
"priceSAR": "Price (SAR)",
"amountSAR": "Amount (SAR)",
"amuLastThreeMonths": "AMU (Last 3 months)"
"amuLastThreeMonths": "AMU (Last 3 months)",
"mazayaTitle": "Mazaya",
"mazayaAccessGranted": "Mazaya access granted",
"mazayaAccessGrantedDesc": "{} now has active access. Share the download link below so they can install the Mohem app and complete setup.",
"accessIsNowActive": "Access is now active",
"accessIsNowActiveDesc": "{} can use the provided link to download the Mazaya app and continue setup on their device.",
"dependentAccessActive": "Dependent access active",
"shareableAppDownloadLink": "Shareable app-download link",
"shareableAppDownloadLinkDesc": "Send this link to {} so they can download the Mazaya app and continue setup.",
"downloadLink": "Download link",
"shareDownloadLink": "Share Download Link",
"copyLink": "Copy Link",
"done": "Done",
"linkCopied": "Link copied to clipboard",
"mazayaAccessRevoked": "Mazaya access revoked",
"mazayaAccessRevokedDesc": "Your sponsor has revoked your dependent access. You can no longer open offers or sign in with biometrics.",
"contactYourSponsor": "Contact your sponsor",
"contactYourSponsorDesc": "Ask your sponsor to allow Mazaya access again from Dependent Details. Only the sponsor can restore access.",
"dependentAccessBlocked": "Dependent access blocked",
"returnToSignIn": "Return to Sign In",
"allowMazayaAccess": "Allow Mazaya Access",
"revokeMazayaAccess": "Revoke Mazaya Access",
"active": "Active",
"noAccess": "No access",
"offerDetails": "Offer Details",
"offerIdNotAvailable": "Offer ID not available",
"unableToRedeemOffer": "Unable to redeem offer, please try again later",
"errorRedeemingOffer": "An error occurred while redeeming offer",
"useOffer": "Use Offer",
"online": "Online",
"inStore": "In Store",
"ksa": "KSA",
"riyadh": "Riyadh",
"noInstructionsAvailable": "No instructions available.",
"contactInformation": "Contact Information:",
"humanResources": "Human Resources",
"drSulaimanAlHabib": "DR SULAIMAN AL HABIB",
"allCategories": "All",
"noOffersAvailable": "No offers available",
"addDependents": "Add Dependents",
"myDependents": "My Dependents",
"offDiscount": "{} Off",
"usageLabel": "Usage: ",
"areYouSureWantToLogOut": "Are you sure you want to log out?",
"searchDependents": "Search dependents",
"searchFamilyMembers": "Search family members",
"addAnotherUser": "Add another user",
"joinMazaya": "Join Mazaya",
"joinMazayaSubtitle": "Get the best offers and exclusive deals across dining, shopping, and lifestyle rewards.",
"continueAsEmployee": "Continue as an employee",
"continueAsEmployeeSubtitle": "Sign in with your employee username and password.",
"pleaseEnterNationalId": "Please enter National ID",
"pleaseSelectDob": "Please select Date of Birth",
"unableToRetrieveDependentInfo": "Unable to retrieve dependent information",
"failedToSendOtp": "Failed to send verification code",
"verificationCodeResent": "Verification code resent",
"nationalId": "National ID",
"addDependent": "Add Dependent",
"stepVerify": "Verify",
"stepBasic": "Basic",
"stepAdditional": "Additional",
"findYourDependent": "Find your dependent",
"findYourDependentDesc": "Enter the details exactly as they appear on the National ID. We'll securely retrieve the record from NHIC.",
"enter10DigitNationalId": "Enter 10-digit National ID",
"nationalIdMustBe10Digits": "National ID must be 10 digits",
"dateFormatPlaceholder": "DD / MM / YYYY",
"searchUser": "Search User",
"dependentNotFoundNhic": "Sorry, error while getting dependent info from NHIC",
"basicInfo": "Basic Info",
"basicInfoDesc": "Required identity details fetched from NHIC.",
"additionalInfo": "Additional Info",
"additionalInfoDesc": "Add or review sponsorship and eligibility details.",
"select": "Select",
"enterFieldPlaceholder": "Enter {}",
"requiredField": "Required field",
"pleaseFillField": "Please fill {}",
"reviewAndSubmit": "Review & Submit",
"reviewAndSubmitDesc": "Please review the dependent details carefully before submitting.",
"arabicName": "Arabic Name",
"relationship": "Relationship",
"sponsor": "Sponsor",
"dependentSponsorDetail": "Dependent Sponsor Detail",
"insuranceEligibility": "Insurance Eligibility",
"relationshipStartDate": "Start Date",
"dependentAddedSuccessfully": "Dependent added successfully",
"genderMale": "Male",
"genderFemale": "Female",
"religionIslam": "Islam",
"back": "Back",
"submitRequest": "Submit",
"enterDependentDetailsToContinue": "Enter the dependent details to continue.",
"enterBasicDetailsDesc": "Please enter the dependent's basic identity details.",
"locations": "Locations",
"availableOffers": "Available Offers",
"selectOfferToRedeem": "Select an offer to use",
"viewOnMap": "View on map",
"showAllLocations": "Show all {} locations",
"showLess": "Show less",
"searchBranches": "Search branches or cities...",
"allCities": "All",
"noBranchesFound": "No branches found",
"browseAllBranches": "Browse all {} branches across cities",
"branchesCount": "{} Branches"
};
static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
}

@ -665,5 +665,108 @@ abstract class LocaleKeys {
static const priceSAR = 'priceSAR';
static const amountSAR = 'amountSAR';
static const amuLastThreeMonths = 'amuLastThreeMonths';
static const mazayaTitle = 'mazayaTitle';
static const mazayaAccessGranted = 'mazayaAccessGranted';
static const mazayaAccessGrantedDesc = 'mazayaAccessGrantedDesc';
static const accessIsNowActive = 'accessIsNowActive';
static const accessIsNowActiveDesc = 'accessIsNowActiveDesc';
static const dependentAccessActive = 'dependentAccessActive';
static const shareableAppDownloadLink = 'shareableAppDownloadLink';
static const shareableAppDownloadLinkDesc = 'shareableAppDownloadLinkDesc';
static const downloadLink = 'downloadLink';
static const shareDownloadLink = 'shareDownloadLink';
static const copyLink = 'copyLink';
static const done = 'done';
static const linkCopied = 'linkCopied';
static const mazayaAccessRevoked = 'mazayaAccessRevoked';
static const mazayaAccessRevokedDesc = 'mazayaAccessRevokedDesc';
static const contactYourSponsor = 'contactYourSponsor';
static const contactYourSponsorDesc = 'contactYourSponsorDesc';
static const dependentAccessBlocked = 'dependentAccessBlocked';
static const returnToSignIn = 'returnToSignIn';
static const allowMazayaAccess = 'allowMazayaAccess';
static const revokeMazayaAccess = 'revokeMazayaAccess';
static const active = 'active';
static const noAccess = 'noAccess';
static const offerDetails = 'offerDetails';
static const offerIdNotAvailable = 'offerIdNotAvailable';
static const unableToRedeemOffer = 'unableToRedeemOffer';
static const errorRedeemingOffer = 'errorRedeemingOffer';
static const useOffer = 'useOffer';
static const online = 'online';
static const inStore = 'inStore';
static const ksa = 'ksa';
static const riyadh = 'riyadh';
static const noInstructionsAvailable = 'noInstructionsAvailable';
static const contactInformation = 'contactInformation';
static const humanResources = 'humanResources';
static const drSulaimanAlHabib = 'drSulaimanAlHabib';
static const allCategories = 'allCategories';
static const noOffersAvailable = 'noOffersAvailable';
static const addDependents = 'addDependents';
static const myDependents = 'myDependents';
static const offDiscount = 'offDiscount';
static const usageLabel = 'usageLabel';
static const areYouSureWantToLogOut = 'areYouSureWantToLogOut';
static const searchDependents = 'searchDependents';
static const searchFamilyMembers = 'searchFamilyMembers';
static const addAnotherUser = 'addAnotherUser';
static const joinMazaya = 'joinMazaya';
static const joinMazayaSubtitle = 'joinMazayaSubtitle';
static const continueAsEmployee = 'continueAsEmployee';
static const continueAsEmployeeSubtitle = 'continueAsEmployeeSubtitle';
static const pleaseEnterNationalId = 'pleaseEnterNationalId';
static const pleaseSelectDob = 'pleaseSelectDob';
static const unableToRetrieveDependentInfo = 'unableToRetrieveDependentInfo';
static const failedToSendOtp = 'failedToSendOtp';
static const verificationCodeResent = 'verificationCodeResent';
static const nationalId = 'nationalId';
static const addDependent = 'addDependent';
static const stepVerify = 'stepVerify';
static const stepBasic = 'stepBasic';
static const stepAdditional = 'stepAdditional';
static const findYourDependent = 'findYourDependent';
static const findYourDependentDesc = 'findYourDependentDesc';
static const enter10DigitNationalId = 'enter10DigitNationalId';
static const nationalIdMustBe10Digits = 'nationalIdMustBe10Digits';
static const dateFormatPlaceholder = 'dateFormatPlaceholder';
static const searchUser = 'searchUser';
static const dependentNotFoundNhic = 'dependentNotFoundNhic';
static const basicInfo = 'basicInfo';
static const basicInfoDesc = 'basicInfoDesc';
static const additionalInfo = 'additionalInfo';
static const additionalInfoDesc = 'additionalInfoDesc';
static const select = 'select';
static const enterFieldPlaceholder = 'enterFieldPlaceholder';
static const requiredField = 'requiredField';
static const pleaseFillField = 'pleaseFillField';
static const reviewAndSubmit = 'reviewAndSubmit';
static const reviewAndSubmitDesc = 'reviewAndSubmitDesc';
static const arabicName = 'arabicName';
static const relationship = 'relationship';
static const sponsor = 'sponsor';
static const dependentSponsorDetail = 'dependentSponsorDetail';
static const insuranceEligibility = 'insuranceEligibility';
static const relationshipStartDate = 'relationshipStartDate';
static const dependentAddedSuccessfully = 'dependentAddedSuccessfully';
static const genderMale = 'genderMale';
static const genderFemale = 'genderFemale';
static const religionIslam = 'religionIslam';
static const back = 'back';
static const submitRequest = 'submitRequest';
static const enterDependentDetailsToContinue = 'enterDependentDetailsToContinue';
static const enterBasicDetailsDesc = 'enterBasicDetailsDesc';
static const locations = 'locations';
static const availableOffers = 'availableOffers';
static const selectOfferToRedeem = 'selectOfferToRedeem';
static const viewOnMap = 'viewOnMap';
static const showAllLocations = 'showAllLocations';
static const showLess = 'showLess';
static const searchBranches = 'searchBranches';
static const allCities = 'allCities';
static const noBranchesFound = 'noBranchesFound';
static const browseAllBranches = 'browseAllBranches';
static const branchesCount = 'branchesCount';
}

@ -0,0 +1,92 @@
class GetMobileLoginInfoDependentModel {
int? iD;
String? dependentID;
String? depedentName;
String? dob;
int? channelID;
int? companyID;
String? deviceType;
String? deviceToken;
int? language;
int? gender;
int? loginType;
String? createdOn;
String? editedOn;
bool? businessCardPrivilege;
GetMobileLoginInfoDependentModel({
this.iD,
this.dependentID,
this.depedentName,
this.dob,
this.channelID,
this.companyID,
this.deviceType,
this.deviceToken,
this.language,
this.gender,
this.loginType,
this.createdOn,
this.editedOn,
this.businessCardPrivilege,
});
// Convenience aliases for existing logic
String? get nationalId => dependentID;
String? get birthDate => dob;
String? get employeeName => depedentName;
String? get dependentName => depedentName;
GetMobileLoginInfoDependentModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
dependentID = (json['DependentID'] ??
json['DEPENDENT_ID'] ??
json['P_DEP_NATIONAL_ID'] ??
json['NationalID'] ??
json['NationalId'] ??
json['EmployeeID'] ??
json['UserName'])
?.toString();
depedentName = (json['DepedentName'] ??
json['DependentName'] ??
json['P_DEPENDENT_NAME'] ??
json['EmployeeName'])
?.toString();
dob = (json['DOB'] ??
json['BirthDate'] ??
json['DateOfBirth'] ??
json['P_DEP_BIRTH_DATE'] ??
json['P_BIRTH_DATE'] ??
json['Birth_Date'])
?.toString();
channelID = json['ChannelID'] != null ? int.tryParse(json['ChannelID'].toString()) : null;
companyID = json['CompanyID'] != null ? int.tryParse(json['CompanyID'].toString()) : null;
deviceType = json['DeviceType']?.toString();
deviceToken = json['DeviceToken']?.toString();
language = json['Language'] != null ? int.tryParse(json['Language'].toString()) : null;
gender = json['Gender'] != null ? int.tryParse(json['Gender'].toString()) : null;
loginType = json['LoginType'] != null ? int.tryParse(json['LoginType'].toString()) : null;
createdOn = json['CreatedOn']?.toString();
editedOn = json['EditedOn']?.toString();
businessCardPrivilege = json['BusinessCardPrivilege'];
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = iD;
data['DependentID'] = dependentID;
data['DepedentName'] = depedentName;
data['DOB'] = dob;
data['ChannelID'] = channelID;
data['CompanyID'] = companyID;
data['DeviceType'] = deviceType;
data['DeviceToken'] = deviceToken;
data['Language'] = language;
data['Gender'] = gender;
data['LoginType'] = loginType;
data['CreatedOn'] = createdOn;
data['EditedOn'] = editedOn;
data['BusinessCardPrivilege'] = businessCardPrivilege;
return data;
}
}

@ -688,7 +688,7 @@ class GenericResponseModel {
serviceName = json['ServiceName'];
time = json['Time'];
androidLink = json['AndroidLink'];
authenticationTokenID = json['AuthenticationTokenID'];
authenticationTokenID = json['AuthenticationTokenID']?.toString();
data = json['Data'];
dataw = json['Dataw'];
dietType = json['DietType'];
@ -2009,10 +2009,23 @@ class TicketBookingResult {
class GetWalaplusDepInfoModel {
String? pEmpEmailAddress;
String? pEmpMobileNumber;
String? pDependentName;
GetWalaplusDepInfoModel({this.pEmpEmailAddress, this.pEmpMobileNumber});
factory GetWalaplusDepInfoModel.fromJson(Map<String, dynamic> json) => GetWalaplusDepInfoModel(pEmpEmailAddress: json["P_EMP_EMAIL_ADDRESS"], pEmpMobileNumber: json["P_EMP_MOBILE_NUMBER"]);
GetWalaplusDepInfoModel({
this.pEmpEmailAddress,
this.pEmpMobileNumber,
this.pDependentName,
});
Map<String, dynamic> toJson() => {"P_EMP_EMAIL_ADDRESS": pEmpEmailAddress, "P_EMP_MOBILE_NUMBER": pEmpMobileNumber};
factory GetWalaplusDepInfoModel.fromJson(Map<String, dynamic> json) => GetWalaplusDepInfoModel(
pEmpEmailAddress: json["P_EMP_EMAIL_ADDRESS"],
pEmpMobileNumber: json["P_EMP_MOBILE_NUMBER"],
pDependentName: json["P_DEPENDENT_NAME"],
);
Map<String, dynamic> toJson() => {
"P_EMP_EMAIL_ADDRESS": pEmpEmailAddress,
"P_EMP_MOBILE_NUMBER": pEmpMobileNumber,
"P_DEPENDENT_NAME": pDependentName,
};
}

@ -8,6 +8,8 @@ class GetEmployeeContactsList {
int? pERSONID;
String? pRIMARYCONTACTFLAG;
String? rELATIONSHIP;
String? mAZAYAACCESSFLG;
dynamic mazayaAccess;
GetEmployeeContactsList(
{this.cONTACTNAME,
@ -17,7 +19,9 @@ class GetEmployeeContactsList {
this.dATEOFBIRTH,
this.pERSONID,
this.pRIMARYCONTACTFLAG,
this.rELATIONSHIP});
this.rELATIONSHIP,
this.mAZAYAACCESSFLG,
this.mazayaAccess});
GetEmployeeContactsList.fromJson(Map<String, dynamic> json) {
cONTACTNAME = json['CONTACT_NAME'];
@ -28,18 +32,22 @@ class GetEmployeeContactsList {
pERSONID = json['PERSON_ID'];
pRIMARYCONTACTFLAG = json['PRIMARY_CONTACT_FLAG'];
rELATIONSHIP = json['RELATIONSHIP'];
mAZAYAACCESSFLG = json['MAZAYA_ACCESS_FLG']?.toString() ?? json['MAZAYA_ACCESS']?.toString();
mazayaAccess = mAZAYAACCESSFLG;
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = new Map<String, dynamic>();
data['CONTACT_NAME'] = this.cONTACTNAME;
data['CONTACT_PERSON_ID'] = this.cONTACTPERSONID;
data['CONTACT_RELATIONSHIP_ID'] = this.cONTACTRELATIONSHIPID;
data['CONTACT_TYPE'] = this.cONTACTTYPE;
data['DATE_OF_BIRTH'] = this.dATEOFBIRTH;
data['PERSON_ID'] = this.pERSONID;
data['PRIMARY_CONTACT_FLAG'] = this.pRIMARYCONTACTFLAG;
data['RELATIONSHIP'] = this.rELATIONSHIP;
Map<String, dynamic> data = <String, dynamic>{};
data['CONTACT_NAME'] = cONTACTNAME;
data['CONTACT_PERSON_ID'] = cONTACTPERSONID;
data['CONTACT_RELATIONSHIP_ID'] = cONTACTRELATIONSHIPID;
data['CONTACT_TYPE'] = cONTACTTYPE;
data['DATE_OF_BIRTH'] = dATEOFBIRTH;
data['PERSON_ID'] = pERSONID;
data['PRIMARY_CONTACT_FLAG'] = pRIMARYCONTACTFLAG;
data['RELATIONSHIP'] = rELATIONSHIP;
data['MAZAYA_ACCESS_FLG'] = mAZAYAACCESSFLG;
data['MAZAYA_ACCESS'] = mazayaAccess;
return data;
}
}

@ -1,6 +1,6 @@
class GetMobileLoginInfoListModel {
int? iD;
String? employeeID;
dynamic? employeeID; //do not change this
int? channelID;
int? companyID;
String? deviceType;

@ -11,14 +11,33 @@ class WalaPlusCategoryModel {
this.imageThumb,
this.name,
this.parentId,
});
}) {
if ((image == null || image!.isEmpty) && id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
image = Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
}
}
WalaPlusCategoryModel.fromJson(Map<String, dynamic> json) {
id = json['id']?.toString();
image = json['image']?.toString();
imageThumb = json['image_thumb']?.toString();
name = json['name']?.toString();
parentId = json['parent_id']?.toString();
id = (json['id'] ?? json['Id'] ?? json['ID'])?.toString();
name = (json['name'] ?? json['Name'])?.toString();
parentId = (json['parent_id'] ?? json['Parent_Id'] ?? json['parentId'])?.toString();
imageThumb = (json['image_thumb'] ?? json['Image_Thumb'] ?? json['imageThumb'])?.toString();
// Use category image URL from HMG middleware using ID from WalaPlus_Categories API:
// https://epharmacy.hmg.com/middleware/images/category/{id}.png
if (id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
image = Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
} else {
image = (json['image'] ?? json['Image'])?.toString();
}
}
Map<String, dynamic> toJson() {
@ -33,4 +52,15 @@ class WalaPlusCategoryModel {
bool get isMainCategory => parentId == null || parentId!.isEmpty;
bool get isSvg => (image ?? '').toLowerCase().contains('.svg');
String get categoryImageUrl {
if (id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
return Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
}
return image ?? '';
}
}

@ -1,3 +1,6 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
class WalaPlusRetailersResponse {
@ -15,16 +18,32 @@ class WalaPlusRetailersResponse {
factory WalaPlusRetailersResponse.fromJson(Map<String, dynamic> json) {
List<WalaPlusRetailerModel> retailersList = [];
if (json['retailers'] != null && json['retailers'] is List) {
json['retailers'].forEach((v) {
retailersList.add(WalaPlusRetailerModel.fromJson(v));
dynamic retailersData = json['retailers'] ?? json['Retailers'] ?? json['data'] ?? json['items'];
if (retailersData is String) {
try {
retailersData = jsonDecode(retailersData);
} catch (_) {}
}
if (retailersData is List) {
retailersData.forEach((v) {
if (v is Map<String, dynamic>) {
retailersList.add(WalaPlusRetailerModel.fromJson(v));
} else if (v is Map) {
retailersList.add(WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(v)));
}
});
}
return WalaPlusRetailersResponse(
page: json['page'] is int ? json['page'] : int.tryParse(json['page']?.toString() ?? ''),
pagesCount: json['pages_count'] is int ? json['pages_count'] : int.tryParse(json['pages_count']?.toString() ?? ''),
perPage: json['per_page'] is int ? json['per_page'] : int.tryParse(json['per_page']?.toString() ?? ''),
page: json['page'] is int
? json['page']
: int.tryParse(json['page']?.toString() ?? json['WP_Page']?.toString() ?? '1'),
pagesCount: json['pages_count'] is int
? json['pages_count']
: int.tryParse(json['pages_count']?.toString() ?? json['pagesCount']?.toString() ?? json['totalPages']?.toString() ?? '1'),
perPage: json['per_page'] is int
? json['per_page']
: int.tryParse(json['per_page']?.toString() ?? json['perPage']?.toString() ?? json['WP_Per_page']?.toString() ?? '10'),
retailers: retailersList,
);
}
@ -41,6 +60,95 @@ class WalaPlusRetailersResponse {
}
}
class WalaPlusRetailersByIDResponse {
int? page;
int? pagesCount;
int? perPage;
List<WalaPlusRetailerLocation>? locations;
List<WalaPlusOfferModel>? offers;
List<WalaPlusRetailerModel>? retailer;
WalaPlusRetailersByIDResponse({
this.page,
this.pagesCount,
this.perPage,
this.locations,
this.offers,
this.retailer,
});
factory WalaPlusRetailersByIDResponse.fromJson(Map<String, dynamic> json) {
List<WalaPlusRetailerLocation> locList = WalaPlusRetailerModel.parseLocationsList(
json['locations'] ?? json['Locations'] ?? json['location'] ?? json['Location'],
);
List<WalaPlusOfferModel> offList = WalaPlusRetailerModel.parseOffersList(
json['offers'] ?? json['Offers'],
);
List<WalaPlusRetailerModel> retList = [];
dynamic retData = json['retailer'] ?? json['Retailer'];
if (retData is List) {
for (var r in retData) {
if (r is Map<String, dynamic>) {
var model = WalaPlusRetailerModel.fromJson(r);
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
retList.add(model);
} else if (r is Map) {
var model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(r));
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
retList.add(model);
}
}
} else if (retData is Map) {
var model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(retData));
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
retList.add(model);
}
return WalaPlusRetailersByIDResponse(
page: json['page'] is int
? json['page']
: int.tryParse(json['page']?.toString() ?? json['WP_Page']?.toString() ?? ''),
pagesCount: json['pages_count'] is int
? json['pages_count']
: int.tryParse(json['pages_count']?.toString() ?? json['pagesCount']?.toString() ?? ''),
perPage: json['per_page'] is int
? json['per_page']
: int.tryParse(json['per_page']?.toString() ?? json['perPage']?.toString() ?? ''),
locations: locList,
offers: offList,
retailer: retList,
);
}
WalaPlusRetailerModel? get primaryRetailer {
if (retailer != null && retailer!.isNotEmpty) {
return retailer!.first;
}
return null;
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['page'] = page;
data['pages_count'] = pagesCount;
data['per_page'] = perPage;
if (locations != null) {
data['locations'] = locations!.map((v) => v.toJson()).toList();
}
if (offers != null) {
data['offers'] = offers!.map((v) => v.toJson()).toList();
}
if (retailer != null) {
data['retailer'] = retailer!.map((v) => v.toJson()).toList();
}
return data;
}
}
class WalaPlusRetailerModel {
String? id;
String? name;
@ -64,8 +172,17 @@ class WalaPlusRetailerModel {
String? howToUse;
WalaPlusRetailerContact? contact;
dynamic offerWeight;
List<WalaPlusOfferModel>? offers;
List<WalaPlusRetailerLocation>? locations;
List<WalaPlusCategoryModel>? categories;
String? validity;
String? validTo;
String? validUntil;
String? endDate;
String? expiryDate;
String? duration;
String? email;
String? phone;
WalaPlusRetailerModel({
this.id,
@ -90,62 +207,223 @@ class WalaPlusRetailerModel {
this.howToUse,
this.contact,
this.offerWeight,
this.offers,
this.locations,
this.categories,
this.validity,
this.validTo,
this.validUntil,
this.endDate,
this.expiryDate,
this.duration,
this.email,
this.phone,
});
static List<WalaPlusOfferModel> parseOffersList(data) {
List<WalaPlusOfferModel> list = [];
if (data == null) return list;
if (data is String) {
try {
data = jsonDecode(data);
} catch (_) {}
}
if (data is List) {
for (var v in data) {
if (v is Map<String, dynamic>) {
list.add(WalaPlusOfferModel.fromJson(v));
} else if (v is Map) {
list.add(WalaPlusOfferModel.fromJson(Map<String, dynamic>.from(v)));
}
}
}
return list;
}
static List<WalaPlusRetailerLocation> parseLocationsList(data) {
List<WalaPlusRetailerLocation> list = [];
if (data == null) return list;
if (data is String) {
try {
data = jsonDecode(data);
} catch (_) {}
}
if (data is List) {
for (var v in data) {
if (v is Map<String, dynamic>) {
list.add(WalaPlusRetailerLocation.fromJson(v));
} else if (v is Map) {
list.add(WalaPlusRetailerLocation.fromJson(Map<String, dynamic>.from(v)));
}
}
}
return list;
}
factory WalaPlusRetailerModel.fromJson(Map<String, dynamic> json) {
List<WalaPlusRetailerLocation> locationsList = [];
if (json['locations'] != null && json['locations'] is List) {
json['locations'].forEach((v) {
locationsList.add(WalaPlusRetailerLocation.fromJson(v));
});
Map<String, dynamic> targetJson = json;
dynamic offersSource = json['offers'] ?? json['Offers'];
dynamic locationsSource = json['locations'] ?? json['Locations'] ?? json['location'] ?? json['Location'];
// If json is the wrapper object from WalaPlus_RetailersByID containing 'retailer' key
if (json['retailer'] != null || json['Retailer'] != null) {
dynamic retailerData = json['retailer'] ?? json['Retailer'];
if (retailerData is List && retailerData.isNotEmpty) {
var first = retailerData.first;
if (first is Map<String, dynamic>) {
targetJson = first;
} else if (first is Map) {
targetJson = Map<String, dynamic>.from(first);
}
} else if (retailerData is Map<String, dynamic>) {
targetJson = retailerData;
} else if (retailerData is Map) {
targetJson = Map<String, dynamic>.from(retailerData);
}
if (locationsSource == null || (locationsSource is List && locationsSource.isEmpty)) {
locationsSource = targetJson['locations'] ?? targetJson['Locations'] ?? targetJson['location'] ?? targetJson['Location'];
}
if (offersSource == null || (offersSource is List && offersSource.isEmpty)) {
offersSource = targetJson['offers'] ?? targetJson['Offers'];
}
}
List<WalaPlusOfferModel> offersList = parseOffersList(offersSource ?? targetJson['offers'] ?? targetJson['Offers']);
List<WalaPlusRetailerLocation> locationsList = parseLocationsList(
locationsSource ?? targetJson['locations'] ?? targetJson['Locations'] ?? targetJson['location'] ?? targetJson['Location'],
);
List<WalaPlusCategoryModel> categoriesList = [];
if (json['categories'] != null && json['categories'] is List) {
json['categories'].forEach((v) {
if (targetJson['categories'] != null && targetJson['categories'] is List) {
targetJson['categories'].forEach((v) {
categoriesList.add(WalaPlusCategoryModel.fromJson(v));
});
}
WalaPlusRetailerContact? contactObj;
if (json['contact'] != null) {
if (json['contact'] is Map) {
contactObj = WalaPlusRetailerContact.fromJson(Map<String, dynamic>.from(json['contact']));
} else if (json['contact'] is String && json['contact'].toString().trim().isNotEmpty) {
contactObj = WalaPlusRetailerContact(website: json['contact'].toString());
if (targetJson['contact'] != null) {
if (targetJson['contact'] is Map) {
contactObj = WalaPlusRetailerContact.fromJson(Map<String, dynamic>.from(targetJson['contact']));
} else if (targetJson['contact'] is String && targetJson['contact'].toString().trim().isNotEmpty) {
contactObj = WalaPlusRetailerContact(website: targetJson['contact'].toString());
}
}
String? foundValidity = targetJson['validity']?.toString() ??
targetJson['valid_to']?.toString() ??
targetJson['valid_until']?.toString() ??
targetJson['end_date']?.toString() ??
targetJson['expiry_date']?.toString() ??
targetJson['expire_date']?.toString() ??
targetJson['duration']?.toString() ??
targetJson['validity_period']?.toString() ??
targetJson['valid_duration']?.toString() ??
targetJson['offer_validity']?.toString();
if (foundValidity == null || foundValidity == "null" || foundValidity.trim().isEmpty) {
for (var entry in targetJson.entries) {
String k = entry.key.toLowerCase();
if ((k.contains('valid') || k.contains('expir') || k.contains('duration')) &&
!k.contains('isverificationcodevalid') &&
!k.contains('barcode')) {
if (entry.value != null && entry.value.toString().trim().isNotEmpty && entry.value.toString() != "null") {
foundValidity = entry.value.toString().trim();
break;
}
}
}
}
return WalaPlusRetailerModel(
id: json['id']?.toString(),
name: json['name']?.toString(),
offerTitle: json['offer_title']?.toString(),
offerType: json['offer_type']?.toString(),
description: json['description']?.toString(),
info: json['info']?.toString(),
image: json['image']?.toString(),
imageThumb: json['image_thumb']?.toString(),
logo: json['logo']?.toString(),
logoThumb: json['logo_thumb']?.toString(),
website: json['website']?.toString(),
type: json['type']?.toString(),
usage: json['usage'],
weight: json['weight'],
geoId: json['geo_id'],
pin: json['pin']?.toString(),
barcodeStandard: json['barcode_standard']?.toString(),
arHowToUse: json['ar_how_to_use']?.toString(),
enHowToUse: json['en_how_to_use']?.toString(),
howToUse: json['how_to_use']?.toString(),
id: targetJson['id']?.toString() ??
targetJson['WP_RetailerID']?.toString() ??
targetJson['retailer_id']?.toString() ??
targetJson['RetailerID']?.toString() ??
targetJson['retailerId']?.toString(),
name: decodeUnicodeString(targetJson['name']),
offerTitle: decodeUnicodeString(targetJson['offer_title']),
offerType: targetJson['offer_type']?.toString(),
description: decodeUnicodeString(targetJson['description']),
info: decodeUnicodeString(targetJson['info']),
image: targetJson['image']?.toString(),
imageThumb: targetJson['image_thumb']?.toString(),
logo: targetJson['logo']?.toString(),
logoThumb: targetJson['logo_thumb']?.toString(),
website: targetJson['website']?.toString(),
type: targetJson['type']?.toString(),
usage: targetJson['usage'] is String ? decodeUnicodeString(targetJson['usage']) : targetJson['usage'],
weight: targetJson['weight'],
geoId: targetJson['geo_id'],
pin: targetJson['pin']?.toString(),
barcodeStandard: targetJson['barcode_standard']?.toString(),
arHowToUse: decodeUnicodeString(targetJson['ar_how_to_use']),
enHowToUse: decodeUnicodeString(targetJson['en_how_to_use']),
howToUse: decodeUnicodeString(targetJson['how_to_use']),
contact: contactObj,
offerWeight: json['offer_weight'],
offerWeight: targetJson['offer_weight'],
offers: offersList,
locations: locationsList,
categories: categoriesList,
validity: foundValidity,
validTo: targetJson['valid_to']?.toString() ?? targetJson['validTo']?.toString(),
validUntil: targetJson['valid_until']?.toString() ?? targetJson['validUntil']?.toString(),
endDate: targetJson['end_date']?.toString() ?? targetJson['endDate']?.toString(),
expiryDate: targetJson['expiry_date']?.toString() ?? targetJson['expiryDate']?.toString(),
duration: targetJson['duration']?.toString(),
email: targetJson['email']?.toString() ?? targetJson['contact_email']?.toString() ?? contactObj?.primaryEmail,
phone: targetJson['phone']?.toString() ?? targetJson['contact_phone']?.toString() ?? targetJson['mobile']?.toString() ?? contactObj?.primaryPhone,
);
}
static String? decodeUnicodeString(Object? val) {
if (val == null) return null;
String s = val.toString().trim();
if (s.isEmpty || s == "null") return null;
while ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
if (s.length >= 2) {
s = s.substring(1, s.length - 1).trim();
} else {
break;
}
}
s = s
.replaceAll(r'\/', '/')
.replaceAll(r'\"', '"')
.replaceAll(r"\'", "'");
s = s.replaceAllMapped(RegExp(r'\\u([0-9a-fA-F]{4})'), (match) {
try {
var codeUnit = int.parse(match.group(1)!, radix: 16);
return String.fromCharCode(codeUnit);
} catch (_) {
return match.group(0)!;
}
});
s = s
.replaceAll(r'\u201c', '“')
.replaceAll(r'\u201d', '”')
.replaceAll(r'\u2019', '’')
.replaceAll(r'\u2018', '‘')
.replaceAll(r'\u00a0', ' ')
.replaceAll(r'\u2022', '•')
.replaceAll(r'\u2013', '–')
.replaceAll(r'\u2014', '—')
.replaceAll(r'\u200f', '')
.replaceAll(r'\u200e', '')
.replaceAll(r'\u061f', '؟')
.replaceAll(r'\u201', '“')
.replaceAll('&quot;', '"')
.replaceAll('&apos;', "'")
.replaceAll('&#39;', "'")
.replaceAll('&nbsp;', ' ')
.replaceAll('&amp;', '&');
return s;
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
@ -170,6 +448,17 @@ class WalaPlusRetailerModel {
data['how_to_use'] = howToUse;
data['contact'] = contact?.toJson();
data['offer_weight'] = offerWeight;
data['validity'] = validity;
data['valid_to'] = validTo;
data['valid_until'] = validUntil;
data['end_date'] = endDate;
data['expiry_date'] = expiryDate;
data['duration'] = duration;
data['email'] = email;
data['phone'] = phone;
if (offers != null) {
data['offers'] = offers!.map((v) => v.toJson()).toList();
}
if (locations != null) {
data['locations'] = locations!.map((v) => v.toJson()).toList();
}
@ -179,76 +468,279 @@ class WalaPlusRetailerModel {
return data;
}
String? get displayImage {
if (image != null && image!.trim().isNotEmpty && image != "null") return image!.trim();
if (imageThumb != null && imageThumb!.trim().isNotEmpty && imageThumb != "null") return imageThumb!.trim();
return null;
}
String? get displayLogo {
if (logo != null && logo!.trim().isNotEmpty && logo != "null") return logo!.trim();
if (logoThumb != null && logoThumb!.trim().isNotEmpty && logoThumb != "null") return logoThumb!.trim();
return null;
}
String? get resolvedWebsite {
if (contact?.website != null && contact!.website!.trim().isNotEmpty) {
if (contact?.website != null && contact!.website!.trim().isNotEmpty && contact!.website != "null") {
return contact!.website!.trim();
}
if (website != null && website!.trim().isNotEmpty) {
if (website != null && website!.trim().isNotEmpty && website != "null") {
return website!.trim();
}
return null;
}
String get displayCity {
bool get hasPhysicalLocation {
if (locations != null && locations!.isNotEmpty) {
for (var loc in locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty) {
return loc.city!.trim();
if ((loc.cityNameEn != null && loc.cityNameEn!.trim().isNotEmpty && loc.cityNameEn != "null") ||
(loc.cityNameAr != null && loc.cityNameAr!.trim().isNotEmpty && loc.cityNameAr != "null") ||
(loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null")) {
return true;
}
}
}
if (type?.toLowerCase() == 'online') {
return 'Online';
return false;
}
String getDisplayCity({bool isAr = false}) {
if (locations != null && locations!.isNotEmpty) {
// 1. First pass: look for specific city name
for (var loc in locations!) {
String? cName = isAr ? loc.cityNameAr : loc.cityNameEn;
if (cName != null && cName.trim().isNotEmpty && cName != "null") {
return cName.trim();
}
}
// 2. Second pass: fallback to city if not generic country
for (var loc in locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
String c = loc.city!.trim();
if (c.toLowerCase() != 'saudi arabia' && c != 'المملكة العربية السعودية') {
return c;
}
}
}
// 3. Third pass: any non-empty city
for (var loc in locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
return loc.city!.trim();
}
}
}
return '';
}
String get displayCity => getDisplayCity(isAr: false);
String get displayUsage {
if (usage != null && usage.toString().isNotEmpty) {
return 'Usage : $usage';
}
if (type != null && type!.isNotEmpty) {
if (type!.toLowerCase() == 'online') return 'Online';
if (type!.toLowerCase() == 'store') return 'In Store';
return type!;
if (usage != null) {
String u = usage.toString().trim();
if (int.tryParse(u) != null) {
return 'usage:$u';
}
if (!u.contains('<') && !u.contains('{') && u.isNotEmpty && u.length <= 12 && u.toLowerCase() != 'online' && u.toLowerCase() != 'store') {
return 'usage:$u';
}
}
return '';
}
String getDisplayValidity({bool isAr = false}) {
if (validity != null && validity!.trim().isNotEmpty && validity != "null") {
return validity!.trim();
}
if (validTo != null && validTo!.trim().isNotEmpty && validTo != "null") {
return validTo!.trim();
}
if (validUntil != null && validUntil!.trim().isNotEmpty && validUntil != "null") {
return validUntil!.trim();
}
if (endDate != null && endDate!.trim().isNotEmpty && endDate != "null") {
return endDate!.trim();
}
if (expiryDate != null && expiryDate!.trim().isNotEmpty && expiryDate != "null") {
return expiryDate!.trim();
}
if (duration != null && duration!.trim().isNotEmpty && duration != "null") {
return duration!.trim();
}
return LocaleKeys.offerValid.tr();
}
String? get resolvedEmail {
if (email != null && email!.trim().isNotEmpty && email != "null") {
return email!.trim();
}
if (contact?.primaryEmail != null && contact!.primaryEmail!.trim().isNotEmpty && contact!.primaryEmail != "null") {
return contact!.primaryEmail!.trim();
}
if (locations != null && locations!.isNotEmpty) {
for (var loc in locations!) {
if (loc.contact != null && loc.contact!.contains('@')) {
return loc.contact!.trim();
}
}
}
String combined = "${description ?? ''} ${info ?? ''} ${howToUse ?? ''} ${arHowToUse ?? ''} ${enHowToUse ?? ''}";
RegExp emailRegex = RegExp(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}');
Match? match = emailRegex.firstMatch(combined);
if (match != null) {
return match.group(0);
}
return null;
}
String? get resolvedPhone {
if (phone != null && phone!.trim().isNotEmpty && phone != "null") {
return phone!.trim();
}
if (contact?.primaryPhone != null && contact!.primaryPhone!.trim().isNotEmpty && contact!.primaryPhone != "null") {
return contact!.primaryPhone!.trim();
}
if (locations != null && locations!.isNotEmpty) {
for (var loc in locations!) {
if (loc.contact != null &&
loc.contact!.trim().isNotEmpty &&
loc.contact != "null" &&
!loc.contact!.contains('@')) {
return loc.contact!.trim();
}
}
}
String combined = "${description ?? ''} ${info ?? ''} ${howToUse ?? ''} ${arHowToUse ?? ''} ${enHowToUse ?? ''}";
RegExp phoneRegex = RegExp(r'(?:\+?966|0)?(?:5\d{8}|1\d{7}|9200\d{5}|800\d{6,7})');
Match? match = phoneRegex.firstMatch(combined);
if (match != null) {
return match.group(0);
}
return null;
}
bool get isOnlineStore => (type?.trim().toLowerCase() == 'online');
bool get isInstore => (type?.trim().toLowerCase() == 'instore' ||
type?.trim().toLowerCase() == 'in_store' ||
type?.trim().toLowerCase() == 'in store' ||
type?.trim().toLowerCase() == 'store' ||
(type == null && locations != null && locations!.isNotEmpty));
String? get normalizedType {
if (type != null && type!.trim().isNotEmpty) {
String t = type!.trim().toLowerCase();
if (t == 'online') return 'online';
if (t == 'instore' || t == 'in_store' || t == 'in store' || t == 'store') return 'instore';
return t;
}
if (locations != null && locations!.isNotEmpty) {
return 'instore';
}
return null;
}
String? getDisplayType() {
String? norm = normalizedType;
if (norm == 'online') return LocaleKeys.online.tr();
if (norm == 'instore') return LocaleKeys.inStore.tr();
if (type != null && type!.trim().isNotEmpty) {
return type!.trim();
}
return null;
}
}
class WalaPlusRetailerContact {
String? email;
List<String>? emails;
String? phone;
List<String>? phones;
String? website;
String? address;
String? instagram;
String? twitter;
WalaPlusRetailerContact({this.emails, this.phones, this.website});
WalaPlusRetailerContact({
this.email,
this.emails,
this.phone,
this.phones,
this.website,
this.address,
this.instagram,
this.twitter,
});
factory WalaPlusRetailerContact.fromJson(Map<String, dynamic> json) {
List<String> emailsList = [];
if (json['emails'] != null && json['emails'] is List) {
json['emails'].forEach((v) => emailsList.add(v.toString()));
}
String? singleEmail = json['email']?.toString() ?? json['email_address']?.toString();
if (singleEmail != null && singleEmail.trim().isNotEmpty && singleEmail != "null") {
if (!emailsList.contains(singleEmail.trim())) {
emailsList.add(singleEmail.trim());
}
}
List<String> phonesList = [];
if (json['phones'] != null && json['phones'] is List) {
json['phones'].forEach((v) => phonesList.add(v.toString()));
}
String? singlePhone = json['phone']?.toString() ?? json['mobile']?.toString() ?? json['phone_number']?.toString();
if (singlePhone != null && singlePhone.trim().isNotEmpty && singlePhone != "null") {
if (!phonesList.contains(singlePhone.trim())) {
phonesList.add(singlePhone.trim());
}
}
return WalaPlusRetailerContact(
email: singleEmail,
emails: emailsList,
phone: singlePhone,
phones: phonesList,
website: json['website']?.toString(),
website: WalaPlusRetailerModel.decodeUnicodeString(json['website']),
address: WalaPlusRetailerModel.decodeUnicodeString(json['address']),
instagram: json['instagram']?.toString(),
twitter: json['twitter']?.toString(),
);
}
Map<String, dynamic> toJson() {
return {
'email': email,
'emails': emails,
'phone': phone,
'phones': phones,
'website': website,
'address': address,
'instagram': instagram,
'twitter': twitter,
};
}
String? get primaryEmail {
if (email != null && email!.trim().isNotEmpty && email != "null") return email!.trim();
if (emails != null && emails!.isNotEmpty) {
for (var e in emails!) {
if (e.trim().isNotEmpty && e != "null") return e.trim();
}
}
return null;
}
String? get primaryPhone {
if (phone != null && phone!.trim().isNotEmpty && phone != "null") return phone!.trim();
if (phones != null && phones!.isNotEmpty) {
for (var p in phones!) {
if (p.trim().isNotEmpty && p != "null") return p.trim();
}
}
return null;
}
}
class WalaPlusRetailerLocation {
dynamic id;
String? address;
String? city;
String? cityNameAr;
@ -261,6 +753,7 @@ class WalaPlusRetailerLocation {
dynamic placeId;
WalaPlusRetailerLocation({
this.id,
this.address,
this.city,
this.cityNameAr,
@ -275,13 +768,14 @@ class WalaPlusRetailerLocation {
factory WalaPlusRetailerLocation.fromJson(Map<String, dynamic> json) {
return WalaPlusRetailerLocation(
address: json['address']?.toString(),
city: json['city']?.toString(),
cityNameAr: json['city_name_ar']?.toString(),
cityNameEn: json['city_name_en']?.toString(),
id: json['id'],
address: WalaPlusRetailerModel.decodeUnicodeString(json['address']),
city: WalaPlusRetailerModel.decodeUnicodeString(json['city']),
cityNameAr: WalaPlusRetailerModel.decodeUnicodeString(json['city_name_ar']),
cityNameEn: WalaPlusRetailerModel.decodeUnicodeString(json['city_name_en']),
contact: json['contact']?.toString(),
countryNameAr: json['country_name_ar']?.toString(),
countryNameEn: json['country_name_en']?.toString(),
countryNameAr: WalaPlusRetailerModel.decodeUnicodeString(json['country_name_ar']),
countryNameEn: WalaPlusRetailerModel.decodeUnicodeString(json['country_name_en']),
latitude: json['latitude'] is num ? (json['latitude'] as num).toDouble() : double.tryParse(json['latitude']?.toString() ?? ''),
longitude: json['longitude'] is num ? (json['longitude'] as num).toDouble() : double.tryParse(json['longitude']?.toString() ?? ''),
placeId: json['place_id'],
@ -289,7 +783,8 @@ class WalaPlusRetailerLocation {
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['address'] = address;
data['city'] = city;
data['city_name_ar'] = cityNameAr;
@ -302,4 +797,95 @@ class WalaPlusRetailerLocation {
data['place_id'] = placeId;
return data;
}
String getLocalizedCity(bool isAr) {
if (isAr) {
if (cityNameAr != null && cityNameAr!.trim().isNotEmpty && cityNameAr != "null") {
return cityNameAr!.trim();
}
} else {
if (cityNameEn != null && cityNameEn!.trim().isNotEmpty && cityNameEn != "null") {
return cityNameEn!.trim();
}
}
if (city != null && city!.trim().isNotEmpty && city != "null") {
return city!.trim();
}
return isAr ? (cityNameAr ?? "") : (cityNameEn ?? "");
}
String getLocalizedCountry(bool isAr) {
if (isAr) {
if (countryNameAr != null && countryNameAr!.trim().isNotEmpty && countryNameAr != "null") {
return countryNameAr!.trim();
}
} else {
if (countryNameEn != null && countryNameEn!.trim().isNotEmpty && countryNameEn != "null") {
return countryNameEn!.trim();
}
}
return "";
}
}
class WalaPlusOfferModel {
String? id;
String? retailerId;
String? title;
String? image;
String? imageThumb;
String? type;
String? description;
dynamic price;
dynamic usage;
String? expiredAt;
WalaPlusOfferModel({
this.id,
this.retailerId,
this.title,
this.image,
this.imageThumb,
this.type,
this.description,
this.price,
this.usage,
this.expiredAt,
});
factory WalaPlusOfferModel.fromJson(Map<String, dynamic> json) {
return WalaPlusOfferModel(
id: json['id']?.toString(),
retailerId: (json['retailer_id'] ?? json['retailerId'] ?? json['retailerID'])?.toString(),
title: WalaPlusRetailerModel.decodeUnicodeString(json['title']),
image: json['image']?.toString(),
imageThumb: (json['image_thumb'] ?? json['imageThumb'])?.toString(),
type: json['type']?.toString(),
description: WalaPlusRetailerModel.decodeUnicodeString(json['description']),
price: json['price'],
usage: json['usage'],
expiredAt: (json['expired_at'] ?? json['expiredAt'] ?? json['expiry_date'])?.toString(),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'retailer_id': retailerId,
'title': title,
'image': image,
'image_thumb': imageThumb,
'type': type,
'description': description,
'price': price,
'usage': usage,
'expired_at': expiredAt,
};
}
String? get displayImage {
if (image != null && image!.trim().isNotEmpty && image != "null") return image!.trim();
if (imageThumb != null && imageThumb!.trim().isNotEmpty && imageThumb != "null") return imageThumb!.trim();
return null;
}
}

@ -40,7 +40,7 @@ class PostParamsModel {
mobileType = json['MobileType'];
logInTokenID = json['LogInTokenID'];
payrollCodeStr = json['PayrollCodeStr'];
pLegislationCode = json['LegislationCodeStr'];
pLegislationCode = json['LegislationCodeStr']?.toString().trim().toUpperCase();
tokenID = json['TokenID'];
}
@ -92,7 +92,7 @@ class PostParamsModel {
set setPSelectedEmployeeNumber(String? v) => pSelectedEmployeeNumber = v;
set setPLegislationCode(String? v) => pLegislationCode = v;
set setPLegislationCode(String? v) => pLegislationCode = v?.trim().toUpperCase();
set setPayrollCodeStr(String? v) => payrollCodeStr = v;
}

@ -0,0 +1,139 @@
class PatientInfoModel {
int? age;
String? dateOfBirth;
String? firstNameAr;
String? firstNameEn;
String? secondNameAr;
String? secondNameEn;
String? thirdNameAr;
String? thirdNameEn;
String? lastNameAr;
String? lastNameEn;
String? gender;
String? healthId;
String? idNumber;
String? idType;
bool? isHijri;
String? maritalStatus;
String? maritalStatusCode;
String? nationalDateOfBirth;
String? nationality;
String? nationalityCode;
String? occupation;
String? patientStatus;
String? placeOfBirth;
int? messageStatus;
String? errorEndUserMessage;
String? errorMessage;
PatientInfoModel({
this.age,
this.dateOfBirth,
this.firstNameAr,
this.firstNameEn,
this.secondNameAr,
this.secondNameEn,
this.thirdNameAr,
this.thirdNameEn,
this.lastNameAr,
this.lastNameEn,
this.gender,
this.healthId,
this.idNumber,
this.idType,
this.isHijri,
this.maritalStatus,
this.maritalStatusCode,
this.nationalDateOfBirth,
this.nationality,
this.nationalityCode,
this.occupation,
this.patientStatus,
this.placeOfBirth,
this.messageStatus,
this.errorEndUserMessage,
this.errorMessage,
});
factory PatientInfoModel.fromJson(Map<String, dynamic> json) {
return PatientInfoModel(
age: json['Age'],
dateOfBirth: json['DateOfBirth'],
firstNameAr: json['FirstNameAr'],
firstNameEn: json['FirstNameEn'],
secondNameAr: json['SecondNameAr'],
secondNameEn: json['SecondNameEn'],
thirdNameAr: json['ThirdNameAr'],
thirdNameEn: json['ThirdNameEn'],
lastNameAr: json['LastNameAr'],
lastNameEn: json['LastNameEn'],
gender: json['Gender'],
healthId: json['HealthId'],
idNumber: json['IdNumber'],
idType: json['IdType'],
isHijri: json['IsHijri'],
maritalStatus: json['MaritalStatus'],
maritalStatusCode: json['MaritalStatusCode'],
nationalDateOfBirth: json['NationalDateOfBirth'],
nationality: json['Nationality'],
nationalityCode: json['NationalityCode'],
occupation: json['Occupation'],
patientStatus: json['PatientStatus']?.toString(),
placeOfBirth: json['PlaceofBirth'],
messageStatus: json['MessageStatus'],
errorEndUserMessage: json['ErrorEndUserMessage'],
errorMessage: json['ErrorMessage'],
);
}
Map<String, dynamic> toJson() {
return {
'Age': age,
'DateOfBirth': dateOfBirth,
'FirstNameAr': firstNameAr,
'FirstNameEn': firstNameEn,
'SecondNameAr': secondNameAr,
'SecondNameEn': secondNameEn,
'ThirdNameAr': thirdNameAr,
'ThirdNameEn': thirdNameEn,
'LastNameAr': lastNameAr,
'LastNameEn': lastNameEn,
'Gender': gender,
'HealthId': healthId,
'IdNumber': idNumber,
'IdType': idType,
'IsHijri': isHijri,
'MaritalStatus': maritalStatus,
'MaritalStatusCode': maritalStatusCode,
'NationalDateOfBirth': nationalDateOfBirth,
'Nationality': nationality,
'NationalityCode': nationalityCode,
'Occupation': occupation,
'PatientStatus': patientStatus,
'PlaceofBirth': placeOfBirth,
'MessageStatus': messageStatus,
'ErrorEndUserMessage': errorEndUserMessage,
'ErrorMessage': errorMessage,
};
}
String get fullNameAr {
List<String> parts = [
firstNameAr ?? '',
secondNameAr ?? '',
(thirdNameAr != null && thirdNameAr != '-') ? thirdNameAr! : '',
lastNameAr ?? '',
].where((s) => s.trim().isNotEmpty && s != '-').toList();
return parts.join(' ').trim();
}
String get fullNameEn {
List<String> parts = [
firstNameEn ?? '',
secondNameEn ?? '',
(thirdNameEn != null && thirdNameEn != '-') ? thirdNameEn! : '',
lastNameEn ?? '',
].where((s) => s.trim().isNotEmpty && s != '-').toList();
return parts.join(' ').trim();
}
}

@ -17,12 +17,12 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin {
MediaStream? _remoteStream;
void initCallListeners() {
chatHubConnection.on("OnCallAcceptedAsync", onCallAcceptedAsync);
chatHubConnection.on("OnIceCandidateAsync", onIceCandidateAsync);
chatHubConnection.on("OnOfferAsync", onOfferAsync);
chatHubConnection.on("OnAnswerOffer", onAnswerOffer);
chatHubConnection.on("OnHangUpAsync", onHangUpAsync);
chatHubConnection.on("OnCallDeclinedAsync", onCallDeclinedAsync);
chatHubConnection?.on("OnCallAcceptedAsync", onCallAcceptedAsync);
chatHubConnection?.on("OnIceCandidateAsync", onIceCandidateAsync);
chatHubConnection?.on("OnOfferAsync", onOfferAsync);
chatHubConnection?.on("OnAnswerOffer", onAnswerOffer);
chatHubConnection?.on("OnHangUpAsync", onHangUpAsync);
chatHubConnection?.on("OnCallDeclinedAsync", onCallDeclinedAsync);
}
//Video Constraints
@ -175,13 +175,13 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin {
args = [targetUserID, data];
//json In Data
}
await chatHubConnection.invoke(invokeMethod, args: args);
await chatHubConnection?.invoke(invokeMethod, args: args);
}
void stopListeners() async {
chatHubConnection.off('OnCallDeclinedAsync');
chatHubConnection.off('OnCallAcceptedAsync');
chatHubConnection.off('OnIceCandidateAsync');
chatHubConnection.off('OnAnswerOffer');
chatHubConnection?.off('OnCallDeclinedAsync');
chatHubConnection?.off('OnCallAcceptedAsync');
chatHubConnection?.off('OnIceCandidateAsync');
chatHubConnection?.off('OnAnswerOffer');
}
}

@ -113,16 +113,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
Future<void> buildHubConnection() async {
chatHubConnection = await getHubConnection();
await chatHubConnection.start();
await chatHubConnection?.start();
if (kDebugMode) {
logger.i("Hub Conn: Startedddddddd");
}
chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection.on("OnGetChatConversationCount", onNewChatConversion);
chatHubConnection?.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection?.on("OnGetChatConversationCount", onNewChatConversion);
//group On message
chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
chatHubConnection?.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
}
Future<HubConnection> getHubConnection() async {
@ -135,16 +135,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
}
void registerEvents() {
chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus);
// chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection?.on("OnUpdateUserStatusAsync", changeStatus);
// chatHubConnection?.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync);
chatHubConnection.on("OnUserTypingAsync", onUserTyping);
chatHubConnection.on("OnUserCountAsync", userCountAsync);
// chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow);
chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered);
chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus);
chatHubConnection.on("OnGetGroupUserStatusAsync", getGroupUserStatus);
chatHubConnection?.on("OnSubmitChatAsync", OnSubmitChatAsync);
chatHubConnection?.on("OnUserTypingAsync", onUserTyping);
chatHubConnection?.on("OnUserCountAsync", userCountAsync);
// chatHubConnection?.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow);
chatHubConnection?.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered);
chatHubConnection?.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus);
chatHubConnection?.on("OnGetGroupUserStatusAsync", getGroupUserStatus);
//
// {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]}
@ -183,7 +183,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
}
Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async {
await chatHubConnection.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]);
await chatHubConnection?.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]);
return "";
}
@ -252,7 +252,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void updateUserChatHistoryStatusAsync(List data) {
try {
chatHubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
chatHubConnection?.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
} catch (e) {
throw e;
}
@ -260,7 +260,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void updateUserChatHistoryOnMsg(List data) {
try {
chatHubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
chatHubConnection?.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
} catch (e) {
throw e;
}
@ -753,7 +753,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
String chatData =
'{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}';
await chatHubConnection.invoke("AddChatUserAsync", args: <Object>[json.decode(chatData)]);
await chatHubConnection?.invoke("AddChatUserAsync", args: <Object>[json.decode(chatData)]);
}
//groupChatMessage
@ -829,7 +829,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
String chatData =
'{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId":$fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"groupId":$targetGroupId,"groupChatHistoryLineRequestList":${json.encode(targetUsers)},"chatReplyId": $chatReplyId,"conversationId":"${uuid.v4()}"}';
await chatHubConnection.invoke("AddGroupChatHistoryAsync", args: <Object>[json.decode(chatData)]);
await chatHubConnection?.invoke("AddGroupChatHistoryAsync", args: <Object>[json.decode(chatData)]);
}
void sendGroupChatMessage(
@ -1450,7 +1450,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
pChatHistory?.clear();
uGroups?.clear();
searchGroup?.clear();
chatHubConnection.stop();
try {
chatHubConnection?.stop();
} catch (e) {
if (kDebugMode) {
print("Error stopping chatHubConnection: $e");
}
}
chatHubConnection = null;
AppState().chatDetails = null;
}
}
@ -1582,17 +1589,17 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
}
Future invokeChatCounter({required int userId}) async {
await chatHubConnection.invoke("GetChatCounversationCount", args: [userId]);
await chatHubConnection?.invoke("GetChatCounversationCount", args: [userId]);
return "";
}
void userTypingInvoke({required int currentUser, required int reciptUser}) async {
await chatHubConnection.invoke("UserTypingAsync", args: [reciptUser, currentUser]);
await chatHubConnection?.invoke("UserTypingAsync", args: [reciptUser, currentUser]);
}
void groupTypingInvoke({required GroupResponse groupDetails, required int groupId}) async {
var data = json.decode(json.encode(groupDetails.groupUserList));
await chatHubConnection.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]);
await chatHubConnection?.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]);
}
//////// Audio Recoding Work ////////////////////

@ -33,7 +33,7 @@ class _ChatHomeState extends State<ChatHome> {
void initState() {
super.initState();
if (chatHubConnection.state == HubConnectionState.Connected) {
if (chatHubConnection?.state == HubConnectionState.Connected) {
data = Provider.of<ChatProviderModel>(context, listen: false);
data.registerEvents();
}
@ -46,7 +46,7 @@ class _ChatHomeState extends State<ChatHome> {
}
void fetchAgain() {
if (chatHubConnection.state != HubConnectionState.Connected) {
if (chatHubConnection?.state != HubConnectionState.Connected) {
data.getUserAutoLoginToken().whenComplete(() async {
await data.buildHubConnection();
data.getUserRecentChats();

@ -29,13 +29,13 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_banner.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart';
import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:signalr_netcore/signalr_client.dart';
import 'package:url_launcher/url_launcher.dart';
late HubConnection chatHubConnection;
HubConnection? chatHubConnection;
class DashboardScreen extends StatefulWidget {
DashboardScreen({Key? key}) : super(key: key);
@ -56,7 +56,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
int currentIndex = 0;
bool isDisplayMazaya = false;
bool isDisplayMazaya = true;
@override
void initState() {
@ -93,7 +93,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
WidgetsBinding.instance.removeObserver(this);
super.dispose();
if (!cProvider.disbaleChatForThisUser) {
chatHubConnection.stop();
chatHubConnection?.stop();
}
}
@ -120,16 +120,16 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
Future<void> checkHubCon() async {
// chatHubConnection = await context.read<ChatProviderModel>().getHubConnection();
if (chatHubConnection.state == HubConnectionState.Connected) {
await chatHubConnection.stop();
await chatHubConnection.start();
} else if (chatHubConnection.state != HubConnectionState.Connected) {
await chatHubConnection.start();
if (chatHubConnection?.state == HubConnectionState.Connected) {
await chatHubConnection?.stop();
await chatHubConnection?.start();
} else if (chatHubConnection != null && chatHubConnection!.state != HubConnectionState.Connected) {
await chatHubConnection?.start();
}
}
void gotoChat(BuildContext context) async {
if (chatHubConnection.state == HubConnectionState.Connected) {
if (chatHubConnection?.state == HubConnectionState.Connected) {
Utils.hideLoading(context);
Navigator.pushNamed(context, AppRoutes.chat);
String isAppOpendByChat = await Utils.getStringFromPrefs("isAppOpendByChat");
@ -453,141 +453,32 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
return const SizedBox();
}
},
).paddingOnly(bottom: 24, left: 24, right: 24),
).paddingOnly(bottom: 18, left: 24, right: 24),
// eventActivityWidget(context),
if (isDisplayMazaya) ...[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Directionality(
textDirection: AppState().isArabic(context) ? ui.TextDirection.rtl : ui.TextDirection.ltr,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(colors: [Color(0xFF91C481), Color(0xFF7CCED7)], begin: Alignment.centerLeft, end: Alignment.centerRight),
),
child: Padding(
padding: const EdgeInsets.all(3.0), // This creates the border width
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(17), // Slightly less than outer radius
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Expanded(
flex: 2,
child: RichText(
text:
AppState().isArabic(context)
? TextSpan(
children: [
TextSpan(
text: 'اطلع على مميزات',
style: TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: const Color(0xFF5D5E5E),
),
),
TextSpan(
text: ' مزايا',
style: TextStyle(
fontSize: 16,
fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
fontWeight: FontWeight.w700,
letterSpacing: -0.2,
height: 24 / 16,
color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
),
),
],
)
: TextSpan(
children: [
TextSpan(
text: LocaleKeys.explore.tr() + ' ',
style: const TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: Color(0xFF5D5E5E),
),
),
TextSpan(
text: LocaleKeys.mazaya.tr(),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
fontFamily: 'Poppins',
letterSpacing: -0.2,
height: 24 / 16,
color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
),
),
TextSpan(
text: ' ' + LocaleKeys.benefits.tr(),
style: const TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: Color(0xFF5D5E5E),
),
),
],
),
),
),
const Expanded(flex: 1, child: SizedBox()),
],
),
const SizedBox(height: 8),
LocaleKeys.mazayaDesc.tr().toText11(color: const Color(0xFF5D5E5E)),
],
),
),
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
SvgPicture.asset("assets/icons/mazaya_brand.svg", width: 90, height: 47),
const SizedBox(height: 28),
LocaleKeys.viewallofferMazaya.tr().toText12(isUnderLine: true, color: const Color(0xFF3B3D4A)).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
}),
],
),
),
],
).paddingOnly(left: 21, right: 21, top: 14, bottom: 14),
),
),
).paddingOnly(left: 21, right: 21, top: 0, bottom: 21),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
Navigator.pushNamed(context, AppRoutes.dashboardScreenForDependet);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: AspectRatio(
aspectRatio: 386 / 121.37,
child: SvgPicture.asset(
AppState().isArabic(context)
? "assets/images/mazaya_banner_ar.svg"
: "assets/images/mazaya_banner_en.svg",
width: double.infinity,
fit: BoxFit.fill,
),
),
],
),
),
).paddingOnly(left: 21, right: 21, top: 0, bottom: 21),
],
// Offers & Discounts commented out as requested
/*
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
@ -678,6 +569,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
),
],
),
*/
Container(
width: double.infinity,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
import 'dart:io';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:easy_localization/src/public_ext.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
@ -12,7 +12,6 @@ import 'package:flutter/services.dart';
// import 'package:huawei_hmsavailability/huawei_hmsavailability.dart';
import 'package:mohem_flutter_app/api/login_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:intl/intl.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/notifications.dart';
@ -27,6 +26,7 @@ import 'package:mohem_flutter_app/models/basic_member_information_model.dart';
import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart';
import 'package:mohem_flutter_app/models/dependent/get_mobile_login_info_dependent_model.dart';
import 'package:mohem_flutter_app/models/member_information_list_model.dart';
import 'package:mohem_flutter_app/models/member_login_list_model.dart';
import 'package:mohem_flutter_app/models/privilege_list_model.dart';
@ -53,6 +53,7 @@ class _LoginScreenState extends State<LoginScreen> {
TextEditingController dobController = TextEditingController();
DateTime? selectedDob;
// ignore: unused_field
CheckMobileAppVersionModel? _checkMobileAppVersion;
MemberLoginListModel? _memberLoginList;
@ -76,6 +77,9 @@ class _LoginScreenState extends State<LoginScreen> {
super.initState();
ApiConsts.selectedMemberType = MemberType.employee;
ApiConsts.ISDependetLogin = false;
nationalId.addListener(_onCredentialsChanged);
dobController.addListener(_onCredentialsChanged);
checkDependentPrefs();
// hmsApiAvailability = HmsApiAvailability();
// checkFirebaseToken();
if (kReleaseMode) {
@ -83,6 +87,12 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
void _onCredentialsChanged() {
if (mounted && ApiConsts.ISDependetLogin) {
setState(() {});
}
}
void checkDeviceSafety() {
try {
SafeDevice.isJailBroken.then((bool value) {
@ -105,12 +115,21 @@ class _LoginScreenState extends State<LoginScreen> {
@override
void dispose() {
nationalId.removeListener(_onCredentialsChanged);
dobController.removeListener(_onCredentialsChanged);
dobController.dispose();
super.dispose();
}
String? firebaseToken;
GetMobileLoginInfoListModel? loginInfo;
GetMobileLoginInfoDependentModel? dependentLoginInfo;
bool get _hasDeviceDependentData {
if (dependentLoginInfo == null) return false;
String id = dependentLoginInfo?.nationalId ?? dependentLoginInfo?.dependentID ?? "";
return id.trim().isNotEmpty;
}
Future<void> checkFirebaseToken() async {
try {
@ -129,6 +148,10 @@ class _LoginScreenState extends State<LoginScreen> {
_firebaseMessaging = FirebaseMessaging.instance;
try {
firebaseToken = await _firebaseMessaging.getToken();
if (firebaseToken != null && firebaseToken!.isNotEmpty) {
AppState().setDeviceToken = firebaseToken;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", firebaseToken!);
}
} catch (ex) {
firebaseToken = "";
}
@ -149,6 +172,10 @@ class _LoginScreenState extends State<LoginScreen> {
await Firebase.initializeApp();
_firebaseMessaging = FirebaseMessaging.instance;
firebaseToken = await _firebaseMessaging.getToken();
if (firebaseToken != null && firebaseToken!.isNotEmpty) {
AppState().setDeviceToken = firebaseToken;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", firebaseToken!);
}
String? firebaseAPNSToken = await _firebaseMessaging.getAPNSToken();
print("Firebase Token: $firebaseToken");
print("Firebase APNS Token: $firebaseAPNSToken");
@ -165,15 +192,52 @@ class _LoginScreenState extends State<LoginScreen> {
void checkLoginInfo() async {
try {
loginInfo = await LoginApiClient().getMobileLoginInfoNEW(
AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken ?? "",
Platform.isAndroid ? "android" : "ios",
memberType: ApiConsts.selectedMemberType.value,
);
String devToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (firebaseToken ?? "");
if (devToken.isEmpty && !AppState().getIsHuawei) {
if (AppState().getDeviceToken != null && AppState().getDeviceToken!.isNotEmpty) {
devToken = AppState().getDeviceToken!;
firebaseToken = devToken;
} else {
String saved = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (saved.isNotEmpty) {
devToken = saved;
firebaseToken = devToken;
} else {
try {
await Firebase.initializeApp();
String? tok = await FirebaseMessaging.instance.getToken();
if (tok != null && tok.isNotEmpty) {
devToken = tok;
firebaseToken = tok;
AppState().setDeviceToken = tok;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", tok);
}
} catch (e) {
if (kDebugMode) print("Error fetching FCM token in checkLoginInfo: $e");
}
}
}
}
String devType = Platform.isAndroid ? "android" : "ios";
if (ApiConsts.selectedMemberType.isDependent) {
dependentLoginInfo = await LoginApiClient().getMobileLoginInfoNEWDependent(
devToken,
devType,
memberType: MemberType.dependent.value,
);
Utils.hideLoading(context);
if (dependentLoginInfo != null) {
_handleDependentLoginInfo(dependentLoginInfo!);
}
return;
}
loginInfo = await LoginApiClient().getMobileLoginInfoNEW(
devToken,
devType,
memberType: ApiConsts.selectedMemberType.value,
);
if (loginInfo == null) {
await checkPrefs();
_autoLogin = false;
@ -190,6 +254,100 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
void _handleDependentLoginInfo(GetMobileLoginInfoDependentModel info) {
if (info.nationalId != null && info.nationalId!.isNotEmpty) {
nationalId.text = info.nationalId!;
} else if (info.dependentID != null && info.dependentID!.isNotEmpty) {
nationalId.text = info.dependentID!;
}
String? rawDob = (info.birthDate != null && info.birthDate!.isNotEmpty)
? info.birthDate
: ((info.dob != null && info.dob!.isNotEmpty) ? info.dob : null);
if (rawDob != null && rawDob.isNotEmpty) {
DateTime? parsed = _parseBirthDate(rawDob);
if (parsed != null) {
selectedDob = parsed;
dobController.text = DateFormat('yyyy-MM-dd').format(parsed);
} else {
dobController.text = rawDob;
}
}
if (info.dependentName != null && info.dependentName!.isNotEmpty) {
AppState().setDependentName = info.dependentName;
Utils.saveStringFromPrefs("P_DEPENDENT_NAME", info.dependentName!);
}
}
DateTime? _parseBirthDate(String? dateStr) {
if (dateStr == null || dateStr.trim().isEmpty) return null;
String s = dateStr.trim();
// 1. Handle Microsoft WCF JSON Date format: /Date(889401600000)/ or /Date(889401600000+0300)/
RegExp dateRegex = RegExp(r'/?Date\((-?\d+)(?:[+-]\d+)?\)/?');
Match? match = dateRegex.firstMatch(s);
if (match != null) {
try {
int millis = int.parse(match.group(1)!);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
// 2. Handle pure epoch milliseconds string e.g. "889401600000"
if (RegExp(r'^-?\d{10,14}$').hasMatch(s)) {
try {
int millis = int.parse(s);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
// 3. Try standard ISO-8601 (yyyy-MM-dd, yyyy-MM-ddTHH:mm:ss, etc.)
DateTime? dt = DateTime.tryParse(s);
if (dt != null) return DateTime(dt.year, dt.month, dt.day);
// 4. Try standard custom date patterns
List<String> formats = ['yyyy-MM-dd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'dd-MM-yyyy', 'yyyy/MM/dd'];
for (String fmt in formats) {
try {
DateTime parsed = DateFormat(fmt).parseStrict(s);
return DateTime(parsed.year, parsed.month, parsed.day);
} catch (_) {}
}
return null;
}
Future<void> checkDependentPrefs() async {
if (nationalId.text.isEmpty) {
String savedNid = await Utils.getStringFromPrefs("P_DEP_NATIONAL_ID");
if (savedNid.isNotEmpty) nationalId.text = savedNid;
}
if (dobController.text.isEmpty) {
String savedDob = await Utils.getStringFromPrefs("P_DEP_BIRTH_DATE");
if (savedDob.isNotEmpty) {
DateTime? parsed = _parseBirthDate(savedDob);
if (parsed != null) {
selectedDob = parsed;
dobController.text = DateFormat('yyyy-MM-dd').format(parsed);
} else {
dobController.text = savedDob;
}
}
} else {
// If dobController already has raw /Date(...) text, parse and format it cleanly
DateTime? parsed = _parseBirthDate(dobController.text);
if (parsed != null) {
selectedDob = parsed;
dobController.text = DateFormat('yyyy-MM-dd').format(parsed);
}
}
}
Future<void> checkPrefs() async {
String username = await Utils.getStringFromPrefs(SharedPrefsConsts.username);
if (username.isNotEmpty) {
@ -294,81 +452,111 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
void _onTabChanged(MemberType type) {
void _switchToMode(MemberType type) async {
if (ApiConsts.selectedMemberType != type) {
setState(() {
ApiConsts.selectedMemberType = type;
ApiConsts.ISDependetLogin = (type == MemberType.dependent);
});
if (type == MemberType.dependent) {
await checkDependentPrefs();
}
if (firebaseToken == null || firebaseToken!.isEmpty) {
String saved = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (saved.isNotEmpty) {
firebaseToken = saved;
}
}
checkLoginInfo();
}
}
Widget _buildTabSwitcher() {
bool isEmployee = ApiConsts.selectedMemberType == MemberType.employee;
bool isAr = AppState().isArabic(context);
return Container(
height: 46,
decoration: BoxDecoration(
color: const Color(0xffF2F4F7),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(4),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: isEmployee ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(9),
boxShadow: isEmployee
? [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: null,
Widget _buildCtaCard({
required BuildContext context,
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
bool isRtl = Directionality.of(context) == ui.TextDirection.rtl || AppState().isArabic(context);
return InkWell(
borderRadius: BorderRadius.circular(14),
onTap: onTap,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFEAF8F4),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFB8E8DB), width: 1.0),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: (isAr ? "الموظف" : "Employee").toText14(
color: isEmployee ? MyColors.textMixColor : MyColors.grey70Color,
isBold: isEmployee,
),
).onPress(() {
_onTabChanged(MemberType.employee);
}),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: !isEmployee ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(9),
boxShadow: !isEmployee
? [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: null,
child: Icon(
icon,
color: const Color(0xFF168A89),
size: 22,
),
alignment: Alignment.center,
child: (isAr ? "التابع" : "Dependent").toText14(
color: !isEmployee ? MyColors.textMixColor : MyColors.grey70Color,
isBold: !isEmployee,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
title.toText14(
isBold: true,
color: const Color(0xFF1E293B),
textAlign: isRtl ? TextAlign.right : TextAlign.left,
),
const SizedBox(height: 4),
subtitle.toText12(
color: const Color(0xFF64748B),
),
],
),
).onPress(() {
_onTabChanged(MemberType.dependent);
}),
),
],
),
const SizedBox(width: 8),
Icon(
isRtl ? Icons.chevron_left_rounded : Icons.chevron_right_rounded,
color: const Color(0xFF64748B),
size: 20,
),
],
),
),
);
}
Widget _buildDependentCtaCard(BuildContext context) {
return _buildCtaCard(
context: context,
icon: Icons.person_outline_rounded,
title: LocaleKeys.joinMazaya.tr(),
subtitle: LocaleKeys.joinMazayaSubtitle.tr(),
onTap: () => _switchToMode(MemberType.dependent),
);
}
Widget _buildEmployeeCtaCard(BuildContext context) {
return _buildCtaCard(
context: context,
icon: Icons.badge_outlined,
title: LocaleKeys.continueAsEmployee.tr(),
subtitle: LocaleKeys.continueAsEmployeeSubtitle.tr(),
onTap: () => _switchToMode(MemberType.employee),
);
}
Future<void> _selectDateOfBirth(BuildContext context) async {
DateTime initialDate = selectedDob ?? DateTime(2000, 1, 1);
DateTime firstDate = DateTime(1920, 1, 1);
@ -402,37 +590,7 @@ class _LoginScreenState extends State<LoginScreen> {
}
String getFormattedBirthDate() {
DateTime? date = selectedDob;
if (date == null && dobController.text.trim().isNotEmpty) {
String text = dobController.text.trim();
try {
if (text.contains('-')) {
List<String> parts = text.split('-');
if (parts[0].length == 4) {
// yyyy-MM-dd
date = DateTime.utc(int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
} else if (parts[2].length == 4) {
// dd-MM-yyyy
date = DateTime.utc(int.parse(parts[2]), int.parse(parts[1]), int.parse(parts[0]));
}
} else if (text.contains('/')) {
List<String> parts = text.split('/');
if (parts[0].length == 4) {
// yyyy/MM/dd
date = DateTime.utc(int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
} else if (parts[2].length == 4) {
// dd/MM/yyyy
date = DateTime.utc(int.parse(parts[2]), int.parse(parts[1]), int.parse(parts[0]));
}
} else {
DateTime parsed = DateTime.parse(text);
date = DateTime.utc(parsed.year, parsed.month, parsed.day);
}
} catch (_) {
return "";
}
}
DateTime? date = selectedDob ?? _parseBirthDate(dobController.text.trim());
if (date != null) {
int utcMillis = DateTime.utc(date.year, date.month, date.day).millisecondsSinceEpoch;
return "/Date($utcMillis)/";
@ -442,20 +600,35 @@ class _LoginScreenState extends State<LoginScreen> {
void performDependentLogin() async {
if (nationalId.text.trim().isEmpty) {
Utils.showToast(AppState().isArabic(context) ? "الرجاء إدخال رقم الهوية الوطنية" : "Please enter National ID");
Utils.showToast(LocaleKeys.pleaseEnterNationalId.tr());
return;
}
String birthDateStr = getFormattedBirthDate();
if (birthDateStr.isEmpty) {
Utils.showToast(AppState().isArabic(context) ? "الرجاء تحديد تاريخ الميلاد" : "Please select Date of Birth");
Utils.showToast(LocaleKeys.pleaseSelectDob.tr());
return;
}
Utils.showLoading(context);
try {
String devToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (firebaseToken ?? "");
if (devToken.isEmpty && !AppState().getIsHuawei) {
if (AppState().getDeviceToken != null && AppState().getDeviceToken!.isNotEmpty) {
devToken = AppState().getDeviceToken!;
} else {
String saved = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (saved.isNotEmpty) {
devToken = saved;
}
}
}
String devType = Platform.isAndroid ? "android" : "ios";
GenericResponseModel? depLoginResponse = await LoginApiClient().memberLoginDependent(
nationalId: nationalId.text.trim(),
birthDate: birthDateStr,
deviceToken: devToken,
deviceType: devType,
context: context,
);
@ -465,16 +638,44 @@ class _LoginScreenState extends State<LoginScreen> {
return;
}
if (depLoginResponse?.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = depLoginResponse!.getWalaplusDepInfoModel;
String? depName = depLoginResponse.getWalaplusDepInfoModel?.pDependentName;
if (depName != null && depName.trim().isNotEmpty) {
AppState().setDependentName = depName.trim();
Utils.saveStringFromPrefs("P_DEPENDENT_NAME", depName.trim());
}
}
String? authToken = (depLoginResponse?.authenticationTokenID != null &&
depLoginResponse!.authenticationTokenID!.trim().isNotEmpty)
? depLoginResponse.authenticationTokenID!.trim()
: null;
String? logInTokenID = depLoginResponse?.logInTokenID;
String? mobileNumber = depLoginResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber;
if (logInTokenID == null || mobileNumber == null || mobileNumber.isEmpty) {
// If AuthenticationTokenID is present in MemberLogin_Dependent response:
// No need to show OTP screen, direct move to dashboard and use AuthenticationTokenID as TokenID for all other requests
if (authToken != null && authToken.isNotEmpty) {
Utils.hideLoading(context);
_completeDependentLogin(
depLoginResponse,
logInTokenID ?? authToken,
mobileNumber ?? "",
loginType: 1,
navigateToDashboard: true,
);
return;
}
// Step 2: Send OTP if no previous login found on this device
if (logInTokenID == null || logInTokenID.isEmpty || mobileNumber == null || mobileNumber.isEmpty) {
Utils.hideLoading(context);
Utils.showToast(AppState().isArabic(context) ? "تعذر الحصول على معلومات التابع" : "Unable to retrieve dependent information");
Utils.showToast(LocaleKeys.unableToRetrieveDependentInfo.tr());
return;
}
// Step 2: Send OTP
GenericResponseModel? sendOtpResponse = await LoginApiClient().mohemmSendOTPDependent(
nationalId: nationalId.text.trim(),
mobileNumber: mobileNumber,
@ -488,10 +689,10 @@ class _LoginScreenState extends State<LoginScreen> {
// Step 3: Open OTP Dialog
_showDependentOtpDialog(logInTokenID, mobileNumber);
} else {
Utils.showToast(sendOtpResponse?.errorMessage ?? (AppState().isArabic(context) ? "فشل إرسال رمز التحقق" : "Failed to send OTP"));
Utils.showToast(sendOtpResponse?.errorMessage ?? LocaleKeys.failedToSendOtp.tr());
}
} catch (ex) {
print(ex);
if (kDebugMode) print(ex);
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
@ -522,31 +723,16 @@ class _LoginScreenState extends State<LoginScreen> {
}
Navigator.pop(context);
String effectiveToken = (checkOtpResponse?.tokenID != null && checkOtpResponse!.tokenID!.isNotEmpty)
? checkOtpResponse.tokenID!
: ((checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty)
? checkOtpResponse.logInTokenID!
: logInTokenID);
AppState().postParamsObject?.setTokenID = effectiveToken;
AppState().postParamsObject?.setLogInTokenID = (checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty)
? checkOtpResponse.logInTokenID!
: logInTokenID;
AppState().postParamsObject?.setUserName = nationalId.text.trim();
AppState().postParamsObject?.setPUserName = nationalId.text.trim();
AppState().postParamsObject?.setMobileNumer = mobileNumber;
AppState().postParamsObject?.channel = 31;
AppState().postParamsObject?.versionID = 20;
AppState().setUserName = nationalId.text.trim();
AppState().isAuthenticated = true;
AppState().setLogged = true;
Utils.showToast(AppState().isArabic(context) ? "تم التحقق بنجاح" : "Verified successfully");
if (ApiConsts.ISDependetLogin) {
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.dashboardScreenForDependet, (route) => false);
}
_completeDependentLogin(
checkOtpResponse,
logInTokenID,
mobileNumber,
loginType: 1,
navigateToDashboard: true,
);
} catch (ex) {
print(ex);
if (kDebugMode) print(ex);
_pinPutController.clear();
otpFieldClear.value = "";
Utils.hideLoading(context);
@ -566,7 +752,7 @@ class _LoginScreenState extends State<LoginScreen> {
context: context,
);
Utils.hideLoading(context);
Utils.showToast(AppState().isArabic(context) ? "تم إعادة إرسال رمز التحقق" : "Verification code resent");
Utils.showToast(LocaleKeys.verificationCodeResent.tr());
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
@ -575,6 +761,65 @@ class _LoginScreenState extends State<LoginScreen> {
).displayDialog(context);
}
void _completeDependentLogin(GenericResponseModel? checkOtpResponse, String logInTokenID, String mobileNumber, {int loginType = 1, bool navigateToDashboard = true}) {
String effectiveToken = (checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty)
? checkOtpResponse.authenticationTokenID!.trim()
: ((checkOtpResponse?.tokenID != null && checkOtpResponse!.tokenID!.isNotEmpty)
? checkOtpResponse.tokenID!
: ((checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty)
? checkOtpResponse.logInTokenID!
: logInTokenID));
AppState().postParamsObject?.setTokenID = effectiveToken;
AppState().setAuthenticationTokenID = effectiveToken;
AppState().postParamsObject?.setLogInTokenID = (checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty)
? checkOtpResponse.logInTokenID!
: logInTokenID;
AppState().postParamsObject?.setUserName = nationalId.text.trim();
AppState().postParamsObject?.setPUserName = nationalId.text.trim();
String resolvedMobile = mobileNumber.isNotEmpty
? mobileNumber
: (checkOtpResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber ?? "");
if (resolvedMobile.isNotEmpty) {
AppState().postParamsObject?.setMobileNumer = resolvedMobile;
Utils.saveStringFromPrefs("P_DEP_MOBILE_NUMBER", resolvedMobile);
}
AppState().postParamsObject?.channel = 31;
AppState().postParamsObject?.versionID = 20;
AppState().setUserName = nationalId.text.trim();
AppState().isAuthenticated = true;
AppState().setLogged = true;
if (checkOtpResponse?.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = checkOtpResponse!.getWalaplusDepInfoModel;
String? depName = checkOtpResponse.getWalaplusDepInfoModel?.pDependentName;
if (depName != null && depName.trim().isNotEmpty) {
AppState().setDependentName = depName.trim();
Utils.saveStringFromPrefs("P_DEPENDENT_NAME", depName.trim());
}
}
AppState().setDependentLoginType = loginType;
AppState().setDependentDob = getFormattedBirthDate();
Utils.saveStringFromPrefs("P_DEP_NATIONAL_ID", nationalId.text.trim());
Utils.saveStringFromPrefs("P_DEP_BIRTH_DATE", getFormattedBirthDate());
Utils.saveStringFromPrefs("P_DEP_LOGIN_TOKEN_ID", AppState().postParamsObject?.logInTokenID ?? logInTokenID);
Utils.saveStringFromPrefs("P_DEP_TOKEN_ID", effectiveToken);
if (checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty) {
Utils.saveStringFromPrefs("P_DEP_AUTHENTICATION_TOKEN_ID", checkOtpResponse.authenticationTokenID!.trim());
}
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken ?? "");
Utils.saveStringFromPrefs("P_DEP_LOGIN_TYPE", loginType.toString());
if (navigateToDashboard && ApiConsts.ISDependetLogin) {
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.dashboardScreenForDependet, (route) => false);
}
}
@override
Widget build(BuildContext context) {
if (isAppOpenBySystem == null) {
@ -607,9 +852,6 @@ class _LoginScreenState extends State<LoginScreen> {
children: [
Row(
children: [
// Expanded(
// child:SizedBox(child: HmgConnectivityButton(),),
// ),
Row(
children: [
LocaleKeys.english.tr().toText14(color: AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() {
@ -629,83 +871,91 @@ class _LoginScreenState extends State<LoginScreen> {
],
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildTabSwitcher(),
20.height,
LocaleKeys.login.tr().toText24(isBold: true),
LocaleKeys.pleaseEnterLoginDetails.tr().toText16(),
16.height,
if (ApiConsts.selectedMemberType == MemberType.employee) ...[
InputWidget(LocaleKeys.username.tr(), "123456", username),
12.height,
InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isTextIsPassword: true),
9.height,
Align(
alignment: Alignment.centerRight,
child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(
() {
// Navigator.pushNamed(context, AppRoutes.forgotPassword);
performForgotPassword();
},
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
),
20.height,
] else ...[
InputWidget(AppState().isArabic(context) ? "الهوية الوطنية" : "National ID", "1234567890", nationalId, isInputTypeNum: true),
12.height,
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _selectDateOfBirth(context),
child: Stack(
alignment: AlignmentDirectional.centerEnd,
children: [
IgnorePointer(
child: InputWidget(
LocaleKeys.dateOfBirth.tr(),
"YYYY-MM-DD",
dobController,
),
),
Positioned.directional(
textDirection: Directionality.of(context),
end: 16,
child: const Icon(
Icons.calendar_today_outlined,
size: 20,
color: MyColors.grey70Color,
),
),
],
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocaleKeys.login.tr().toText24(isBold: true),
LocaleKeys.pleaseEnterLoginDetails.tr().toText16(),
16.height,
if (ApiConsts.selectedMemberType == MemberType.employee) ...[
InputWidget(LocaleKeys.username.tr(), "123456", username),
12.height,
InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isTextIsPassword: true),
9.height,
Align(
alignment: AlignmentDirectional.centerEnd,
child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(
() {
performForgotPassword();
},
),
),
20.height,
_buildDependentCtaCard(context),
] else ...[
InputWidget(LocaleKeys.nationalId.tr(), "1234567890", nationalId, isInputTypeNum: true),
12.height,
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _selectDateOfBirth(context),
child: Stack(
alignment: AlignmentDirectional.centerEnd,
children: [
IgnorePointer(
child: InputWidget(
LocaleKeys.dateOfBirth.tr(),
"YYYY-MM-DD",
dobController,
),
),
Positioned.directional(
textDirection: Directionality.of(context),
end: 16,
child: const Icon(
Icons.calendar_today_outlined,
size: 20,
color: MyColors.grey70Color,
),
),
],
),
),
20.height,
_buildEmployeeCtaCard(context),
],
],
),
),
),
20.height,
],
// DefaultButton(
// "Connect HMG Network",
// () async {
// SystemChannels.textInput.invokeMethod('TextInput.hide');
// connectWithHmgNetwork();
// },
// ),
],
);
},
),
)
],
),
),
),
DefaultButton(LocaleKeys.login.tr(), () async {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (ApiConsts.ISDependetLogin) {
performDependentLogin();
} else {
performLogin();
}
}).insideContainer
DefaultButton(
LocaleKeys.login.tr(),
() async {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (ApiConsts.ISDependetLogin) {
performDependentLogin();
} else {
performLogin();
}
},
).insideContainer
],
),
);

@ -52,7 +52,7 @@ class _DynamicInputScreenState extends State<DynamicInputScreen> {
genericResponseModel = await MyAttendanceApiClient().getEitDffStructure(dynamicParams!.dynamicId, dynamicParams!.selectedEmp);
getEitDffStructureList = genericResponseModel?.getEITDFFStructureList ?? [];
} else {
genericResponseModel = await MyAttendanceApiClient().getCEIDffStructure(dynamicParams!.dynamicId, null);
genericResponseModel = await MyAttendanceApiClient().getCEIDffStructure(dynamicParams!.dynamicId, null, dynamicParams?.pContactRelationshipId); //added pContactRelationshipId this based on the ERP requested.
getEitDffStructureList = genericResponseModel?.getCEIDFFStructureList ?? [];
}

File diff suppressed because it is too large Load Diff

@ -1,19 +1,16 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart';
import 'package:mohem_flutter_app/models/get_employee_contacts.model.dart';
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:provider/provider.dart';
@ -25,9 +22,13 @@ class FamilyMembers extends StatefulWidget {
}
class _FamilyMembersState extends State<FamilyMembers> {
List<GetEmployeeContactsList> getEmployeeContactsList = [];
List<GetEmployeeContactsList> getEmployeeContactsList = <GetEmployeeContactsList>[];
bool fromMazaya = false;
bool _argsLoaded = false;
bool _isLoading = false;
int? relationId;
final TextEditingController _searchController = TextEditingController();
String _searchQuery = "";
@override
void initState() {
@ -35,18 +36,131 @@ class _FamilyMembersState extends State<FamilyMembers> {
getEmployeeContacts();
}
void getEmployeeContacts() async {
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_argsLoaded) {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is Map && args['fromMazaya'] == true) {
fromMazaya = true;
}
_argsLoaded = true;
}
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
bool _isAccessAllowed(GetEmployeeContactsList contact) {
String? flag = contact.mAZAYAACCESSFLG ?? contact.mazayaAccess?.toString();
return flag?.trim().toUpperCase() == 'Y';
}
Future<void> _toggleMazayaAccess(GetEmployeeContactsList contact) async {
bool current = _isAccessAllowed(contact);
String action = current ? "Revoke" : "Grant";
try {
Utils.showLoading(context);
getEmployeeContactsList = await ProfileApiClient().getEmployeeContacts();
await ProfileApiClient().processContactMazayaAccess(
accessAction: action,
contactRelationshipId: contact.cONTACTRELATIONSHIPID,
contactPersonId: contact.cONTACTPERSONID,
personId: contact.pERSONID,
);
List<GetEmployeeContactsList> rawList = await ProfileApiClient().getEmployeeContacts();
getEmployeeContactsList = rawList;
Utils.hideLoading(context);
setState(() {});
if (mounted) {
setState(() {});
if (action == "Grant") {
Navigator.push(
context,
MaterialPageRoute<dynamic>(
builder: (BuildContext cxt) => MazayaAccessResultScreen(
isGranted: true,
dependentName: contact.cONTACTNAME ?? "",
dependentInitials: _getInitials(contact.cONTACTNAME),
),
),
);
} else {
Utils.showToast(LocaleKeys.revokeMazayaAccess.tr());
}
}
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
}
String _getInitials(String? name) {
if (name == null || name.trim().isEmpty) return "D";
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
if (parts.isEmpty) return "D";
if (parts.length == 1) {
return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase();
}
return "${parts.first[0]}${parts.last[0]}".toUpperCase();
}
String _getIdEnding(GetEmployeeContactsList contact) {
String raw = contact.pERSONID?.toString() ??
contact.cONTACTPERSONID?.toString() ??
contact.cONTACTRELATIONSHIPID?.toString() ??
"";
if (raw.length > 4) {
return raw.substring(raw.length - 4);
}
if (raw.isNotEmpty) {
return raw.padLeft(4, '0');
}
return "";
}
List<GetEmployeeContactsList> get _filteredList {
if (_searchQuery.trim().isEmpty) return getEmployeeContactsList;
String q = _searchQuery.trim().toLowerCase();
return getEmployeeContactsList.where((GetEmployeeContactsList item) {
String name = (item.cONTACTNAME ?? "").toLowerCase();
String rel = (item.rELATIONSHIP ?? "").toLowerCase();
String id = _getIdEnding(item);
String personId = (item.pERSONID?.toString() ?? "").toLowerCase();
String contactPersonId = (item.cONTACTPERSONID?.toString() ?? "").toLowerCase();
String contactType = (item.cONTACTTYPE ?? "").toLowerCase();
return name.contains(q) ||
rel.contains(q) ||
id.contains(q) ||
personId.contains(q) ||
contactPersonId.contains(q) ||
contactType.contains(q);
}).toList();
}
void getEmployeeContacts() async {
setState(() {
_isLoading = true;
});
try {
List<GetEmployeeContactsList> rawList = await ProfileApiClient().getEmployeeContacts();
getEmployeeContactsList = rawList;
} catch (ex) {
Utils.handleException(ex, context, null);
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
GetMenuEntriesList _getContactMenuEntries(BuildContext context) {
List<GetMenuEntriesList> menuData = context.watch<DashboardProviderModel>().getMenuEntriesList ?? <GetMenuEntriesList>[];
List<GetMenuEntriesList> arr = menuData.where((GetMenuEntriesList e) => e.requestType == 'CONTACT').toList();
@ -55,181 +169,424 @@ class _FamilyMembersState extends State<FamilyMembers> {
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
GetMenuEntriesList menuEntries = _getContactMenuEntries(context);
bool canAddFamilyMember = menuEntries.addButton == 'Y';
bool canAddFamilyMember = menuEntries.addButton == 'Y' || menuEntries.updateButton == 'Y';
bool canUpdateFamilyMember = menuEntries.updateButton == 'Y';
bool canDeleteFamilyMember = menuEntries.deleteButton == 'Y';
List<GetEmployeeContactsList> displayList = _filteredList;
return Scaffold(
appBar: AppBarWidget(
context,
title: LocaleKeys.profile_familyDetails.tr(),
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: <Widget>[
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.pop(context),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.arrow_back_ios_new_rounded,
size: 20,
color: Color(0xFF1E293B),
),
),
),
const SizedBox(width: 8),
(fromMazaya
? LocaleKeys.myDependents.tr()
: LocaleKeys.profile_familyDetails.tr())
.toText20(
isBold: true,
color: const Color(0xFF1E293B),
),
const Spacer(),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.home_outlined,
size: 24,
color: Color(0xFF1E293B),
),
),
),
],
),
),
),
backgroundColor: MyColors.backgroundColor,
body: Column(
children: [
),
body: SafeArea(
child: Column(
children: <Widget>[
// Search Bar
Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 12),
child: Container(
height: 48,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Row(
children: <Widget>[
const Icon(Icons.search, color: Color(0xFF64748B), size: 20),
const SizedBox(width: 10),
Expanded(
child: TextField(
controller: _searchController,
onChanged: (String val) {
setState(() {
_searchQuery = val;
});
},
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
hintText: fromMazaya
? LocaleKeys.searchDependents.tr()
: LocaleKeys.searchFamilyMembers.tr(),
hintStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 14),
),
),
),
if (_searchQuery.isNotEmpty)
InkWell(
onTap: () {
_searchController.clear();
setState(() {
_searchQuery = "";
});
},
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.close, color: Color(0xFF94A3B8), size: 18),
),
),
],
),
),
),
// Dependents list
Expanded(
child: getEmployeeContactsList.isEmpty
? Utils.getNoDataWidget(context)
: ListView.separated(
padding: const EdgeInsets.all(21),
separatorBuilder: (cxt, index) => 12.height,
itemCount: getEmployeeContactsList.length,
itemBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
"${getEmployeeContactsList[index].cONTACTNAME}".toText16(color: MyColors.grey3AColor),
"${getEmployeeContactsList[index].rELATIONSHIP}".toText11(color: MyColors.textMixColor),
]).paddingOnly(top: 15, left: 13, right: 13, bottom: 12),
1.divider,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
// todo @faitma clean below logic and write clean code.
RichText(
text: TextSpan(
children: [
WidgetSpan(
child: Icon(
Icons.edit,
size: 14,
color: canUpdateFamilyMember ? MyColors.grey67Color : MyColors.lightGreyColor,
)),
TextSpan(
text: LocaleKeys.update.tr(),
style: TextStyle(
color: canUpdateFamilyMember ? MyColors.grey67Color : MyColors.lightGreyColor, fontSize: 12, letterSpacing: -0.36, fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = () async {
relationId = getEmployeeContactsList[index].cONTACTRELATIONSHIPID;
if (canUpdateFamilyMember && relationId != null) {
showUpdateAlertDialog(context, relationId, 2, "UPDATE");
}
})
],
),
),
Container(height: 35, width: 1, color: const Color(0xffEFEFEF)),
RichText(
text: TextSpan(
children: [
WidgetSpan(
child: Icon(
Icons.delete,
size: 15,
color: canDeleteFamilyMember ? MyColors.redColor : MyColors.lightGreyColor,
),
),
TextSpan(
text: LocaleKeys.remove.tr(),
style: TextStyle(
color: canDeleteFamilyMember ? MyColors.redColor : MyColors.lightGreyColor, fontSize: 12, letterSpacing: -0.36, fontWeight: FontWeight.w600),
),
],
),
).onPress(() {
relationId = getEmployeeContactsList[index].cONTACTRELATIONSHIPID;
if (canDeleteFamilyMember && relationId != null) {
showRemoveAlertDialog(context, relationId);
}
}),
],
),
],
).objectContainerView(disablePadding: true);
}),
child: _isLoading
? Center(
child: Image.asset(
"assets/images/logos/loading_mohemm_logo.gif",
height: 96.0,
width: 96.0,
),
)
: displayList.isEmpty
? Utils.getNoDataWidget(context)
: ListView.separated(
padding: const EdgeInsets.fromLTRB(20, 4, 20, 20),
separatorBuilder: (BuildContext cxt, int index) => const SizedBox(height: 12),
itemCount: displayList.length,
itemBuilder: (BuildContext context, int index) {
return _buildDependentCard(
displayList[index],
isAr,
canUpdateFamilyMember,
canDeleteFamilyMember,
);
},
),
),
// Bottom action button: + Add another user
DefaultButton(
LocaleKeys.addNewFamilyMember.tr(),
canAddFamilyMember
? () async {
Navigator.pushNamed(
context,
AppRoutes.addUpdateFamilyMember,
arguments: FamilyMemberScreenParams(null, flag: 1, actionType: "ADD"),
);
fromMazaya
? LocaleKeys.addAnotherUser.tr()
: LocaleKeys.addNewFamilyMember.tr(),
(canAddFamilyMember || fromMazaya)
? () async {
await Navigator.pushNamed(
context,
AppRoutes.addDependentVerify,
arguments: <String, dynamic>{'fromMazaya': fromMazaya},
);
getEmployeeContacts();
}
: null,
iconData: Icons.add,
isTextExpanded: false,
height: 48,
borderRadius: 12,
).insideContainer,
],
),
),
);
}
Widget _buildDependentCard(
GetEmployeeContactsList item,
bool isAr,
bool canUpdate,
bool canDelete,
) {
bool isAllowed = _isAccessAllowed(item);
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color(0x0A000000),
blurRadius: 8,
offset: Offset(0, 3),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Top row: Avatar, Info, and Status Badge
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// Initials Circle
Container(
width: 44,
height: 44,
decoration: const BoxDecoration(
color: Color(0xFFE6F7F2),
shape: BoxShape.circle,
),
child: Center(
child: _getInitials(item.cONTACTNAME).toText16(
isBold: true,
color: const Color(0xFF168A89),
),
),
),
const SizedBox(width: 12),
// Name & Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
(item.cONTACTNAME ?? "").toText16(
isBold: true,
maxlines: 1,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 3),
Row(
children: <Widget>[
Flexible(
child: (item.rELATIONSHIP ?? "").toText12(
maxLine: 1,
color: const Color(0xFF168A89),
),
),
],
),
],
),
),
// Top right pill (shown in Mazaya flow)
if (fromMazaya) ...<Widget>[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isAllowed ? const Color(0xFFE8F8F0) : const Color(0xFFFEECEB),
borderRadius: BorderRadius.circular(20),
),
child: (isAllowed ? LocaleKeys.active.tr() : LocaleKeys.noAccess.tr()).toText11(
color: isAllowed ? const Color(0xFF2DBD72) : const Color(0xFFED1C24),
),
),
],
],
),
const SizedBox(height: 14),
// Bottom Action Row
if (fromMazaya) ...<Widget>[
// Mazaya Access Buttons: Revoke or Allow
InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () => _toggleMazayaAccess(item),
child: Container(
width: double.infinity,
height: 40,
decoration: BoxDecoration(
color: isAllowed ? const Color(0xFFFFF1F0) : const Color(0xFFE6F7F2),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isAllowed ? const Color(0xFFFFD9D7) : const Color(0xFFCEEFE7),
width: 0.8,
),
),
child: Center(
child: (isAllowed
? LocaleKeys.revokeMazayaAccess.tr()
: LocaleKeys.allowMazayaAccess.tr())
.toText14(
isBold: true,
isCenter: true,
color: isAllowed ? const Color(0xFFED1C24) : const Color(0xFF168A89),
),
),
),
),
] else ...<Widget>[
// Profile Flow: Update and Remove action buttons
Row(
children: <Widget>[
Expanded(
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: canUpdate
? () {
int? relationId = item.cONTACTRELATIONSHIPID;
if (relationId != null) {
showUpdateAlertDialog(context, relationId, 2, "UPDATE");
}
}
: null,
child: Container(
height: 38,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: canUpdate ? const Color(0xFFCBD5E1) : const Color(0xFFE2E8F0),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.edit_outlined,
size: 15,
color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8),
),
const SizedBox(width: 6),
LocaleKeys.update.tr().toText12(
color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8),
),
],
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: canDelete
? () {
int? relationId = item.cONTACTRELATIONSHIPID;
if (relationId != null) {
showRemoveAlertDialog(context, relationId);
}
}
: null)
.insideContainer,
: null,
child: Container(
height: 38,
decoration: BoxDecoration(
color: canDelete ? const Color(0xFFFFF5F5) : Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: canDelete ? const Color(0xFFFFCDD2) : const Color(0xFFE2E8F0),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.delete_outline_rounded,
size: 15,
color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8),
),
const SizedBox(width: 6),
LocaleKeys.remove.tr().toText12(
color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8),
),
],
),
),
),
),
],
),
],
));
],
),
);
}
void showUpdateAlertDialog(BuildContext context, relationId, int flag, String actionType) {
void showUpdateAlertDialog(BuildContext context, int relationId, int flag, String actionType) {
Widget cancelButton = TextButton(
child: Text(
LocaleKeys.cancel.tr(),
),
onPressed: () {
Navigator.pop(context);
},
child: LocaleKeys.cancel.tr().toText14(),
onPressed: () => Navigator.pop(context),
);
Widget continueButton = TextButton(
child: Text(
LocaleKeys.ok.tr(),
),
child: LocaleKeys.ok.tr().toText14(),
onPressed: () {
Navigator.pop(context);
Navigator.pushNamed(
context,
AppRoutes.addUpdateFamilyMember,
arguments:FamilyMemberScreenParams(relationId, flag: 2, actionType: "UPDATE"),
);
arguments: FamilyMemberScreenParams(relationId, flag: 2, actionType: "UPDATE"),
).then((_) => getEmployeeContacts());
},
);
AlertDialog alert = AlertDialog(
title: Text(
LocaleKeys.confirm.tr(),
),
content: Text(LocaleKeys.updateMember.tr()),
actions: [
cancelButton,
continueButton,
],
title: LocaleKeys.confirm.tr().toText16(isBold: true),
content: LocaleKeys.updateMember.tr().toText14(),
actions: <Widget>[cancelButton, continueButton],
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
builder: (BuildContext context) => alert,
);
}
void showRemoveAlertDialog(BuildContext context, relationId) {
void showRemoveAlertDialog(BuildContext context, int relationId) {
Widget cancelButton = TextButton(
child: Text(
LocaleKeys.cancel.tr(),
),
onPressed: () {
Navigator.pop(context);
},
child: LocaleKeys.cancel.tr().toText14(),
onPressed: () => Navigator.pop(context),
);
Widget continueButton = TextButton(
child: Text(
LocaleKeys.ok.tr(),
),
child: LocaleKeys.ok.tr().toText14(),
onPressed: () {
Navigator.pushNamed(context, AppRoutes.deleteFamilyMember, arguments: relationId);
// continueDynamicForms();
Navigator.pop(context);
Navigator.pushNamed(context, AppRoutes.deleteFamilyMember, arguments: relationId).then((_) => getEmployeeContacts());
},
);
AlertDialog alert = AlertDialog(
title: Text(
LocaleKeys.confirm.tr(),
),
content: Text(LocaleKeys.removeThisMember.tr()),
actions: [
cancelButton,
continueButton,
],
title: LocaleKeys.confirm.tr().toText16(isBold: true),
content: LocaleKeys.removeThisMember.tr().toText14(),
actions: <Widget>[cancelButton, continueButton],
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
builder: (BuildContext context) => alert,
);
}
}

@ -0,0 +1,674 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:share_plus/share_plus.dart';
class MazayaAccessResultParams {
final bool isGranted;
final String dependentName;
final String? dependentInitials;
final String? downloadUrl;
const MazayaAccessResultParams({
required this.isGranted,
required this.dependentName,
this.dependentInitials,
this.downloadUrl,
});
}
class _ShieldPainter extends CustomPainter {
final Color color;
final bool isGranted;
_ShieldPainter({required this.color, required this.isGranted});
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2.4
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round;
double w = size.width;
double h = size.height;
// Shield path
Path path = Path();
path.moveTo(w * 0.5, h * 0.1);
path.cubicTo(w * 0.72, h * 0.1, w * 0.88, h * 0.18, w * 0.88, h * 0.38);
path.cubicTo(w * 0.88, h * 0.65, w * 0.68, h * 0.84, w * 0.5, h * 0.92);
path.cubicTo(w * 0.32, h * 0.84, w * 0.12, h * 0.65, w * 0.12, h * 0.38);
path.cubicTo(w * 0.12, h * 0.18, w * 0.28, h * 0.1, w * 0.5, h * 0.1);
path.close();
canvas.drawPath(path, paint);
if (isGranted) {
// Checkmark path
Path checkPath = Path();
checkPath.moveTo(w * 0.34, h * 0.49);
checkPath.lineTo(w * 0.46, h * 0.61);
checkPath.lineTo(w * 0.66, h * 0.39);
canvas.drawPath(checkPath, paint);
} else {
// Cross path
canvas.drawLine(
Offset(w * 0.38, h * 0.38),
Offset(w * 0.62, h * 0.62),
paint,
);
canvas.drawLine(
Offset(w * 0.62, h * 0.38),
Offset(w * 0.38, h * 0.62),
paint,
);
}
}
@override
bool shouldRepaint(covariant _ShieldPainter oldDelegate) =>
color != oldDelegate.color || isGranted != oldDelegate.isGranted;
}
class MazayaAccessResultScreen extends StatelessWidget {
final bool isGranted;
final String dependentName;
final String? dependentInitials;
final String? downloadUrl;
const MazayaAccessResultScreen({
Key? key,
this.isGranted = true,
this.dependentName = "",
this.dependentInitials,
this.downloadUrl,
}) : super(key: key);
static String getDownloadLink() {
int channel = AppState().postParamsObject?.channel ?? 31;
bool isIos = Platform.isIOS;
if (channel == 33) {
return isIos
? "https://apps.apple.com/sa/app/al-habib-mohem/id1480157255"
: "https://play.google.com/store/apps/details?id=com.cloudsolutions.alhabibmohemm&hl=en";
} else {
return isIos
? "https://apps.apple.com/sa/app/mohemm/id1468856602"
: "https://play.google.com/store/apps/details?id=hmg.cloudSolutions.mohem&hl=en";
}
}
String _getInitials(String? name) {
if (name == null || name.trim().isEmpty) return "D";
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
if (parts.isEmpty) return "D";
if (parts.length == 1) {
return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase();
}
return "${parts.first[0]}${parts.last[0]}".toUpperCase();
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
String fontFamily = isAr ? 'Cairo' : 'Poppins';
// Resolve arguments if passed via ModalRoute
Object? routeArgs = ModalRoute.of(context)?.settings.arguments;
bool effectiveGranted = isGranted;
String effectiveName = dependentName;
String? effectiveInitials = dependentInitials;
String? effectiveUrl = downloadUrl;
if (routeArgs is MazayaAccessResultParams) {
effectiveGranted = routeArgs.isGranted;
effectiveName = routeArgs.dependentName;
effectiveInitials = routeArgs.dependentInitials;
effectiveUrl = routeArgs.downloadUrl;
} else if (routeArgs is Map<String, dynamic>) {
effectiveGranted = routeArgs['isGranted'] ?? isGranted;
effectiveName = routeArgs['dependentName'] ?? dependentName;
effectiveInitials = routeArgs['dependentInitials'] ?? dependentInitials;
effectiveUrl = routeArgs['downloadUrl'] ?? downloadUrl;
}
String linkToShare = effectiveUrl ?? getDownloadLink();
String initials = effectiveInitials ?? _getInitials(effectiveName);
return Scaffold(
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: <Widget>[
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.pop(context),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.arrow_back_ios_new_rounded,
size: 20,
color: Color(0xFF1E293B),
),
),
),
const SizedBox(width: 4),
LocaleKeys.mazayaTitle.tr().toText20(
isBold: true,
color: const Color(0xFF1E293B),
),
const Spacer(),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.pop(context),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.close_rounded,
size: 24,
color: Color(0xFF1E293B),
),
),
),
],
),
),
),
),
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 20),
child: effectiveGranted
? _buildGrantedContent(context, isAr, fontFamily, effectiveName, initials, linkToShare)
: _buildRevokedContent(context, isAr, fontFamily, effectiveName, initials),
),
),
_buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare),
],
),
),
);
}
Widget _buildGrantedContent(
BuildContext context,
bool isAr,
String fontFamily,
String name,
String initials,
String linkToShare,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 8),
// Shield checkmark badge
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
color: Color(0xFFE6F7F2),
shape: BoxShape.circle,
),
child: Center(
child: CustomPaint(
size: const Size(40, 40),
painter: _ShieldPainter(
color: const Color(0xFF168A89),
isGranted: true,
),
),
),
),
const SizedBox(height: 18),
// Title
LocaleKeys.mazayaAccessGranted.tr().toText22(
isBold: true,
isCentered: true,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 8),
// Description
LocaleKeys.mazayaAccessGrantedDesc.tr(args: <String>[name]).toText14(
isCenter: true,
color: const Color(0xFF64748B),
),
const SizedBox(height: 22),
// Access is now active banner
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFFE8F8F0),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFCEEFE7), width: 1),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Padding(
padding: EdgeInsets.only(top: 2),
child: Icon(
Icons.check_circle_outline_rounded,
color: Color(0xFF168A89),
size: 20,
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.accessIsNowActive.tr().toText14(
isBold: true,
color: const Color(0xFF168A89),
),
const SizedBox(height: 3),
LocaleKeys.accessIsNowActiveDesc.tr(args: <String>[name]).toText12(
color: const Color(0xFF64748B),
),
],
),
),
],
),
),
const SizedBox(height: 14),
// Dependent Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color(0x08000000),
blurRadius: 10,
offset: Offset(0, 3),
),
],
),
child: Row(
children: <Widget>[
Container(
width: 42,
height: 42,
decoration: const BoxDecoration(
color: Color(0xFFE6F7F2),
shape: BoxShape.circle,
),
child: Center(
child: initials.toText14(
isBold: true,
color: const Color(0xFF168A89),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
name.toText14(
isBold: true,
maxlines: 1,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 2),
LocaleKeys.dependentAccessActive.tr().toText12(
isBold: true,
color: const Color(0xFF168A89),
),
],
),
),
const Icon(
Icons.lock_open_rounded,
color: Color(0xFF168A89),
size: 20,
),
],
),
),
const SizedBox(height: 14),
// Shareable App-Download Link Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFE8F8F0),
borderRadius: BorderRadius.circular(10),
),
child: const Center(
child: Icon(
Icons.smartphone_rounded,
color: Color(0xFF168A89),
size: 20,
),
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.shareableAppDownloadLink.tr().toText14(
isBold: true,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 2),
LocaleKeys.shareableAppDownloadLinkDesc.tr(args: <String>[name]).toText12(
color: const Color(0xFF64748B),
),
],
),
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
decoration: BoxDecoration(
color: const Color(0xFFF8FAFC),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.downloadLink.tr().toText11(
isBold: true,
color: const Color(0xFF168A89),
),
const SizedBox(height: 3),
linkToShare.toText12(
maxLine: 1,
color: const Color(0xFF334155),
),
],
),
),
],
),
),
],
);
}
Widget _buildRevokedContent(
BuildContext context,
bool isAr,
String fontFamily,
String name,
String initials,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 8),
// Shield bad badge
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
color: Color(0xFFFEECEB),
shape: BoxShape.circle,
),
child: Center(
child: CustomPaint(
size: const Size(40, 40),
painter: _ShieldPainter(
color: const Color(0xFFED1C24),
isGranted: false,
),
),
),
),
const SizedBox(height: 18),
// Title
LocaleKeys.mazayaAccessRevoked.tr().toText22(
isBold: true,
isCentered: true,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 8),
// Description
LocaleKeys.mazayaAccessRevokedDesc.tr().toText14(
isCenter: true,
color: const Color(0xFF64748B),
),
const SizedBox(height: 22),
// Contact your sponsor banner
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFFFFF5F5),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFFFCDD2), width: 1),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Padding(
padding: EdgeInsets.only(top: 2),
child: Icon(
Icons.phone_outlined,
color: Color(0xFFED1C24),
size: 20,
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.contactYourSponsor.tr().toText14(
isBold: true,
color: const Color(0xFFED1C24),
),
const SizedBox(height: 3),
LocaleKeys.contactYourSponsorDesc.tr().toText12(
color: const Color(0xFF64748B),
),
],
),
),
],
),
),
const SizedBox(height: 14),
// Dependent Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color(0x08000000),
blurRadius: 10,
offset: Offset(0, 3),
),
],
),
child: Row(
children: <Widget>[
Container(
width: 42,
height: 42,
decoration: const BoxDecoration(
color: Color(0xFFFFF1F0),
shape: BoxShape.circle,
),
child: Center(
child: initials.toText14(
isBold: true,
color: const Color(0xFFED1C24),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
name.toText14(
isBold: true,
maxlines: 1,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 2),
LocaleKeys.dependentAccessBlocked.tr().toText12(
isBold: true,
color: const Color(0xFFED1C24),
),
],
),
),
const Icon(
Icons.lock_outline_rounded,
color: Color(0xFFED1C24),
size: 20,
),
],
),
),
],
);
}
Widget _buildBottomButtons(
BuildContext context,
bool isAr,
String fontFamily,
bool granted,
String linkToShare,
) {
if (granted) {
return Container(
color: Colors.white,
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
DefaultButton(
LocaleKeys.shareDownloadLink.tr(),
() async {
try {
await SharePlus.instance.share(ShareParams(text: linkToShare));
} catch (_) {}
},
trailingIconData: isAr ? Icons.arrow_back_rounded : Icons.arrow_forward_rounded,
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
const SizedBox(height: 10),
DefaultButton(
LocaleKeys.copyLink.tr(),
() {
Clipboard.setData(ClipboardData(text: linkToShare));
Utils.showToast(LocaleKeys.linkCopied.tr());
},
colors: const <Color>[Colors.white, Colors.white],
border: Border.all(color: const Color(0xFFCBD5E1)),
textColor: const Color(0xFF168A89),
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
const SizedBox(height: 10),
DefaultButton(
LocaleKeys.done.tr(),
() => Navigator.pop(context),
colors: const <Color>[Colors.white, Colors.white],
border: Border.all(color: const Color(0xFFCBD5E1)),
textColor: const Color(0xFF334155),
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
],
),
);
} else {
return Container(
color: Colors.white,
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: DefaultButton(
LocaleKeys.returnToSignIn.tr(),
() => Navigator.pop(context),
colors: const <Color>[Colors.white, Colors.white],
border: Border.all(color: const Color(0xFFCEEFE7), width: 1.2),
textColor: const Color(0xFF168A89),
iconData: isAr ? Icons.arrow_forward_rounded : Icons.arrow_back_rounded,
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
);
}
}
}

@ -0,0 +1,153 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:permission_handler/permission_handler.dart';
class AppInAppWebViewScreen extends StatefulWidget {
final String url;
final String? title;
const AppInAppWebViewScreen({
Key? key,
required this.url,
this.title,
}) : super(key: key);
@override
State<AppInAppWebViewScreen> createState() => _AppInAppWebViewScreenState();
}
class _AppInAppWebViewScreenState extends State<AppInAppWebViewScreen> {
InAppWebViewController? _webViewController;
double _progress = 0;
String _title = "";
@override
void initState() {
super.initState();
_title = widget.title ?? "";
_requestCameraPermission();
}
Future<void> _requestCameraPermission() async {
try {
PermissionStatus cameraStatus = await Permission.camera.status;
if (!cameraStatus.isGranted) {
await Permission.camera.request();
}
} catch (e) {
debugPrint("Error requesting camera permission: $e");
}
}
void _onHomeTapped() {
if (ApiConsts.isDependetLogin) {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboardScreenForDependet));
} else {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
}
}
Future<void> _handleBack() async {
if (_webViewController != null && await _webViewController!.canGoBack()) {
await _webViewController!.goBack();
} else {
if (mounted) {
Navigator.pop(context);
}
}
}
@override
Widget build(BuildContext context) {
String displayTitle = _title.isNotEmpty ? _title : LocaleKeys.offerDetails.tr();
return PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) async {
if (didPop) return;
await _handleBack();
},
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBarWidget(
context,
title: displayTitle,
showHomeButton: true,
onHomeTapped: _onHomeTapped,
onBackTapped: () async {
await _handleBack();
},
),
body: SafeArea(
child: Stack(
children: <Widget>[
InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(widget.url)),
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
domStorageEnabled: true,
supportZoom: true,
mediaPlaybackRequiresUserGesture: false,
allowsInlineMediaPlayback: true,
javaScriptCanOpenWindowsAutomatically: true,
transparentBackground: false,
useShouldOverrideUrlLoading: false,
cacheEnabled: true,
clearCache: false,
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
},
onPermissionRequest: (InAppWebViewController controller, PermissionRequest permissionRequest) async {
debugPrint("InAppWebView onPermissionRequest: ${permissionRequest.resources}");
try {
PermissionStatus status = await Permission.camera.status;
if (!status.isGranted) {
await Permission.camera.request();
}
} catch (e) {
debugPrint("Error requesting camera permission: $e");
}
return PermissionResponse(
resources: permissionRequest.resources,
action: PermissionResponseAction.GRANT,
);
},
onProgressChanged: (InAppWebViewController controller, int progress) {
if (mounted) {
setState(() {
_progress = progress / 100;
});
}
},
onTitleChanged: (InAppWebViewController controller, String? title) {
if (widget.title == null || widget.title!.isEmpty) {
if (title != null && title.isNotEmpty && !title.startsWith("http")) {
if (mounted) {
setState(() {
_title = title;
});
}
}
}
},
),
if (_progress < 1.0)
LinearProgressIndicator(
value: _progress,
color: const Color(0xFF168A89),
backgroundColor: Colors.transparent,
minHeight: 2.5,
),
],
),
),
),
);
}
}

@ -17,11 +17,15 @@ class DefaultButton extends StatelessWidget {
final Color? color;
final Color? disabledColor;
final IconData? iconData;
final IconData? trailingIconData;
final String? svgIcon;
final double? fontSize;
final bool isTextExpanded;
final int count;
final List<Color>? colors;
final BoxBorder? border;
final double? borderRadius;
final double? height;
const DefaultButton(
this.text,
@ -34,8 +38,12 @@ class DefaultButton extends StatelessWidget {
this.count = 0,
this.textColor = Colors.white,
this.iconData,
this.trailingIconData,
this.fontSize,
this.colors,
this.border,
this.borderRadius,
this.height,
}) : super(key: key);
@override
@ -43,9 +51,10 @@ class DefaultButton extends StatelessWidget {
return InkWell(
onTap: onPress,
child: Container(
height: 43,
height: height ?? 43,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
border: border,
borderRadius: BorderRadius.circular(borderRadius ?? 6.0),
gradient: onPress == null
? LinearGradient(
colors: <Color>[
@ -67,12 +76,12 @@ class DefaultButton extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (iconData != null) Icon(iconData, color: textColor),
if (iconData != null) Icon(iconData, color: textColor, size: 20),
if (svgIcon != null) SvgPicture.asset(svgIcon ?? "", color: textColor),
if (!isTextExpanded)
Padding(
padding: EdgeInsets.only(
left: (iconData ?? svgIcon) != null ? 6 : 0,
padding: EdgeInsets.symmetric(
horizontal: (iconData ?? svgIcon ?? trailingIconData) != null ? 8 : 0,
),
child: Text(
text,
@ -98,6 +107,7 @@ class DefaultButton extends StatelessWidget {
),
),
),
if (trailingIconData != null) Icon(trailingIconData, color: textColor, size: 20),
if (count > 0)
Align(
alignment: Alignment.topCenter,

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
class DynamicTextFieldWidget extends StatelessWidget {
final String labelText;
final String hintText;
// final TextEditingController controller;
final TextEditingController? controller;
final VoidCallback? onTap;
final IconData? suffixIconData;
final bool isEnable;
@ -18,9 +18,13 @@ class DynamicTextFieldWidget extends StatelessWidget {
final bool isObscureText;
final bool isBackgroundEnable;
final void Function(String)? onChange;
final int? maxLength;
final List<TextInputFormatter>? inputFormatters;
final Color? backgroundColor;
DynamicTextFieldWidget(this.labelText, this.hintText, //this.controller,
{this.isObscureText = false,
DynamicTextFieldWidget(this.labelText, this.hintText,
{this.controller,
this.isObscureText = false,
this.onTap,
this.suffixIconData,
this.isEnable = true,
@ -31,7 +35,10 @@ class DynamicTextFieldWidget extends StatelessWidget {
this.onChange,
this.isInputTypeNum = false,
this.isInputTypeNumSigned = true,
this.isBackgroundEnable = false});
this.isBackgroundEnable = false,
this.maxLength,
this.inputFormatters,
this.backgroundColor});
@override
Widget build(BuildContext context) {
@ -40,7 +47,7 @@ class DynamicTextFieldWidget extends StatelessWidget {
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: isReadOnly ? MyColors.lightGreyEFColor : Colors.white,
color: backgroundColor ?? (isReadOnly ? MyColors.lightGreyEFColor : Colors.white),
border: Border.all(
color: MyColors.lightGreyEFColor,
width: 1,
@ -66,6 +73,7 @@ class DynamicTextFieldWidget extends StatelessWidget {
),
),
TextField(
controller: controller,
enabled: isEnable,
scrollPadding: EdgeInsets.zero,
readOnly: isReadOnly,
@ -73,8 +81,9 @@ class DynamicTextFieldWidget extends StatelessWidget {
? (isInputTypeNumSigned ? const TextInputType.numberWithOptions(signed: true, decimal: true) : TextInputType.numberWithOptions(signed: true, decimal: true))
: TextInputType.text,
textInputAction: TextInputAction.done,
//controller: controller,
maxLines: lines,
maxLength: maxLength,
inputFormatters: inputFormatters,
obscuringCharacter: "*",
obscureText: isObscureText,
onChanged: onChange,
@ -86,9 +95,10 @@ class DynamicTextFieldWidget extends StatelessWidget {
letterSpacing: -0.44,
),
decoration: InputDecoration(
counterText: "",
isDense: true,
hintText: hintText,
fillColor: isReadOnly ? MyColors.borderColor : null,
fillColor: backgroundColor ?? (isReadOnly ? MyColors.borderColor : null),
hintStyle: const TextStyle(
fontSize: 14,
height: 21 / 14,

Loading…
Cancel
Save