design changed

master
Sultan khan 2 weeks ago
parent 99c2d658e1
commit fe36263ac0

@ -830,5 +830,25 @@
"locationServicesRequiredForNearestOffers": "فعّل خدمات الموقع لعرض العروض القريبة منك",
"locationPermissionRequiredForNearestOffers": "اسمح للتطبيق بالوصول إلى موقعك لعرض العروض القريبة منك",
"favoriteMerchant": "العروض المفضلة",
"favoriteOffers": "العروض المفضلة"
"favoriteOffers": "العروض المفضلة",
"goodToKnow": "معلومات مهمة",
"goodToKnowDesc": "احتفظ بعضوية مزايا في متناول يدك. تحقق من توفر التاجر والأنشطة المؤهلة والشروط الكاملة قبل الحجز.",
"termsAndConditions": "الشروط والأحكام",
"haveAQuestionAboutOffer": "هل لديك سؤال حول هذا العرض؟",
"getHelp": "الحصول على المساعدة",
"redeemOffer": "استرداد العرض",
"merchantContact": "بيانات التواصل مع التاجر",
"website": "الموقع الإلكتروني",
"phone": "الهاتف",
"viewAll": "عرض الكل",
"moreCitiesCount": "+ {} مدن أخرى",
"mazayaDeals": "عروض مزايا",
"exploreYourBenefits": "استكشف مزاياك",
"findAnEscapeOrAnExperience": "ابحث عن تجربة أو عطلة",
"allOffers": "جميع العروض",
"sampleAvailability": "حسب التوفر",
"offersCount": "{} عروض",
"mazayaExclusive": "حصري لمزايا",
"youAreAllCaughtUp": "لقد شاهدت كل العروض. المزيد قريباً.",
"selectCity": "اختر المدينة"
}

@ -834,6 +834,26 @@
"favoriteRemovedSuccess": "Removed from favorites successfully",
"favoriteError": "An error occurred while updating favorites",
"favoriteMerchant": "Favorite Offers",
"favoriteOffers": "Favorite Offers"
"favoriteOffers": "Favorite Offers",
"goodToKnow": "Good to know",
"goodToKnowDesc": "Keep your Mazaya membership handy. Check the merchant's availability, eligible activities and full terms before you book.",
"termsAndConditions": "Terms & conditions",
"haveAQuestionAboutOffer": "Have a question about this offer?",
"getHelp": "Get help",
"redeemOffer": "Redeem offer",
"merchantContact": "Merchant contact",
"website": "Website",
"phone": "Phone",
"viewAll": "View all",
"moreCitiesCount": "+ {} more cities",
"mazayaDeals": "Mazaya Deals",
"exploreYourBenefits": "Explore your benefits",
"findAnEscapeOrAnExperience": "Find an escape or an experience",
"allOffers": "All offers",
"sampleAvailability": "sample availability",
"offersCount": "{} offers",
"mazayaExclusive": "MAZAYA EXCLUSIVE",
"youAreAllCaughtUp": "You're all caught up. More little extras soon.",
"selectCity": "Select City"
}

@ -16,6 +16,7 @@ import 'package:mohem_flutter_app/widgets/location/Location.dart' as location;
class OffersAndDiscountsApiClient {
static final OffersAndDiscountsApiClient _instance = OffersAndDiscountsApiClient._internal();
static List<WalaPlusCategoryModel>? cachedCategories;
OffersAndDiscountsApiClient._internal();
@ -204,7 +205,7 @@ class OffersAndDiscountsApiClient {
}
_cleanWalaPlusParams(postParams);
return await ApiClient().postJsonForObject(
List<WalaPlusCategoryModel> list = await ApiClient().postJsonForObject(
(response) {
List<WalaPlusCategoryModel> categories = [];
if (response != null) {
@ -231,6 +232,10 @@ class OffersAndDiscountsApiClient {
url,
postParams,
);
if ((parentId == null || parentId.trim().isEmpty) && list.isNotEmpty) {
cachedCategories = list;
}
return list;
}
// Confirmed with backend team: WP_Offer_Type accepts "online" and "store".
@ -446,6 +451,16 @@ class OffersAndDiscountsApiClient {
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 (catData is List) {
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))];
} else if (catData is String && catData.trim().isNotEmpty) {
model?.categories = [WalaPlusCategoryModel(name: catData.trim())];
}
}
} else if (response is Map<String, dynamic> && response.containsKey('name')) {
model = WalaPlusRetailerModel.fromJson(response);
}
@ -472,6 +487,16 @@ class OffersAndDiscountsApiClient {
(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 (catData is List) {
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))];
} else if (catData is String && catData.trim().isNotEmpty) {
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}");
return model;

