Merge branch 'wala_plus_with_dialog_removal' of http://34.17.182.140/Haroon6138/mohemm-flutter-app into wala_plus_with_dialog_removal

* 'wala_plus_with_dialog_removal' of http://34.17.182.140/Haroon6138/mohemm-flutter-app:
  Fav Tab Changes and SearchBar Fix
  icons
master
Sultan khan 1 week ago
commit ae1bd7f0c5

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-right">
<path id="Vector" d="M3.1245 7.5H11.8755M7.5 11.8755L11.8755 7.5L7.5 3.1245" stroke-width="1.7" stroke-linecap="round" stroke="#147D6B"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 337 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-up-right">
<path id="Vector" d="M14.166 14.166V5.834H5.834M14.166 5.834L5.834 14.166" stroke-width="1.7" stroke-linecap="round" stroke="#143F36"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 338 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-down">
<path id="Vector" d="M3 4.5L6 7.5L9 4.5" stroke-width="1.7" stroke-linecap="round" stroke="#147D6B"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 302 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-left">
<path id="Vector" d="M12.5 15L7.5 10L12.5 5" stroke-width="1.7" stroke-linecap="round" stroke="#202F32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 306 B

@ -0,0 +1,10 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="compass" clip-path="url(#clip0_0_24)">
<path id="Vector" d="M20.1674 11C20.1674 16.063 16.063 20.1674 11 20.1674C5.93698 20.1674 1.8326 16.063 1.8326 11C1.8326 5.93698 5.93698 1.8326 11 1.8326C16.063 1.8326 20.1674 5.93698 20.1674 11ZM14.8868 7.11281L13.233 12.0733C13.143 12.3433 12.9913 12.5887 12.79 12.79C12.5887 12.9913 12.3433 13.143 12.0733 13.233L7.11281 14.8868L8.76661 9.92628C8.85661 9.65623 9.00827 9.41084 9.20955 9.20955C9.41084 9.00827 9.65623 8.85661 9.92628 8.76661L14.8868 7.11281Z" stroke-width="1.7" stroke-linecap="round" stroke="#147D6B"/>
</g>
<defs>
<clipPath id="clip0_0_24">
<rect width="22" height="22" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 845 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="heart">
<path id="Vector" d="M1.2495 5.93763C1.24951 5.24212 1.46051 4.56298 1.85463 3.9899C2.24875 3.41682 2.80744 2.97676 3.45693 2.72785C4.10641 2.47893 4.81612 2.43287 5.49233 2.59575C6.16855 2.75863 6.77944 3.12278 7.24434 3.64011C7.27708 3.67512 7.31667 3.70303 7.36064 3.72211C7.40462 3.74119 7.45204 3.75104 7.49998 3.75104C7.54792 3.75104 7.59534 3.74119 7.63932 3.72211C7.68329 3.70303 7.72288 3.67512 7.75563 3.64011C8.21906 3.11941 8.8301 2.7522 9.50741 2.58735C10.1847 2.42249 10.8962 2.46781 11.5471 2.71728C12.198 2.96674 12.7575 3.40851 13.1511 3.9838C13.5447 4.55909 13.7538 5.2406 13.7505 5.93763C13.7505 7.36889 12.8129 8.43765 11.8753 9.37516L8.44255 12.6958C8.32609 12.8296 8.18249 12.937 8.0213 13.011C7.86011 13.085 7.68502 13.1239 7.50766 13.125C7.3303 13.1261 7.15473 13.0895 6.99262 13.0175C6.83051 12.9456 6.68556 12.84 6.56741 12.7077L3.12464 9.37516C2.18707 8.43765 1.2495 7.37514 1.2495 5.93763Z" stroke-width="1.7" stroke-linecap="round" stroke="#202F32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="heart">
<path id="Vector" d="M1.8326 8.70852C1.83262 7.68845 2.14208 6.69237 2.72012 5.85185C3.29816 5.01134 4.11758 4.36592 5.07016 4.00084C6.02273 3.63577 7.06365 3.56821 8.05542 3.8071C9.0472 4.04598 9.94318 4.58007 10.625 5.33882C10.6731 5.39017 10.7311 5.43111 10.7956 5.45909C10.8601 5.48708 10.9297 5.50152 11 5.50152C11.0703 5.50152 11.1398 5.48708 11.2043 5.45909C11.2688 5.43111 11.3269 5.39017 11.3749 5.33882C12.0546 4.57514 12.9508 4.03656 13.9442 3.79478C14.9376 3.55299 15.9811 3.61946 16.9357 3.98534C17.8904 4.35122 18.711 4.99915 19.2883 5.8429C19.8656 6.68666 20.1722 7.68621 20.1673 8.70852C20.1673 10.8077 18.7922 12.3752 17.4171 13.7502L12.3824 18.6205C12.2116 18.8167 12.001 18.9743 11.7646 19.0828C11.5282 19.1913 11.2714 19.2483 11.0112 19.25C10.7511 19.2516 10.4936 19.1979 10.2558 19.0924C10.0181 18.9869 9.80549 18.8319 9.6322 18.638L4.58281 13.7502C3.20771 12.3752 1.8326 10.8169 1.8326 8.70852Z" stroke-width="1.7" stroke-linecap="round" stroke="#78817F"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="map-pin">
<path id="Vector" d="M9.9996 4.99997C9.9996 7.4967 7.23038 10.0969 6.30047 10.9C6.21384 10.9652 6.10839 11.0004 6 11.0004C5.89161 11.0004 5.78616 10.9652 5.69953 10.9C4.76962 10.0969 2.0004 7.4967 2.0004 4.99997C2.0004 3.93901 2.42179 2.9215 3.17186 2.17128C3.92193 1.42107 4.93924 0.9996 6 0.9996C7.06076 0.9996 8.07807 1.42107 8.82814 2.17128C9.57822 2.9215 9.9996 3.93901 9.9996 4.99997ZM7.49985 4.99997C7.49985 5.82847 6.82834 6.50011 6 6.50011C5.17166 6.50011 4.50015 5.82847 4.50015 4.99997C4.50015 4.17147 5.17166 3.49983 6 3.49983C6.82834 3.49983 7.49985 4.17147 7.49985 4.99997Z" stroke-linecap="round" stroke="#147D6B"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 826 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="search">
<path id="Vector" d="M15.7501 15.7501L12.4951 12.4951M14.25 8.25C14.25 11.5637 11.5637 14.25 8.25 14.25C4.93629 14.25 2.25 11.5637 2.25 8.25C2.25 4.93629 4.93629 2.25 8.25 2.25C11.5637 2.25 14.25 4.93629 14.25 8.25Z" stroke-width="1.7" stroke-linecap="round" stroke="#78817F"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 472 B

