Fav Tab Changes and SearchBar Fix

wala_plus_with_dialog_removal
FaizHashmi 1 week ago
parent d75f9a9cc3
commit 6ba06065b3

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