@ -28,11 +28,11 @@ class WalaPlusCategoryModel {
WalaPlusCategoryModel.fromJson(Map<String, dynamic> json) {
id = (json['id'] ?? json['Id'] ?? json['ID'])?.toString();
name = (json['name'] ?? json['Name'])?.toString();
name = (json['name'] ?? json['Name'] ?? json['category_name'] ?? json['categoryName'] ?? json['cat_name'] ?? json['CategoryName'])?.toString();
parentId = (json['parent_id'] ?? json['Parent_Id'] ?? json['parentId'])?.toString();
imageThumb = (json['image_thumb'] ?? json['Image_Thumb'] ?? json['imageThumb'])?.toString();
countryNameAr = (json['country_name_ar'] ?? json['countryNameAr'] ?? json['category_name_ar'])?.toString();
countryNameEn = (json['country_name_en'] ?? json['countryNameEn'] ?? json['category_name_en'])?.toString();
countryNameAr = (json['country_name_ar'] ?? json['countryNameAr'] ?? json['category_name_ar'] ?? json['categoryNameAr'] ?? json['cat_name_ar'] ?? json['name_ar'])?.toString();
countryNameEn = (json['country_name_en'] ?? json['countryNameEn'] ?? json['category_name_en'] ?? json['categoryNameEn'] ?? json['cat_name_en'] ?? json['name_en'])?.toString();
// Use category image URL from HMG middleware using ID from WalaPlus_Categories API:
// https://epharmacy.hmg.com/middleware/images/category/{id}.png

@ -68,6 +68,23 @@ class WalaPlusRetailersByIDResponse {
factory WalaPlusRetailersByIDResponse.fromJson(Map<String, dynamic> json) {
List<WalaPlusRetailerLocation> locList = WalaPlusRetailerModel.parseLocationsList(json['locations'] ?? json['Locations'] ?? json['location'] ?? json['Location']);
List<WalaPlusOfferModel> offList = WalaPlusRetailerModel.parseOffersList(json['offers'] ?? json['Offers']);
List<WalaPlusCategoryModel> catList = [];
dynamic catData = json['categories'] ?? json['Categories'] ?? json['category'] ?? json['Category'];
if (catData is List) {
for (var c in catData) {
if (c is Map<String, dynamic>) {
catList.add(WalaPlusCategoryModel.fromJson(c));
} else if (c is Map) {
catList.add(WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(c)));
} else if (c is String && c.trim().isNotEmpty) {
catList.add(WalaPlusCategoryModel(name: c.trim()));
}
}
} else if (catData is Map) {
catList.add(WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(catData)));
} else if (catData is String && catData.trim().isNotEmpty) {
catList.add(WalaPlusCategoryModel(name: catData.trim()));
}
List<WalaPlusRetailerModel> retList = [];
dynamic retData = json['retailer'] ?? json['Retailer'];
@ -77,6 +94,7 @@ class WalaPlusRetailersByIDResponse {
var model = WalaPlusRetailerModel.fromJson(r);
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
if ((model.categories == null || model.categories!.isEmpty) && catList.isNotEmpty) model.categories = catList;
if ((model.website == null || model.website!.isEmpty || model.website == "null") && (json['website'] != null || json['merchant_website'] != null || json['url'] != null)) {
model.website = (json['website'] ?? json['merchant_website'] ?? json['url'])?.toString();
}
@ -89,6 +107,7 @@ class WalaPlusRetailersByIDResponse {
var model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(r));
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
if ((model.categories == null || model.categories!.isEmpty) && catList.isNotEmpty) model.categories = catList;
if ((model.website == null || model.website!.isEmpty || model.website == "null") && (json['website'] != null || json['merchant_website'] != null || json['url'] != null)) {
model.website = (json['website'] ?? json['merchant_website'] ?? json['url'])?.toString();
}
@ -103,6 +122,7 @@ class WalaPlusRetailersByIDResponse {
var model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(retData));
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
if ((model.categories == null || model.categories!.isEmpty) && catList.isNotEmpty) model.categories = catList;
if ((model.website == null || model.website!.isEmpty || model.website == "null") && (json['website'] != null || json['merchant_website'] != null || json['url'] != null)) {
model.website = (json['website'] ?? json['merchant_website'] ?? json['url'])?.toString();
}
@ -174,6 +194,8 @@ class WalaPlusRetailerModel {
List<WalaPlusOfferModel>? offers;
List<WalaPlusRetailerLocation>? locations;
List<WalaPlusCategoryModel>? categories;
String? categoryId;
String? categoryName;
String? validity;
String? validTo;
String? validUntil;
@ -213,6 +235,8 @@ class WalaPlusRetailerModel {
this.offers,
this.locations,
this.categories,
this.categoryId,
this.categoryName,
this.validity,
this.validTo,
this.validUntil,
@ -297,10 +321,79 @@ class WalaPlusRetailerModel {
List<WalaPlusRetailerLocation> locationsList = parseLocationsList(locationsSource ?? targetJson['locations'] ?? targetJson['Locations'] ?? targetJson['location'] ?? targetJson['Location']);
List<WalaPlusCategoryModel> categoriesList = [];
if (targetJson['categories'] != null && targetJson['categories'] is List) {
targetJson['categories'].forEach((v) {
categoriesList.add(WalaPlusCategoryModel.fromJson(v));
});
dynamic rawCats = targetJson['categories'] ?? targetJson['Categories'] ?? targetJson['category'] ?? targetJson['Category'];
if (rawCats != null && rawCats is List) {
for (var v in rawCats) {
if (v is Map<String, dynamic>) {
categoriesList.add(WalaPlusCategoryModel.fromJson(v));
} else if (v is Map) {
categoriesList.add(WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(v)));
} else if (v is String && v.trim().isNotEmpty) {
categoriesList.add(WalaPlusCategoryModel(name: v.trim()));
}
}
} else if (rawCats is Map) {
categoriesList.add(WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(rawCats)));
} else if (rawCats is String && rawCats.toString().trim().isNotEmpty && rawCats.toString().trim() != "null") {
categoriesList.add(WalaPlusCategoryModel(name: rawCats.toString().trim()));
}
String? singleCatName = (targetJson['category_name'] ??
targetJson['categoryName'] ??
targetJson['cat_name'] ??
targetJson['catName'] ??
targetJson['CategoryName'] ??
targetJson['Category_Name'] ??
targetJson['category_title'] ??
targetJson['categoryTitle'])?.toString().trim();
String? singleCatAr = (targetJson['category_name_ar'] ??
targetJson['categoryNameAr'] ??
targetJson['cat_name_ar'] ??
targetJson['catNameAr'] ??
targetJson['category_ar'] ??
targetJson['country_name_ar'] ??
targetJson['countryNameAr'])?.toString().trim();
String? singleCatEn = (targetJson['category_name_en'] ??
targetJson['categoryNameEn'] ??
targetJson['cat_name_en'] ??
targetJson['catNameEn'] ??
targetJson['category_en'] ??
targetJson['country_name_en'] ??
targetJson['countryNameEn'])?.toString().trim();
String? foundCategoryId = (targetJson['category_id'] ??
targetJson['categoryId'] ??
targetJson['cat_id'] ??
targetJson['catId'] ??
targetJson['WP_CategoryID'] ??
targetJson['WP_CategoryId'])?.toString().trim();
String? rawType = targetJson['type']?.toString().trim();
String? lowerType = rawType?.toLowerCase();
bool isTypeStoreKeyword = lowerType == null ||
lowerType.isEmpty ||
lowerType == 'online' ||
lowerType == 'instore' ||
lowerType == 'in_store' ||
lowerType == 'in store' ||
lowerType == 'store' ||
lowerType == 'online,instore' ||
lowerType == 'instore,online';
if ((singleCatName == null || singleCatName.isEmpty) && !isTypeStoreKeyword && rawType != null && rawType.isNotEmpty) {
singleCatName = rawType;
}
if (categoriesList.isEmpty) {
if ((singleCatName != null && singleCatName.isNotEmpty && singleCatName != "null") ||
(singleCatAr != null && singleCatAr.isNotEmpty && singleCatAr != "null") ||
(singleCatEn != null && singleCatEn.isNotEmpty && singleCatEn != "null")) {
categoriesList.add(WalaPlusCategoryModel(
id: foundCategoryId,
name: singleCatName ?? singleCatEn ?? singleCatAr,
countryNameAr: singleCatAr ?? singleCatName,
countryNameEn: singleCatEn ?? singleCatName,
));
}
}
WalaPlusRetailerContact? contactObj;
@ -424,6 +517,8 @@ class WalaPlusRetailerModel {
offers: offersList,
locations: locationsList,
categories: categoriesList,
categoryId: foundCategoryId,
categoryName: singleCatName ?? singleCatEn ?? singleCatAr,
validity: foundValidity,
validTo: targetJson['valid_to']?.toString() ?? targetJson['validTo']?.toString(),
validUntil: targetJson['valid_until']?.toString() ?? targetJson['validUntil']?.toString(),
@ -528,9 +623,50 @@ class WalaPlusRetailerModel {
if (categories != null) {
data['categories'] = categories!.map((v) => v.toJson()).toList();
}
data['category_id'] = categoryId;
data['category_name'] = categoryName;
return data;
}
String? getDisplayCategory({bool isAr = false, List<WalaPlusCategoryModel>? fallbackCategories}) {
if (categories != null && categories!.isNotEmpty) {
for (var cat in categories!) {
String? primary = isAr ? cat.countryNameAr : cat.countryNameEn;
String? secondary = isAr ? (cat.name ?? cat.countryNameEn) : (cat.name ?? cat.countryNameAr);
String? resolved = (primary != null && primary.trim().isNotEmpty && primary != "null")
? primary.trim()
: (secondary != null && secondary.trim().isNotEmpty && secondary != "null" ? secondary.trim() : null);
if (resolved != null && resolved.isNotEmpty) {
return resolved;
}
}
}
if (categoryName != null && categoryName!.trim().isNotEmpty && categoryName != "null") {
return categoryName!.trim();
}
if (fallbackCategories != null && categoryId != null && categoryId!.trim().isNotEmpty) {
for (var cat in fallbackCategories) {
if (cat.id?.toString() == categoryId?.toString()) {
String? primary = isAr ? cat.countryNameAr : cat.countryNameEn;
String? secondary = isAr ? (cat.name ?? cat.countryNameEn) : (cat.name ?? cat.countryNameAr);
String? resolved = (primary != null && primary.trim().isNotEmpty && primary != "null")
? primary.trim()
: (secondary != null && secondary.trim().isNotEmpty && secondary != "null" ? secondary.trim() : null);
if (resolved != null && resolved.isNotEmpty) {
return resolved;
}
}
}
}
if (type != null && type!.trim().isNotEmpty) {
String lt = type!.trim().toLowerCase();
if (lt != 'online' && lt != 'instore' && lt != 'store' && lt != 'in_store') {
return type!.trim();
}
}
return null;
}
String? get displayImage {
if (image != null && image!.trim().isNotEmpty && image != "null") return image!.trim();
if (imageThumb != null && imageThumb!.trim().isNotEmpty && imageThumb != "null") return imageThumb!.trim();
@ -688,8 +824,8 @@ class WalaPlusRetailerModel {
// this splits and normalizes every part instead of only ever matching a single exact value,
// so a combined retailer is correctly recognized as both rather than neither/just one.
List<String> get normalizedTypes {
Set<String> result = <String>{};
if (type != null && type!.trim().isNotEmpty) {
Set<String> result = <String>{};
for (String part in type!.split(',')) {
String t = part.trim().toLowerCase();
if (t.isEmpty) continue;
@ -698,15 +834,19 @@ class WalaPlusRetailerModel {
} else if (t == 'instore' || t == 'in_store' || t == 'in store' || t == 'store') {
result.add('instore');
} else {
result.add(t);
// Non-online types (e.g. category names like "Physiotherapy") represent
// in-store merchants/services that require physical presence.
result.add('instore');
}
}
if (result.isNotEmpty) return result.toList();
}
if (locations != null && locations!.isNotEmpty) {
return <String>['instore'];
result.add('instore');
}
return <String>[];
if (result.isEmpty && hasPhysicalLocation) {
result.add('instore');
}
return result.toList();
}
bool get isOnlineStore => normalizedTypes.contains('online');
@ -721,14 +861,12 @@ class WalaPlusRetailerModel {
String _displayTypeLabel(String norm) {
if (norm == 'online') return LocaleKeys.online.tr();
if (norm == 'instore') return LocaleKeys.inStore.tr();
return norm;
return LocaleKeys.inStore.tr();
}
String? getDisplayType() {
List<String> types = normalizedTypes;
if (types.isNotEmpty) return _displayTypeLabel(types.first);
if (type != null && type!.trim().isNotEmpty) return type!.trim();
return null;
}
@ -737,7 +875,6 @@ class WalaPlusRetailerModel {
List<String> getDisplayTypes() {
List<String> types = normalizedTypes;
if (types.isNotEmpty) return types.map(_displayTypeLabel).toList();
if (type != null && type!.trim().isNotEmpty) return <String>[type!.trim()];
return <String>[];
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save