@ -0,0 +1,5 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="user-round">
<path id="Vector" d="M11 11.9167C13.5311 11.9167 15.5829 9.86464 15.5829 7.33333C15.5829 4.80203 13.5311 2.75 11 2.75C8.46895 2.75 6.41712 4.80203 6.41712 7.33333C6.41712 9.86464 8.46895 11.9167 11 11.9167ZM11 11.9167C12.9447 11.9167 14.8098 12.6893 16.1849 14.0646C17.5601 15.4398 18.3326 17.3051 18.3326 19.25M11 11.9167C9.05527 11.9167 7.1902 12.6893 5.81507 14.0646C4.43994 15.4398 3.6674 17.3051 3.6674 19.25" stroke-width="1.7" stroke-linecap="round" stroke="#78817F"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 674 B

@ -831,6 +831,7 @@
"locationServicesRequiredForNearestOffers": "فعّل خدمات الموقع لعرض العروض القريبة منك",
"locationPermissionRequiredForNearestOffers": "اسمح للتطبيق بالوصول إلى موقعك لعرض العروض القريبة منك",
"mazayaDeals": "عروض مزايا",
"helloName": "مرحباً، {}",
"findABrandOrSomething": "ابحث عن علامة تجارية أو شيء بسيط",
"exploreYourBenefits": "استكشف مزاياك",
"theMazayaEdit": "اختيارات مزايا",

@ -832,6 +832,7 @@
"locationServicesRequiredForNearestOffers": "You need to enable location services to show nearest offers",
"locationPermissionRequiredForNearestOffers": "You need to give location permission to show nearest offers",
"mazayaDeals": "Mazaya Deals",
"helloName": "Hello, {}",
"findABrandOrSomething": "Find a brand or a little something",
"exploreYourBenefits": "Explore your benefits",
"theMazayaEdit": "The Mazaya Edit",

