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