@ -15,7 +15,8 @@ import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer
import 'package:mohem_flutter_app/widgets/location/Location.dart' as location;
class OffersAndDiscountsApiClient {
static final OffersAndDiscountsApiClient _instance = OffersAndDiscountsApiClient._internal();
static final OffersAndDiscountsApiClient _instance =
OffersAndDiscountsApiClient._internal();
static List<WalaPlusCategoryModel>? cachedCategories;
OffersAndDiscountsApiClient._internal();
@ -26,7 +27,12 @@ class OffersAndDiscountsApiClient {
List<GetCategoriesList> getSaleCategoriesList = [];
String url = "${ApiConsts.cocRest}Mohemm_ITG_GetCategories";
Map<String, dynamic> postParams = {"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER, "ItgPageSize": 100, "ItgPageNo": 1, "ItgIsActive": true};
Map<String, dynamic> postParams = {
"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER,
"ItgPageSize": 100,
"ItgPageNo": 1,
"ItgIsActive": true,
};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
@ -53,11 +59,19 @@ class OffersAndDiscountsApiClient {
);
}
Future<List<OffersListModel>> getOffersList(int categoryID, int pageSize) async {
Future<List<OffersListModel>> getOffersList(
int categoryID,
int pageSize,
) async {
List<OffersListModel> getSaleCategoriesList = [];
String url = "${ApiConsts.cocRest}GetOfferDiscountsConfigData";
Map<String, dynamic> postParams = {"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER, "ItgPageSize": pageSize, "ItgPageNo": 1, "ItgCategoryID": categoryID};
Map<String, dynamic> postParams = {
"EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER,
"ItgPageSize": pageSize,
"ItgPageNo": 1,
"ItgCategoryID": categoryID,
};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
@ -81,7 +95,10 @@ class OffersAndDiscountsApiClient {
String _resolveUserName() {
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = AppState().postParamsObject?.userName ?? AppState().postParamsObject?.pUserName ?? AppState().memberInformationList?.eMPLOYEENUMBER;
pUserName =
AppState().postParamsObject?.userName ??
AppState().postParamsObject?.pUserName ??
AppState().memberInformationList?.eMPLOYEENUMBER;
}
return pUserName ?? "";
}
@ -97,12 +114,15 @@ class OffersAndDiscountsApiClient {
if (!enabledCompleter.isCompleted) enabledCompleter.complete(isEnabled);
});
bool isEnabled = await enabledCompleter.future;
debugPrint("_getCurrentPositionSilently: location service isEnabled=$isEnabled");
debugPrint(
"_getCurrentPositionSilently: location service isEnabled=$isEnabled",
);
if (!isEnabled) return null;
Completer<bool> permissionCompleter = Completer<bool>();
location.Location.havePermission((bool hasPermission) {
if (!permissionCompleter.isCompleted) permissionCompleter.complete(hasPermission);
if (!permissionCompleter.isCompleted)
permissionCompleter.complete(hasPermission);
});
bool hasPermission = await permissionCompleter.future;
debugPrint("_getCurrentPositionSilently: havePermission=$hasPermission");
@ -111,11 +131,16 @@ class OffersAndDiscountsApiClient {
Completer<Position?> positionCompleter = Completer<Position?>();
location.Location.getCurrentLocation(
(Position position, bool isMocked) {
debugPrint("_getCurrentPositionSilently: got position lat=${position.latitude}, lng=${position.longitude}, isMocked=$isMocked");
if (!positionCompleter.isCompleted) positionCompleter.complete(position);
debugPrint(
"_getCurrentPositionSilently: got position lat=${position.latitude}, lng=${position.longitude}, isMocked=$isMocked",
);
if (!positionCompleter.isCompleted)
positionCompleter.complete(position);
},
() {
debugPrint("_getCurrentPositionSilently: getCurrentLocation error callback fired");
debugPrint(
"_getCurrentPositionSilently: getCurrentLocation error callback fired",
);
if (!positionCompleter.isCompleted) positionCompleter.complete(null);
},
context,
@ -134,6 +159,19 @@ class OffersAndDiscountsApiClient {
if (overrideMobile != null && overrideMobile.trim().isNotEmpty) {
return overrideMobile.trim();
}
// postParamsObject.mobileNumber/memberInformationList/memberLoginList are only ever set by
// the employee login flow and never cleared on logout - for a dependent session, the only
// legitimate source is the sponsor's mobile captured at dependent login (walaplusDepInfoModel),
// same reasoning as _resolveUserReference above.
if (ApiConsts.isDependetLogin) {
String? depMobile = AppState().walaplusDepInfoModel?.pEmpMobileNumber;
if (depMobile != null && depMobile.trim().isNotEmpty) {
return depMobile.trim();
}
return "";
}
String? mobile = AppState().postParamsObject?.mobileNumber;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
@ -161,7 +199,9 @@ class OffersAndDiscountsApiClient {
Map<String, dynamic> base = {};
base.addAll(AppState().postParamsJson);
if (ApiConsts.isDependetLogin) {
String? token = AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID;
String? token =
AppState().postParamsObject?.tokenID ??
AppState().authenticationTokenID;
if (token != null && token.isNotEmpty) {
base["TokenID"] = token;
}
@ -185,7 +225,11 @@ class OffersAndDiscountsApiClient {
// Pass parentId to fetch the subcategories under a given main category (WP_ParentId) -
// omit it (the default) to fetch the top-level main categories, same as before.
Future<List<WalaPlusCategoryModel>> getWalaPlusCategories({BuildContext? context, String? mobileNumber, String? parentId}) async {
Future<List<WalaPlusCategoryModel>> getWalaPlusCategories({
BuildContext? context,
String? mobileNumber,
String? parentId,
}) async {
String url = ApiConsts.walaPlusCategories;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
@ -209,7 +253,11 @@ class OffersAndDiscountsApiClient {
(response) {
List<WalaPlusCategoryModel> categories = [];
if (response != null) {
dynamic cats = response['WalaPlus_Categories'] ?? response['walaPlus_Categories'] ?? response['WalaPlusCategories'] ?? response['result'];
dynamic cats =
response['WalaPlus_Categories'] ??
response['walaPlus_Categories'] ??
response['WalaPlusCategories'] ??
response['result'];
if (cats is String) {
try {
cats = json.decode(cats);
@ -222,7 +270,9 @@ class OffersAndDiscountsApiClient {
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)));
categories.add(
WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(v)),
);
}
});
}
@ -279,7 +329,10 @@ class OffersAndDiscountsApiClient {
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
String effectiveCatId = (categoryId == "all" || categoryId == "0" || categoryId == null) ? "" : categoryId;
String effectiveCatId =
(categoryId == "all" || categoryId == "0" || categoryId == null)
? ""
: categoryId;
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["UserName"] = pUserName;
@ -315,7 +368,8 @@ class OffersAndDiscountsApiClient {
try {
Position? position = await _getCurrentPositionSilently(context);
if (position != null) {
postParams["WP_Geo_Location"] = "${position.latitude},${position.longitude}";
postParams["WP_Geo_Location"] =
"${position.latitude},${position.longitude}";
}
} catch (e) {
debugPrint("getWalaPlusRetailers: unable to get current location: $e");
@ -333,7 +387,12 @@ class OffersAndDiscountsApiClient {
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
dynamic val = response['WalaPlus_Retailers'] ?? response['walaPlus_Retailers'] ?? response['WalaPlusRetailers'] ?? response['WP_Retailers'] ?? response['result'];
dynamic val =
response['WalaPlus_Retailers'] ??
response['walaPlus_Retailers'] ??
response['WalaPlusRetailers'] ??
response['WP_Retailers'] ??
response['result'];
if (val is String) {
try {
val = json.decode(val);
@ -344,17 +403,26 @@ class OffersAndDiscountsApiClient {
if (val is Map<String, dynamic>) {
return WalaPlusRetailersResponse.fromJson(val);
} else if (val is Map) {
return WalaPlusRetailersResponse.fromJson(Map<String, dynamic>.from(val));
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)));
retailers.add(
WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(v)),
);
}
}
return WalaPlusRetailersResponse(retailers: retailers, page: 1, pagesCount: 1, perPage: retailers.length);
return WalaPlusRetailersResponse(
retailers: retailers,
page: 1,
pagesCount: 1,
perPage: retailers.length,
);
}
}
return null;
@ -364,12 +432,19 @@ class OffersAndDiscountsApiClient {
);
}
Future<WalaPlusRetailerModel?> getWalaPlusRetailerById({required String retailerId, BuildContext? context, String? mobileNumber, String? userReference}) async {
Future<WalaPlusRetailerModel?> getWalaPlusRetailerById({
required String retailerId,
BuildContext? context,
String? mobileNumber,
String? userReference,
}) async {
String url = ApiConsts.walaPlusRetailersByID;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
String pUserReference = _resolveUserReference(overrideUserReference: userReference);
String pUserReference = _resolveUserReference(
overrideUserReference: userReference,
);
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["WP_RetailerID"] = retailerId;
@ -390,10 +465,13 @@ class OffersAndDiscountsApiClient {
log("context position: $position");
if (position != null) {
postParams["WP_Geo_Location"] = "${position.latitude},${position.longitude}";
postParams["WP_Geo_Location"] =
"${position.latitude},${position.longitude}";
}
} catch (e) {
debugPrint("getWalaPlusRetailerById: unable to get current location: $e");
debugPrint(
"getWalaPlusRetailerById: unable to get current location: $e",
);
}
}
@ -406,18 +484,29 @@ class OffersAndDiscountsApiClient {
postParams["WP_User_Reference"] = null;
}
debugPrint("getWalaPlusRetailerById: calling $url with params: $postParams");
debugPrint(
"getWalaPlusRetailerById: calling $url with params: $postParams",
);
return await ApiClient().postJsonForObject(
(response) {
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'];
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) {
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_RetailersByID JSON string: $e");
debugPrint(
"Error decoding WalaPlus_RetailersByID JSON string: $e",
);
}
}
WalaPlusRetailerModel? model;
@ -426,47 +515,106 @@ class OffersAndDiscountsApiClient {
if (item is Map<String, dynamic>) {
model = WalaPlusRetailerModel.fromJson(item);
} else if (item is Map) {
model = 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);
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);
model =
byIdResponse.primaryRetailer ??
WalaPlusRetailerModel.fromJson(map);
} else {
model = WalaPlusRetailerModel.fromJson(map);
}
if (model?.isFavorite == null) {
if (map.containsKey('isfavorite')) {
model?.isFavorite = map['isfavorite'] == true || map['isfavorite'] == 1 || map['isfavorite'] == 'true' || map['isfavorite'] == '1';
model?.isFavorite =
map['isfavorite'] == true ||
map['isfavorite'] == 1 ||
map['isfavorite'] == 'true' ||
map['isfavorite'] == '1';
} else if (map.containsKey('is_favorite')) {
model?.isFavorite = map['is_favorite'] == true || map['is_favorite'] == 1 || map['is_favorite'] == 'true' || map['is_favorite'] == '1';
model?.isFavorite =
map['is_favorite'] == true ||
map['is_favorite'] == 1 ||
map['is_favorite'] == 'true' ||
map['is_favorite'] == '1';
}
}
if ((model?.offers == null || model!.offers!.isEmpty) && (map.containsKey('offers') || map.containsKey('Offers'))) {
model?.offers = WalaPlusRetailerModel.parseOffersList(map['offers'] ?? map['Offers']);
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']);
(map.containsKey('locations') ||
map.containsKey('Locations') ||
map.containsKey('location') ||
map.containsKey('Location'))) {
model?.locations = WalaPlusRetailerModel.parseLocationsList(
map['locations'] ??
map['Locations'] ??
map['location'] ??
map['Location'],
);
}
if ((model?.website == null || model!.website!.isEmpty || model!.website == "null")) {
String? web = (map['website'] ?? map['merchant_website'] ?? map['website_url'] ?? map['url'] ?? map['merchant_url'])?.toString();
if ((model?.website == null ||
model!.website!.isEmpty ||
model!.website == "null")) {
String? web =
(map['website'] ??
map['merchant_website'] ??
map['website_url'] ??
map['url'] ??
map['merchant_url'])
?.toString();
if (web != null && web.trim().isNotEmpty && web != "null") {
model?.website = web.trim();
}
}
if ((model?.categories == null || model!.categories!.isEmpty) && (map.containsKey('categories') || map.containsKey('Categories') || map.containsKey('category') || map.containsKey('Category'))) {
dynamic catData = map['categories'] ?? map['Categories'] ?? map['category'] ?? map['Category'];
if ((model?.categories == null || model!.categories!.isEmpty) &&
(map.containsKey('categories') ||
map.containsKey('Categories') ||
map.containsKey('category') ||
map.containsKey('Category'))) {
dynamic catData =
map['categories'] ??
map['Categories'] ??
map['category'] ??
map['Category'];
if (catData is List) {
model?.categories = catData.map((c) => c is Map ? WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(c)) : WalaPlusCategoryModel(name: c.toString())).toList();
model?.categories =
catData
.map(
(c) =>
c is Map
? WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(c),
)
: WalaPlusCategoryModel(name: c.toString()),
)
.toList();
} else if (catData is Map) {
model?.categories = [WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(catData))];
model?.categories = [
WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(catData),
),
];
} else if (catData is String && catData.trim().isNotEmpty) {
model?.categories = [WalaPlusCategoryModel(name: catData.trim())];
model?.categories = [
WalaPlusCategoryModel(name: catData.trim()),
];
}
}
} else if (response is Map<String, dynamic> && response.containsKey('name')) {
} else if (response is Map<String, dynamic> &&
response.containsKey('name')) {
model = WalaPlusRetailerModel.fromJson(response);
}
@ -474,36 +622,90 @@ class OffersAndDiscountsApiClient {
if (response is Map) {
if (model.isFavorite == null) {
if (response.containsKey('isfavorite')) {
model.isFavorite = response['isfavorite'] == true || response['isfavorite'] == 1 || response['isfavorite'] == 'true' || response['isfavorite'] == '1';
model.isFavorite =
response['isfavorite'] == true ||
response['isfavorite'] == 1 ||
response['isfavorite'] == 'true' ||
response['isfavorite'] == '1';
} else if (response.containsKey('is_favorite')) {
model.isFavorite = response['is_favorite'] == true || response['is_favorite'] == 1 || response['is_favorite'] == 'true' || response['is_favorite'] == '1';
model.isFavorite =
response['is_favorite'] == true ||
response['is_favorite'] == 1 ||
response['is_favorite'] == 'true' ||
response['is_favorite'] == '1';
}
}
if (model.website == null || model.website!.isEmpty || model.website == "null") {
String? web = (response['website'] ?? response['merchant_website'] ?? response['website_url'] ?? response['url'] ?? response['merchant_url'])?.toString();
if (model.website == null ||
model.website!.isEmpty ||
model.website == "null") {
String? web =
(response['website'] ??
response['merchant_website'] ??
response['website_url'] ??
response['url'] ??
response['merchant_url'])
?.toString();
if (web != null && web.trim().isNotEmpty && web != "null") {
model.website = web.trim();
}
}
if ((model.offers == null || model.offers!.isEmpty) && (response.containsKey('offers') || response.containsKey('Offers'))) {
model.offers = WalaPlusRetailerModel.parseOffersList(response['offers'] ?? response['Offers']);
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']);
(response.containsKey('locations') ||
response.containsKey('Locations') ||
response.containsKey('location') ||
response.containsKey('Location'))) {
model.locations = WalaPlusRetailerModel.parseLocationsList(
response['locations'] ??
response['Locations'] ??
response['location'] ??
response['Location'],
);
}
if ((model.categories == null || model.categories!.isEmpty) && (response.containsKey('categories') || response.containsKey('Categories') || response.containsKey('category') || response.containsKey('Category'))) {
dynamic catData = response['categories'] ?? response['Categories'] ?? response['category'] ?? response['Category'];
if ((model.categories == null || model.categories!.isEmpty) &&
(response.containsKey('categories') ||
response.containsKey('Categories') ||
response.containsKey('category') ||
response.containsKey('Category'))) {
dynamic catData =
response['categories'] ??
response['Categories'] ??
response['category'] ??
response['Category'];
if (catData is List) {
model.categories = catData.map((c) => c is Map ? WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(c)) : WalaPlusCategoryModel(name: c.toString())).toList();
model.categories =
catData
.map(
(c) =>
c is Map
? WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(c),
)
: WalaPlusCategoryModel(name: c.toString()),
)
.toList();
} else if (catData is Map) {
model.categories = [WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(catData))];
model.categories = [
WalaPlusCategoryModel.fromJson(
Map<String, dynamic>.from(catData),
),
];
} else if (catData is String && catData.trim().isNotEmpty) {
model.categories = [WalaPlusCategoryModel(name: catData.trim())];
model.categories = [
WalaPlusCategoryModel(name: catData.trim()),
];
}
}
}
debugPrint("getWalaPlusRetailerById: parsed retailer '${model.name}', isFavorite: ${model.isFavorite}, offers: ${model.offers?.length ?? 0}, locations: ${model.locations?.length ?? 0}");
debugPrint(
"getWalaPlusRetailerById: parsed retailer '${model.name}', isFavorite: ${model.isFavorite}, offers: ${model.offers?.length ?? 0}, locations: ${model.locations?.length ?? 0}",
);
return model;
}
}
@ -519,9 +721,24 @@ class OffersAndDiscountsApiClient {
}
String _resolveUserReference({String? overrideUserReference}) {
if (overrideUserReference != null && overrideUserReference.trim().isNotEmpty) {
if (overrideUserReference != null &&
overrideUserReference.trim().isNotEmpty) {
return overrideUserReference.trim();
}
// AppState().memberInformationList is never cleared on logout, so after logging out of an
// employee session and logging back in as a dependent (without a full app restart) it can
// still be sitting there from the previous session. Checking the CURRENT session type first
// - rather than just whichever field happens to be non-empty - prevents a dependent session
// from ever resolving to the previous employee's number.
if (ApiConsts.isDependetLogin) {
String? depRef = AppState().getUserName;
if (depRef != null && depRef.trim().isNotEmpty) {
return depRef.trim();
}
return "";
}
String? ref = AppState().memberInformationList?.eMPLOYEENUMBER?.toString();
if (ref != null && ref.trim().isNotEmpty) {
return ref.trim();
@ -533,15 +750,25 @@ class OffersAndDiscountsApiClient {
return "";
}
Future<Map<String, dynamic>?> redeemWalaPlusOffer({required String offerId, String? offerType, String? userReference, BuildContext? context}) async {
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);
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["WP_Offer_Type"] =
(offerType != null && offerType.trim().isNotEmpty)
? offerType.trim()
: "promo";
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
@ -563,7 +790,11 @@ class OffersAndDiscountsApiClient {
(response) {
if (response != null) {
debugPrint("redeemWalaPlusOffer response: $response");
dynamic val = response['WalaPlus_OfferRedeem'] ?? response['walaPlus_OfferRedeem'] ?? response['WalaPlusOfferRedeem'] ?? response['result'];
dynamic val =
response['WalaPlus_OfferRedeem'] ??
response['walaPlus_OfferRedeem'] ??
response['WalaPlusOfferRedeem'] ??
response['result'];
if (val is String) {
try {
val = json.decode(val);
@ -584,11 +815,19 @@ class OffersAndDiscountsApiClient {
);
}
Future<GenericResponseModel?> addWalaPlusFavorite({Object? retailerId, List<String>? retailerIds, BuildContext? context, String? mobileNumber, String? userReference}) async {
Future<GenericResponseModel?> addWalaPlusFavorite({
Object? retailerId,
List<String>? retailerIds,
BuildContext? context,
String? mobileNumber,
String? userReference,
}) async {
String url = ApiConsts.walaPlusFavoritesAdd;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pUserReference = _resolveUserReference(overrideUserReference: userReference);
String pUserReference = _resolveUserReference(
overrideUserReference: userReference,
);
List<String> ids = [];
if (retailerIds != null && retailerIds.isNotEmpty) {
@ -643,11 +882,19 @@ class OffersAndDiscountsApiClient {
);
}
Future<GenericResponseModel?> removeWalaPlusFavorite({Object? retailerId, List<String>? retailerIds, BuildContext? context, String? mobileNumber, String? userReference}) async {
Future<GenericResponseModel?> removeWalaPlusFavorite({
Object? retailerId,
List<String>? retailerIds,
BuildContext? context,
String? mobileNumber,
String? userReference,
}) async {
String url = ApiConsts.walaPlusFavoritesRemove;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pUserReference = _resolveUserReference(overrideUserReference: userReference);
String pUserReference = _resolveUserReference(
overrideUserReference: userReference,
);
List<String> ids = [];
if (retailerIds != null && retailerIds.isNotEmpty) {

File diff suppressed because it is too large Load Diff

@ -2,7 +2,7 @@
// ignore_for_file: constant_identifier_names
abstract class LocaleKeys {
abstract class LocaleKeys {
static const mohemm = 'mohemm';
static const english = 'english';
static const arabic = 'arabic';
@ -14,7 +14,8 @@ abstract class LocaleKeys {
static const userSearch = 'userSearch';
static const password = 'password';
static const welcomeBack = 'welcomeBack';
static const wouldYouLikeToLoginWithCurrentUsername = 'wouldYouLikeToLoginWithCurrentUsername';
static const wouldYouLikeToLoginWithCurrentUsername =
'wouldYouLikeToLoginWithCurrentUsername';
static const lastLoginDetails = 'lastLoginDetails';
static const verificationType = 'verificationType';
static const pleaseVerify = 'pleaseVerify';
@ -24,8 +25,10 @@ abstract class LocaleKeys {
static const verifyThroughSMS = 'verifyThroughSMS';
static const verifyThroughWhatsapp = 'verifyThroughWhatsapp';
static const useAnotherAccount = 'useAnotherAccount';
static const pleaseEnterTheVerificationCodeSentTo = 'pleaseEnterTheVerificationCodeSentTo';
static const theVerificationCodeWillExpireIn = 'theVerificationCodeWillExpireIn';
static const pleaseEnterTheVerificationCodeSentTo =
'pleaseEnterTheVerificationCodeSentTo';
static const theVerificationCodeWillExpireIn =
'theVerificationCodeWillExpireIn';
static const goodMorning = 'goodMorning';
static const markAttendance = 'markAttendance';
static const timeLeftToday = 'timeLeftToday';
@ -49,7 +52,8 @@ abstract class LocaleKeys {
static const newString = 'newString';
static const setTheNewPassword = 'setTheNewPassword';
static const typeYourNewPasswordBelow = 'typeYourNewPasswordBelow';
static const typeYourNewActiveDirectoryPasswordBelow = 'typeYourNewActiveDirectoryPasswordBelow';
static const typeYourNewActiveDirectoryPasswordBelow =
'typeYourNewActiveDirectoryPasswordBelow';
static const confirmPassword = 'confirmPassword';
static const update = 'update';
static const title = 'title';
@ -58,7 +62,8 @@ abstract class LocaleKeys {
static const createRequest = 'createRequest';
static const forgotPassword = 'forgotPassword';
static const employeeId = 'employeeId';
static const loginCodeWillSentToMobileNumber = 'loginCodeWillSentToMobileNumber';
static const loginCodeWillSentToMobileNumber =
'loginCodeWillSentToMobileNumber';
static const changePassword = 'changePassword';
static const ok = 'ok';
static const confirm = 'confirm';
@ -94,7 +99,8 @@ abstract class LocaleKeys {
static const atLeastOneNumeric = 'atLeastOneNumeric';
static const minimum8Characters = 'minimum8Characters';
static const doNotAddRepeatingLetters = 'doNotAddRepeatingLetters';
static const itShouldContainSpecialCharacter = 'itShouldContainSpecialCharacter';
static const itShouldContainSpecialCharacter =
'itShouldContainSpecialCharacter';
static const confirmPasswordMustMatch = 'confirmPasswordMustMatch';
static const sms = 'sms';
static const fingerPrint = 'fingerPrint';
@ -137,7 +143,8 @@ abstract class LocaleKeys {
static const category = 'category';
static const employeeEmailAddress = 'employeeEmailAddress';
static const payrollBranch = 'payrollBranch';
static const yourChangeHasBeenSavedSuccessfully = 'yourChangeHasBeenSavedSuccessfully';
static const yourChangeHasBeenSavedSuccessfully =
'yourChangeHasBeenSavedSuccessfully';
static const code = 'code';
static const unit = 'unit';
static const quantity = 'quantity';
@ -232,7 +239,8 @@ abstract class LocaleKeys {
static const templateName = 'templateName';
static const itemCreationStatus = 'itemCreationStatus';
static const standardizationApprovalStatus = 'standardizationApprovalStatus';
static const standardizationApprovalRejectionReason = 'standardizationApprovalRejectionReason';
static const standardizationApprovalRejectionReason =
'standardizationApprovalRejectionReason';
static const analyzedBy = 'analyzedBy';
static const approvedDate = 'approvedDate';
static const itemType = 'itemType';
@ -258,7 +266,7 @@ abstract class LocaleKeys {
static const Payroll = 'Payroll';
static const civilIdentityNumber = 'civilIdentityNumber';
static const dateOfBirth = 'dateOfBirth';
static const maritalStatus = 'maritalStatus ';
static const maritalStatus = 'maritalStatus ';
static const fullName = 'fullName';
static const remove = 'remove';
static const submit = 'submit';
@ -267,7 +275,8 @@ abstract class LocaleKeys {
static const comments = 'comments';
static const writeComment = 'writeComment';
static const approversList = 'approversList';
static const yourRequestHasBeenSubmittedForApprovals = 'yourRequestHasBeenSubmittedForApprovals';
static const yourRequestHasBeenSubmittedForApprovals =
'yourRequestHasBeenSubmittedForApprovals';
static const monthlyPaySlip = 'monthlyPaySlip';
static const particular = 'particular';
static const earnings = 'earnings';
@ -289,7 +298,7 @@ abstract class LocaleKeys {
static const enterNewInfo = 'enterNewInfo';
static const endDate = 'endDate';
static const removeThisMember = 'removeThisMember';
static const wantUpdateThisMember = 'wantUpdateThisMember ';
static const wantUpdateThisMember = 'wantUpdateThisMember ';
static const addNewFamilyMember = 'addNewFamilyMember';
static const addRow = 'addRow';
static const pleaseSelect = 'pleaseSelect';
@ -336,7 +345,8 @@ abstract class LocaleKeys {
static const mark = 'mark';
static const selectMethodOfAttendance = 'selectMethodOfAttendance';
static const comeNearHMGWifi = 'comeNearHMGWifi';
static const deliverNotificationToMeRegardless = 'deliverNotificationToMeRegardless';
static const deliverNotificationToMeRegardless =
'deliverNotificationToMeRegardless';
static const close = 'close';
static const respond = 'respond';
static const vacationRuleAdded = 'vacationRuleAdded';
@ -355,8 +365,10 @@ abstract class LocaleKeys {
static const searchEmployeeForReplacement = 'searchEmployeeForReplacement';
static const searchForEmployee = 'searchForEmployee';
static const pleaseSpecifyEndTime = 'pleaseSpecifyEndTime';
static const pleaseSelectNotificationReassign = 'pleaseSelectNotificationReassign';
static const pleaseSelectEmployeeForReplacement = 'pleaseSelectEmployeeForReplacement';
static const pleaseSelectNotificationReassign =
'pleaseSelectNotificationReassign';
static const pleaseSelectEmployeeForReplacement =
'pleaseSelectEmployeeForReplacement';
static const pleaseSelectAction = 'pleaseSelectAction';
static const pleaseSelectDate = 'pleaseSelectDate';
static const todayAttendance = 'todayAttendance';
@ -441,8 +453,10 @@ abstract class LocaleKeys {
static const CorrectAddress = 'CorrectAddress';
static const SelectChangeWantToMake = 'SelectChangeWantToMake';
static const profile_reset_password_label = 'profile.reset_password.label';
static const profile_reset_password_username = 'profile.reset_password.username';
static const profile_reset_password_password = 'profile.reset_password.password';
static const profile_reset_password_username =
'profile.reset_password.username';
static const profile_reset_password_password =
'profile.reset_password.password';
static const profile_reset_password = 'profile.reset_password';
static const profile_profileCompletionPer = 'profile.profileCompletionPer';
static const profile_completeProfile = 'profile.completeProfile';
@ -537,7 +551,8 @@ abstract class LocaleKeys {
static const missingDocuments = 'missingDocuments';
static const uploadedDocuments = 'uploadedDocuments';
static const addAtLeastOneAttachment = 'addAtLeastOneAttachment';
static const pleaseClickButtonToJoinMarathon = 'pleaseClickButtonToJoinMarathon';
static const pleaseClickButtonToJoinMarathon =
'pleaseClickButtonToJoinMarathon';
static const youCannotJoinTheMarathon = 'youCannotJoinTheMarathon';
static const open = 'open';
static const paymentRequest = 'paymentRequest';
@ -595,8 +610,10 @@ abstract class LocaleKeys {
static const yes = 'yes';
static const no = 'no';
static const mazayaInviteInstructions = 'mazayaInviteInstructions';
static const cameraPermissionRequiredToScan = 'cameraPermissionRequiredToScan';
static const cameraPermissionPermanentlyDenied = 'cameraPermissionPermanentlyDenied';
static const cameraPermissionRequiredToScan =
'cameraPermissionRequiredToScan';
static const cameraPermissionPermanentlyDenied =
'cameraPermissionPermanentlyDenied';
static const openSettings = 'openSettings';
static const nationalIdOrIqamaFallback = 'nationalIdOrIqamaFallback';
static const Attendance = 'Attendance';
@ -761,7 +778,8 @@ abstract class LocaleKeys {
static const religionIslam = 'religionIslam';
static const back = 'back';
static const submitRequest = 'submitRequest';
static const enterDependentDetailsToContinue = 'enterDependentDetailsToContinue';
static const enterDependentDetailsToContinue =
'enterDependentDetailsToContinue';
static const enterBasicDetailsDesc = 'enterBasicDetailsDesc';
static const locations = 'locations';
static const availableOffers = 'availableOffers';
@ -783,14 +801,18 @@ abstract class LocaleKeys {
static const startDependentFlowsDesc = 'startDependentFlowsDesc';
static const mazayaAccess = 'mazayaAccess';
static const accessOff = 'accessOff';
static const allowDependentMazayaOffersDesc = 'allowDependentMazayaOffersDesc';
static const allowDependentMazayaOffersDesc =
'allowDependentMazayaOffersDesc';
static const loginMethod = 'loginMethod';
static const nationalIdAndDob = 'nationalIdAndDob';
static const sponsorOtp = 'sponsorOtp';
static const revokingPreventsAccessDesc = 'revokingPreventsAccessDesc';
static const locationPermissionRequiredForAttendance = 'locationPermissionRequiredForAttendance';
static const locationServicesRequiredForAttendance = 'locationServicesRequiredForAttendance';
static const pleaseProvideLocationPermission = 'pleaseProvideLocationPermission';
static const locationPermissionRequiredForAttendance =
'locationPermissionRequiredForAttendance';
static const locationServicesRequiredForAttendance =
'locationServicesRequiredForAttendance';
static const pleaseProvideLocationPermission =
'pleaseProvideLocationPermission';
static const microphoneAccessRequired = 'microphoneAccessRequired';
static const shareAccessGuide = 'shareAccessGuide';
static const nationalIdOrIqamaNumber = 'nationalIdOrIqamaNumber';
@ -816,21 +838,28 @@ abstract class LocaleKeys {
static const favoriteRemovedSuccess = 'favoriteRemovedSuccess';
static const favoriteError = 'favoriteError';
static const merchantWebsiteNotAvailable = 'merchantWebsiteNotAvailable';
static const locationServicesRequiredForNearestOffers = 'locationServicesRequiredForNearestOffers';
static const locationPermissionRequiredForNearestOffers = 'locationPermissionRequiredForNearestOffers';
static const locationServicesRequiredForNearestOffers =
'locationServicesRequiredForNearestOffers';
static const locationPermissionRequiredForNearestOffers =
'locationPermissionRequiredForNearestOffers';
static const mazayaDeals = 'mazayaDeals';
static const helloName = 'helloName';
static const findABrandOrSomething = 'findABrandOrSomething';
static const exploreYourBenefits = 'exploreYourBenefits';
static const theMazayaEdit = 'theMazayaEdit';
static const exclusivelyYours = 'exclusivelyYours';
static const aLittleExtraJustForMazayaMembers = 'aLittleExtraJustForMazayaMembers';
static const aLittleExtraJustForMazayaMembers =
'aLittleExtraJustForMazayaMembers';
static const seeAll = 'seeAll';
static const latestOnMazaya = 'latestOnMazaya';
static const goodFoodGreatCompanyEvenBetterPerks = 'goodFoodGreatCompanyEvenBetterPerks';
static const goodFoodGreatCompanyEvenBetterPerks =
'goodFoodGreatCompanyEvenBetterPerks';
static const travelEssentials = 'travelEssentials';
static const fromEverydayCareToYourNextAdventure = 'fromEverydayCareToYourNextAdventure';
static const fromEverydayCareToYourNextAdventure =
'fromEverydayCareToYourNextAdventure';
static const homeAndKitchen = 'homeAndKitchen';
static const curatedPicksForYourHomeAndKitchen = 'curatedPicksForYourHomeAndKitchen';
static const curatedPicksForYourHomeAndKitchen =
'curatedPicksForYourHomeAndKitchen';
static const discover = 'discover';
static const saved = 'saved';
static const myFamily = 'myFamily';
@ -859,5 +888,4 @@ abstract class LocaleKeys {
static const mazayaExclusive = 'mazayaExclusive';
static const youAreAllCaughtUp = 'youAreAllCaughtUp';
static const selectCity = 'selectCity';
}

@ -237,6 +237,19 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
}
}
// AppState().dependentName sometimes comes back with stray " - " separators (e.g.
// "John - Smith") - collapse those into plain spaces for display.
String? get _dependentGreetingName {
String? raw = AppState().dependentName;
if (raw == null || raw.trim().isEmpty) return null;
String cleaned =
raw
.replaceAll(RegExp(r'\s*-\s*'), ' ')
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
return cleaned.isEmpty ? null : cleaned;
}
void _openRetailer(WalaPlusRetailerModel item) {
Navigator.pushNamed(
context,
@ -264,6 +277,23 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
);
}
// Tapping the search field opens the same "all categories / newest" data as "Latest on
// Mazaya"'s See all, but with the category hero hidden and the search bar auto-focused with
// the keyboard open (see RetailersListScreen's isSearchMode) instead of landing on Travel.
void _openSearchGrid() {
Navigator.pushNamed(
context,
AppRoutes.retailersListScreen,
arguments: <String, dynamic>{
'category': WalaPlusCategoryModel(
id: 'all',
name: LocaleKeys.allCategories.tr(),
),
'searchMode': true,
},
);
}
Future<void> _onRefresh() async {
await Future.wait<void>(<Future<void>>[
_fetchCategories(),
@ -366,11 +396,13 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
context,
title: LocaleKeys.mazayaDeals.tr(),
showHomeButton: false,
// Discover is the dependent-login landing page - no back button there (logout is still
// reachable via the explicit logout action below). Employees reaching Discover from the
// regular dashboard can still pop back normally.
showBackButton: !ApiConsts.isDependetLogin,
onBackTapped: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
} else if (ApiConsts.isDependetLogin) {
_performLogout();
}
},
actions:
@ -407,6 +439,13 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (ApiConsts.isDependetLogin && _dependentGreetingName != null)
Padding(
padding: const EdgeInsets.fromLTRB(22, 16, 22, 0),
child: LocaleKeys.helloName
.tr(args: [_dependentGreetingName!])
.toText20(isBold: true, color: const Color(0xFF202F31)),
),
Padding(
padding: const EdgeInsets.fromLTRB(22, 16, 22, 0),
child: _buildSearchField(),
@ -506,7 +545,7 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
),
),
style: const TextStyle(fontSize: 12, color: Color(0xFF202F32)),
onTap: _openRetailersGrid,
onTap: _openSearchGrid,
readOnly: true,
),
),
@ -669,6 +708,7 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
}
Widget _buildExclusiveOffersSection() {
bool isAr = AppState().isArabic(context);
return Container(
width: double.infinity,
color: const Color(0xFF143F36),
@ -705,13 +745,16 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
weight: FontWeight.w500,
),
const SizedBox(width: 4),
SvgPicture.asset(
"assets/icons/mazaya_arrow_right.svg",
width: 15,
height: 15,
colorFilter: const ColorFilter.mode(
Color(0xFFF1EBDD),
BlendMode.srcIn,
RotatedBox(
quarterTurns: isAr ? 2 : 0,
child: SvgPicture.asset(
"assets/icons/mazaya_arrow_right.svg",
width: 15,
height: 15,
colorFilter: const ColorFilter.mode(
Color(0xFFF1EBDD),
BlendMode.srcIn,
),
),
),
],
@ -868,6 +911,7 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
required VoidCallback onSeeAll,
}) {
if (!isLoading && retailers.isEmpty) return const SizedBox.shrink();
bool isAr = AppState().isArabic(context);
return Container(
width: double.infinity,
color: backgroundColor,
@ -897,13 +941,16 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
weight: FontWeight.w500,
),
const SizedBox(width: 4),
SvgPicture.asset(
"assets/icons/mazaya_arrow_right.svg",
width: 15,
height: 15,
colorFilter: const ColorFilter.mode(
Color(0xFF147D6B),
BlendMode.srcIn,
RotatedBox(
quarterTurns: isAr ? 2 : 0,
child: SvgPicture.asset(
"assets/icons/mazaya_arrow_right.svg",
width: 15,
height: 15,
colorFilter: const ColorFilter.mode(
Color(0xFF147D6B),
BlendMode.srcIn,
),
),
),
],

@ -52,6 +52,13 @@ class _RetailersListScreenState extends State<RetailersListScreen> {
bool isExclusiveMode = false;
List<WalaPlusRetailerModel> _exclusiveOffers = <WalaPlusRetailerModel>[];
// Reached by tapping Discover's search field - same "all categories / newest" data as the
// "Latest on Mazaya" See all, but the category hero is hidden (there's no specific category
// context, it's a pure search entry point) and the search bar auto-focuses with the keyboard
// open instead.
bool isSearchMode = false;
final FocusNode _searchFocusNode = FocusNode();
// Subcategories of whichever main category is currently selected - fetched via
// getWalaPlusCategories(parentId: ...) whenever a main category card is tapped. Empty/cleared
// when "All Categories" (or nothing) is selected, since there's no specific parent to ask for.
@ -112,6 +119,12 @@ class _RetailersListScreenState extends State<RetailersListScreen> {
selectedCategory = args['category'] as WalaPlusCategoryModel;
currentCategoryId = selectedCategory?.id ?? "";
}
if (args is Map && args['searchMode'] == true) {
isSearchMode = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _searchFocusNode.requestFocus();
});
}
}
}
@ -193,6 +206,7 @@ class _RetailersListScreenState extends State<RetailersListScreen> {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
@ -994,8 +1008,10 @@ class _RetailersListScreenState extends State<RetailersListScreen> {
// 1. Sub-header: "Explore your benefits" | "📍 Riyadh"
_buildSubHeaderRow(isAr),
// 2. Category Hero Section: Title, Subtitle, Category image
_buildCategoryHero(isAr),
// 2. Category Hero Section: Title, Subtitle, Category image - hidden when
// reached from Discover's search field, since there's no specific category
// context there and the search bar should be the first thing on screen.
if (!isSearchMode) _buildCategoryHero(isAr),
// 3. Search Bar: "Find an escape or an experience"
_buildSearchBar(isAr),
@ -1239,6 +1255,7 @@ class _RetailersListScreenState extends State<RetailersListScreen> {
Expanded(
child: TextField(
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: _onSearchChanged,
onSubmitted: (String val) {
_debounceTimer?.cancel();

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
@ -257,6 +258,9 @@ class _SavedRetailersScreenState extends State<SavedRetailersScreen> {
context,
title: LocaleKeys.favoriteOffers.tr(),
showHomeButton: false,
// Reached from the Mazaya bottom nav - same as Discover, no back button for a
// dependent-login session since this is one of their root tabs, not a pushed screen.
showBackButton: !ApiConsts.isDependetLogin,
),
bottomNavigationBar: const MazayaBottomNav(currentTab: MazayaTab.saved),
body: Column(

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/classes/consts.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/ui/mazaya/mazaya_discover_screen.dart';
@ -34,21 +35,33 @@ class MazayaBottomNav extends StatelessWidget {
break;
case MazayaTab.myFamily:
targetScreen = const FamilyMembers();
settings = const RouteSettings(arguments: <String, dynamic>{'fromMazaya': true});
settings = const RouteSettings(
arguments: <String, dynamic>{'fromMazaya': true},
);
break;
}
Navigator.pushReplacement(
context,
PageRouteBuilder<void>(
settings: settings,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => targetScreen,
pageBuilder:
(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) => targetScreen,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
);
}
Widget _buildItem(BuildContext context, {required MazayaTab tab, required String svgAsset, required String label}) {
Widget _buildItem(
BuildContext context, {
required MazayaTab tab,
required String svgAsset,
required String label,
}) {
bool isActive = tab == currentTab;
return Expanded(
child: InkWell(
@ -56,9 +69,20 @@ class MazayaBottomNav extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SvgPicture.asset(svgAsset, width: 22, height: 22, colorFilter: ColorFilter.mode(isActive ? _activeColor : _inactiveColor, BlendMode.srcIn)),
SvgPicture.asset(
svgAsset,
width: 22,
height: 22,
colorFilter: ColorFilter.mode(
isActive ? _activeColor : _inactiveColor,
BlendMode.srcIn,
),
),
const SizedBox(height: 5),
label.toText11(weight: isActive ? FontWeight.w600 : FontWeight.w400, color: isActive ? _activeColor : _inactiveColor),
label.toText11(
weight: isActive ? FontWeight.w600 : FontWeight.w400,
color: isActive ? _activeColor : _inactiveColor,
),
],
),
),
@ -70,13 +94,34 @@ class MazayaBottomNav extends StatelessWidget {
return SafeArea(
top: false,
child: Container(
decoration: const BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFE8EAE4)))),
decoration: const BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Color(0xFFE8EAE4))),
),
padding: const EdgeInsets.only(top: 13, bottom: 9),
child: Row(
children: <Widget>[
_buildItem(context, tab: MazayaTab.discover, svgAsset: "assets/icons/mazaya_compass.svg", label: LocaleKeys.discover.tr()),
_buildItem(context, tab: MazayaTab.saved, svgAsset: "assets/icons/mazaya_heart_outline.svg", label: LocaleKeys.saved.tr()),
_buildItem(context, tab: MazayaTab.myFamily, svgAsset: "assets/icons/mazaya_user_round.svg", label: LocaleKeys.myFamily.tr()),
_buildItem(
context,
tab: MazayaTab.discover,
svgAsset: "assets/icons/mazaya_compass.svg",
label: LocaleKeys.discover.tr(),
),
_buildItem(
context,
tab: MazayaTab.saved,
svgAsset: "assets/icons/mazaya_heart_outline.svg",
label: LocaleKeys.saved.tr(),
),
// My Family lets the account holder manage their dependents' Mazaya access - not
// applicable (and not shown) when a dependent is the one logged in.
if (!ApiConsts.isDependetLogin)
_buildItem(
context,
tab: MazayaTab.myFamily,
svgAsset: "assets/icons/mazaya_user_round.svg",
label: LocaleKeys.myFamily.tr(),
),
],
),
),

@ -1,9 +1,7 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.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';
@ -81,7 +79,8 @@ class MazayaAccessResultScreen extends StatelessWidget {
final String? dateOfBirth;
final String? message;
const MazayaAccessResultScreen({Key? key, this.isGranted = true, this.dependentName = "", this.dependentInitials, this.downloadUrl, this.nationalId, this.dateOfBirth, this.message}) : super(key: key);
const MazayaAccessResultScreen({Key? key, this.isGranted = true, this.dependentName = "", this.dependentInitials, this.downloadUrl, this.nationalId, this.dateOfBirth, this.message})
: super(key: key);
static String getDownloadLink() {
int channel = AppState().postParamsObject?.channel ?? 31;
@ -187,7 +186,9 @@ class MazayaAccessResultScreen extends StatelessWidget {
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, effectiveMessage),
effectiveGranted
? _buildGrantedContent(context, isAr, fontFamily, effectiveName, initials, linkToShare)
: _buildRevokedContent(context, isAr, fontFamily, effectiveName, initials, effectiveMessage),
),
),
_buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare, effectiveNationalId ?? "", effectiveDateOfBirth ?? ""),
@ -497,6 +498,8 @@ class MazayaAccessResultScreen extends StatelessWidget {
} catch (_) {}
}
Widget _buildBottomButtons(BuildContext context, bool isAr, String fontFamily, bool granted, String linkToShare, String nationalId, String dateOfBirth) {
if (granted) {
return Container(

@ -11,6 +11,7 @@ AppBar AppBarWidget(
BuildContext context, {
required String title,
bool showHomeButton = true,
bool showBackButton = true,
bool showLogo = false,
String? logoPath,
bool showWorkListSettingButton = false,
@ -25,18 +26,19 @@ AppBar AppBarWidget(
surfaceTintColor: Colors.transparent,
title: Row(
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Feedback.wrapForTap(() {
(onBackTapped == null
? Navigator.maybePop(context)
: onBackTapped());
}, context),
child: const Icon(
Icons.arrow_back_ios,
color: MyColors.darkIconColor,
if (showBackButton)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Feedback.wrapForTap(() {
(onBackTapped == null
? Navigator.maybePop(context)
: onBackTapped());
}, context),
child: const Icon(
Icons.arrow_back_ios,
color: MyColors.darkIconColor,
),
),
),
if (showLogo) 4.width,
if (showLogo) SvgPicture.asset(logoPath!),
4.width,

Loading…
Cancel
Save