no message

master
Sultan khan 2 weeks ago
parent e21738cf5f
commit f6865959e6

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -809,6 +809,8 @@
"microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت.",
"shareAccessGuide": "مشاركة دليل الوصول",
"nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة",
"multipleCities": "مدن متعددة"
"multipleCities": "مدن متعددة",
"visitMerchantWebsite": "زيارة موقع التاجر",
"merchantWebsiteNotAvailable": "موقع التاجر غير متوفر"
}

@ -809,7 +809,8 @@
"microphoneAccessRequired": "The app needs microphone access to be able to record audio.",
"shareAccessGuide": "Share Access Guide",
"nationalIdOrIqamaNumber": "National ID or Iqama Number",
"multipleCities": "Multiple Cities"
"multipleCities": "Multiple Cities",
"visitMerchantWebsite": "Visit Merchant Website",
"merchantWebsiteNotAvailable": "Merchant website not available"
}

@ -261,6 +261,7 @@ class LoginApiClient {
AppState().postParamsInitConfig.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
AppState().postParamsObject?.setPayrollCodeStr = responseData.memberInformationList!.first.pAYROLLCODE;
AppState().setBusinessCardPrivilege = responseData.businessCardPrivilege ?? false;
AppState().setPrivilegeListModel = responseData.privilegeList ?? [];
return responseData;
}, url, postParams);
}

@ -398,12 +398,24 @@ class OffersAndDiscountsApiClient {
(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 (web != null && web.trim().isNotEmpty && web != "null") {
model?.website = web.trim();
}
}
} else if (response is Map<String, dynamic> && response.containsKey('name')) {
model = WalaPlusRetailerModel.fromJson(response);
}
if (model != null) {
if (response is Map) {
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']);
}

@ -78,11 +78,17 @@ 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.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();
}
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;
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();
}
retList.add(model);
}
}
@ -90,6 +96,9 @@ 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.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();
}
retList.add(model);
}
@ -164,6 +173,7 @@ class WalaPlusRetailerModel {
String? phone;
String? countryNameAr;
String? countryNameEn;
bool? isFavorite;
WalaPlusRetailerModel({
this.id,
@ -177,6 +187,7 @@ class WalaPlusRetailerModel {
this.logo,
this.logoThumb,
this.website,
this.isFavorite,
this.type,
this.usage,
this.weight,
@ -314,6 +325,39 @@ class WalaPlusRetailerModel {
}
}
String? foundWebsite = targetJson['website']?.toString() ??
targetJson['merchant_website']?.toString() ??
targetJson['merchantWebsite']?.toString() ??
targetJson['website_url']?.toString() ??
targetJson['websiteUrl']?.toString() ??
targetJson['web_site']?.toString() ??
targetJson['url']?.toString() ??
targetJson['merchant_url']?.toString() ??
targetJson['merchantUrl']?.toString() ??
targetJson['merchantLink']?.toString() ??
targetJson['merchant_link']?.toString() ??
targetJson['link']?.toString() ??
contactObj?.website;
if (foundWebsite == null || foundWebsite.trim().isEmpty || foundWebsite == "null") {
if (targetJson['merchant'] is Map) {
var m = targetJson['merchant'];
foundWebsite = (m['website'] ?? m['merchant_website'] ?? m['url'] ?? m['link'])?.toString();
}
}
if (foundWebsite == null || foundWebsite.trim().isEmpty || foundWebsite == "null") {
for (var entry in targetJson.entries) {
String k = entry.key.toLowerCase();
if ((k.contains('website') || k.contains('web_site') || k == 'url' || k.contains('merchant_url')) && !k.contains('image') && !k.contains('logo')) {
if (entry.value != null && entry.value.toString().trim().isNotEmpty && entry.value.toString() != "null") {
foundWebsite = entry.value.toString().trim();
break;
}
}
}
}
return WalaPlusRetailerModel(
id:
targetJson['id']?.toString() ??
@ -330,7 +374,12 @@ class WalaPlusRetailerModel {
imageThumb: targetJson['image_thumb']?.toString(),
logo: targetJson['logo']?.toString(),
logoThumb: targetJson['logo_thumb']?.toString(),
website: targetJson['website']?.toString(),
website: foundWebsite,
isFavorite: targetJson['is_favorite'] == true ||
targetJson['is_favorite'] == 1 ||
targetJson['is_favorite'] == 'true' ||
targetJson['isFavorite'] == true ||
targetJson['favorite'] == true,
type: targetJson['type']?.toString(),
usage: targetJson['usage'] is String ? decodeUnicodeString(targetJson['usage']) : targetJson['usage'],
weight: targetJson['weight'],
@ -438,6 +487,7 @@ class WalaPlusRetailerModel {
data['phone'] = phone;
data['country_name_ar'] = countryNameAr;
data['country_name_en'] = countryNameEn;
data['is_favorite'] = isFavorite;
if (offers != null) {
data['offers'] = offers!.map((v) => v.toJson()).toList();
}
@ -463,12 +513,12 @@ class WalaPlusRetailerModel {
}
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();
}
if (contact?.website != null && contact!.website!.trim().isNotEmpty && contact!.website != "null") {
return contact!.website!.trim();
}
return null;
}
@ -521,6 +571,18 @@ class WalaPlusRetailerModel {
return distinctCities.first;
}
List<String> getDistinctCities({bool isAr = false}) {
if (locations == null || locations!.isEmpty) return <String>[];
Set<String> distinctCities = <String>{};
for (var loc in locations!) {
String? resolved = _resolveLocationCity(loc, isAr);
if (resolved != null && resolved.trim().isNotEmpty && resolved != "null") {
distinctCities.add(resolved.trim());
}
}
return distinctCities.toList();
}
String get displayCity => getDisplayCity(isAr: false);
String get displayUsage {

@ -7,20 +7,23 @@ class PrivilegeListModel {
int? iD;
String? serviceName;
bool? previlege;
bool? previlegeHMG;
PrivilegeListModel({this.iD, this.serviceName, this.previlege});
PrivilegeListModel({this.iD, this.serviceName, this.previlege, this.previlegeHMG});
PrivilegeListModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
serviceName = json['ServiceName'];
previlege = json['Previlege'];
previlege = json['Previlege'] ?? json['previlege'] ?? json['Privilege'] ?? json['privilege'];
previlegeHMG = json['Previlege_HMG'] ?? json['PrevilegeHMG'] ?? json['previlege_HMG'] ?? json['previlegeHMG'] ?? json['Privilege_HMG'] ?? json['PrivilegeHMG'];
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = new Map<String, dynamic>();
data['ID'] = this.iD;
data['ServiceName'] = this.serviceName;
data['Previlege'] = this.previlege;
Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = iD;
data['ServiceName'] = serviceName;
data['Previlege'] = previlege;
data['Previlege_HMG'] = previlegeHMG;
return data;
}

@ -29,6 +29,7 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_banner.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart';
import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
@ -62,6 +63,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
isDisplayMazaya = checkIfPrivilegedForMazaya();
cProvider = Provider.of<ChatProviderModel>(context, listen: false);
scheduleMicrotask(() {
data = Provider.of<DashboardProviderModel>(context, listen: false);
@ -254,6 +256,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
@override
Widget build(BuildContext context) {
isDisplayMazaya = checkIfPrivilegedForMazaya();
return SafeArea(
bottom: Platform.isAndroid ? true : false,
top: false,
@ -471,101 +474,98 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
),
),
).paddingOnly(left: 21, right: 21, top: 0, bottom: 21),
],
// Offers & Discounts commented out as requested
/*
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LocaleKeys.offers.tr().toText12(),
Row(
children: [
LocaleKeys.discounts.tr().toText24(isBold: true),
6.width,
Container(
padding: const EdgeInsets.only(left: 8, right: 8),
decoration: BoxDecoration(color: MyColors.yellowColor, borderRadius: BorderRadius.circular(10)),
child: LocaleKeys.newString.tr().toText10(isBold: true),
),
],
),
],
] else ...<Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LocaleKeys.offers.tr().toText12(),
Row(
children: [
LocaleKeys.discounts.tr().toText24(isBold: true),
6.width,
Container(
padding: const EdgeInsets.only(left: 8, right: 8),
decoration: BoxDecoration(color: MyColors.yellowColor, borderRadius: BorderRadius.circular(10)),
child: LocaleKeys.newString.tr().toText10(isBold: true),
),
],
),
],
),
),
),
LocaleKeys.viewAllOffers.tr().toText12(isUnderLine: true).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
}),
],
).paddingOnly(left: 21, right: 21),
Consumer<DashboardProviderModel>(
builder: (BuildContext context, DashboardProviderModel model, Widget? child) {
return SizedBox(
height: 103 + 33,
child: ListView.separated(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 21, right: 21, top: 13),
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext cxt, int index) {
return model.isOffersLoading
? const OffersShimmerWidget()
: InkWell(
onTap: () {
navigateToDetails(data.getOffersList[index]);
},
child: SizedBox(
width: 73,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 73,
height: 73,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(100)),
border: Border.all(color: MyColors.lightGreyE3Color, width: 1),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(50)),
child: Hero(
tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString()!,
transitionOnUserGestures: true,
child: Image.network(data.getOffersList[index].logo ?? "", fit: BoxFit.contain),
LocaleKeys.viewAllOffers.tr().toText12(isUnderLine: true).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
}),
],
).paddingOnly(left: 21, right: 21),
Consumer<DashboardProviderModel>(
builder: (BuildContext context, DashboardProviderModel model, Widget? child) {
return SizedBox(
height: 103 + 33,
child: ListView.separated(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 21, right: 21, top: 13),
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext cxt, int index) {
return model.isOffersLoading
? const OffersShimmerWidget()
: InkWell(
onTap: () {
navigateToDetails(data.getOffersList[index]);
},
child: SizedBox(
width: 73,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 73,
height: 73,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(100)),
border: Border.all(color: MyColors.lightGreyE3Color, width: 1),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(50)),
child: Hero(
tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString(),
transitionOnUserGestures: true,
child: Image.network(data.getOffersList[index].logo ?? "", fit: BoxFit.contain),
),
),
),
4.height,
Expanded(
child:
AppState().isArabic(context)
? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1)
: data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1),
),
],
),
),
),
4.height,
Expanded(
child:
AppState().isArabic(context)
? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1)
: data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1),
),
],
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => 8.width,
itemCount: 9,
),
);
},
),
],
),
*/
);
},
separatorBuilder: (BuildContext cxt, int index) => 8.width,
itemCount: model.isOffersLoading ? 9 : (data.getOffersList.length > 9 ? 9 : data.getOffersList.length),
),
);
},
),
],
),
],
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 31),
@ -774,4 +774,14 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
}
return false;
}
bool checkIfPrivilegedForMazaya() {
if (AppState().privilegeListModel == null) return false;
for (PrivilegeListModel element in AppState().privilegeListModel!) {
if (element.serviceName?.toLowerCase() == "mazayaeligibility") {
return (element.previlege ?? false) || (element.previlegeHMG ?? false);
}
}
return false;
}
}

@ -443,16 +443,22 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
}
Widget _buildLanguageIconButton(bool isAr) {
return IconButton(
icon: const Icon(Icons.language_rounded, size: 24, color: Color(0xFF1E293B)),
tooltip: isAr ? 'English' : 'العربية',
onPressed: () {
if (isAr) {
_changeLanguage(const Locale("en", "US"));
} else {
_changeLanguage(const Locale("ar", "SA"));
}
},
return Tooltip(
message: isAr ? 'English' : 'العربية',
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () {
if (isAr) {
_changeLanguage(const Locale("en", "US"));
} else {
_changeLanguage(const Locale("ar", "SA"));
}
},
child: const Padding(
padding: EdgeInsets.all(3),
child: Icon(Icons.language_rounded, size: 24, color: Color(0xFF1E293B)),
),
),
);
}
@ -486,9 +492,9 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
_performLogout();
}
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: Icon(isAr ? Icons.arrow_forward_ios_rounded : Icons.arrow_back_ios_new_rounded, size: 20, color: const Color(0xFF1E293B)),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B)),
),
),
const SizedBox(width: 8),
@ -499,37 +505,56 @@ class _DashboardScreenForDependetState extends State<DashboardScreenForDependet>
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (ApiConsts.isDependetLogin)
_buildLanguageIconButton(isAr),
if (!ApiConsts.isDependetLogin) ...[
IconButton(
icon: Image.asset(
'assets/icons/ic_add_dependents.png',
width: 24,
height: 24,
color: const Color(0xFF1E293B),
errorBuilder:
(context, error, stackTrace) =>
SvgPicture.asset('assets/icons/ic_dependents_top.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode(Color(0xFF1E293B), BlendMode.srcIn)),
const SizedBox(width: 4),
Tooltip(
message: LocaleKeys.addDependents.tr(),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () {
Navigator.pushNamed(context, AppRoutes.familyMembers, arguments: {'fromMazaya': true});
},
child: Padding(
padding: const EdgeInsets.all(3),
child: Image.asset(
'assets/icons/ic_add_dependents.png',
width: 24,
height: 24,
color: const Color(0xFF1E293B),
errorBuilder:
(context, error, stackTrace) =>
SvgPicture.asset('assets/icons/ic_dependents_top.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode(Color(0xFF1E293B), BlendMode.srcIn)),
),
),
),
tooltip: LocaleKeys.addDependents.tr(),
onPressed: () {
Navigator.pushNamed(context, AppRoutes.familyMembers, arguments: {'fromMazaya': true});
},
),
],
IconButton(
icon: const Icon(Icons.home_rounded, size: 26, color: Color(0xFF1E293B)),
onPressed: () {
if (ApiConsts.isDependetLogin) {
_onRefresh();
} else {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
}
},
const SizedBox(width: 4),
Tooltip(
message: LocaleKeys.favorite.tr(),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () {
// Favorite
},
child: const Padding(
padding: EdgeInsets.all(3),
child: Icon(Icons.favorite_border_rounded, size: 24, color: Color(0xFF1E293B)),
),
),
),
if (ApiConsts.isDependetLogin) ...[
const SizedBox(width: 4),
InkWell(onTap: _performLogout, child: SvgPicture.asset('assets/images/drawer/logout.svg', height: 20, width: 20)),
InkWell(
borderRadius: BorderRadius.circular(8),
onTap: _performLogout,
child: Padding(
padding: const EdgeInsets.all(3),
child: SvgPicture.asset('assets/images/drawer/logout.svg', height: 20, width: 20),
),
),
],
],
),

@ -41,12 +41,14 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
String? _effectiveRetailerId;
bool _hasFetched = false;
bool _isRedeeming = false;
bool _isFavorite = false;
@override
void initState() {
super.initState();
_retailer = widget.retailer;
_effectiveRetailerId = widget.retailerId ?? widget.retailer?.id;
_isFavorite = widget.retailer?.isFavorite ?? false;
}
@override
@ -57,6 +59,7 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
if (args is WalaPlusRetailerModel) {
_retailer ??= args;
_effectiveRetailerId = args.id;
_isFavorite = args.isFavorite ?? _isFavorite;
} else if (args is String) {
_effectiveRetailerId = args;
}
@ -115,6 +118,12 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
if ((result.offerTitle == null || result.offerTitle!.isEmpty) && _retailer?.offerTitle != null) {
result.offerTitle = _retailer!.offerTitle;
}
if ((result.website == null || result.website!.isEmpty || result.website == "null") && _retailer?.website != null) {
result.website = _retailer!.website;
}
if (result.isFavorite == null && _retailer?.isFavorite != null) {
result.isFavorite = _retailer!.isFavorite;
}
if (result.contact == null && _retailer?.contact != null) {
result.contact = _retailer!.contact;
}
@ -128,6 +137,7 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
setState(() {
_retailer = result;
_hasDetailsData = true;
_isFavorite = result.isFavorite ?? _isFavorite;
});
}
} catch (e) {
@ -141,6 +151,41 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
}
}
Future<void> _openMerchantWebsite() async {
String? web = _retailer?.resolvedWebsite;
if (web == null || web.trim().isEmpty || web == "null") {
Utils.showToast(LocaleKeys.merchantWebsiteNotAvailable.tr());
return;
}
String url = web.trim();
try {
Uri uri = Uri.parse(url);
if (!uri.hasScheme) {
uri = Uri.parse("https://$url");
}
bool launched = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (!launched) {
await launchUrl(uri);
}
} catch (e) {
debugPrint("Error opening merchant website with external application: $e");
try {
await Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => AppInAppWebViewScreen(
url: url.startsWith("http") ? url : "https://$url",
title: _retailer?.name ?? LocaleKeys.visitMerchantWebsite.tr(),
),
),
);
} catch (err) {
debugPrint("Error opening merchant website in webview: $err");
Utils.showToast(url);
}
}
}
Future<void> _openInAppBrowser(String url, {String? offerType}) async {
String title = _retailer?.name ?? LocaleKeys.offerDetails.tr();
// Offer types seen so far: "promo", "discount", "unique_promo", "promo_unique_qr" (and
@ -497,38 +542,86 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 1. Top Banner Image with rounded corners
// 1. Top Banner Image with rounded corners (tappable to open merchant website)
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SizedBox(
height: 190,
width: double.infinity,
child:
(_retailer?.displayImage != null && _retailer!.displayImage!.isNotEmpty)
? Image.network(
_retailer!.displayImage!,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container(color: const Color(0xFFF3F4F6), child: const Icon(Icons.broken_image, size: 48, color: Color(0xFF9CA3AF))),
)
: Container(color: const Color(0xFFF3F4F6), child: const Icon(Icons.image_outlined, size: 48, color: Color(0xFF9CA3AF))),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _openMerchantWebsite,
child: SizedBox(
height: 190,
width: double.infinity,
child:
(_retailer?.displayImage != null && _retailer!.displayImage!.isNotEmpty)
? Image.network(
_retailer!.displayImage!,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container(color: const Color(0xFFF3F4F6), child: const Icon(Icons.broken_image, size: 48, color: Color(0xFF9CA3AF))),
)
: Container(color: const Color(0xFFF3F4F6), child: const Icon(Icons.image_outlined, size: 48, color: Color(0xFF9CA3AF))),
),
),
),
),
14.height,
// 2. Brand Strip (Human Resources - Retailer Logo - Dr Sulaiman Al Habib)
_buildBrandStrip(isAr),
18.height,
16.height,
// 3. Retailer Name (Centered, Bold) & Offer Title Subtitle
(_retailer?.name ?? "").toText22(isBold: true, isCentered: false, color: const Color(0xFF1E293B)),
// if (_retailer?.offerTitle != null && _retailer!.offerTitle!.trim().isNotEmpty && _retailer!.offerTitle!.trim() != _retailer!.name?.trim()) ...[
// 6.height,
// Center(child: _cleanHtml(_retailer!.offerTitle!).toText16(isCentered: true, color: const Color(0xFF168A89))),
// ],
18.height,
// 3. Retailer Name & Favorite Button
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: (_retailer?.name ?? "").toText22(isBold: true, isCentered: false, color: const Color(0xFF1E293B)),
),
const SizedBox(width: 12),
Container(
width: 44,
height: 44,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFFF1F5F9),
),
child: IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
icon: Icon(
_isFavorite ? Icons.favorite_rounded : Icons.favorite_border_rounded,
size: 22,
color: _isFavorite ? const Color(0xFFE11D48) : const Color(0xFF1E293B),
),
tooltip: LocaleKeys.favorite.tr(),
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
_retailer?.isFavorite = _isFavorite;
});
},
),
),
],
),
12.height,
// 4. 3 Info Badges Row (Store Type, Validity, City)
// 4. Info Badges Row (Store Type, City Chips)
_buildInfoBadgesRow(isAr),
// 5. Visit Merchant Website Link
if (_retailer?.resolvedWebsite != null && _retailer!.resolvedWebsite!.trim().isNotEmpty) ...[
14.height,
InkWell(
onTap: _openMerchantWebsite,
child: LocaleKeys.visitMerchantWebsite.tr().toText14(
color: const Color(0xFF0D9488),
isUnderLine: true,
weight: FontWeight.w600,
),
),
],
10.height,
// 5. Description Section (parsed as HTML)
@ -609,18 +702,40 @@ class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsS
// that into its distinct parts, so a retailer offering both ways gets a chip each, same
// as the dashboard card.
List<String> offerTypes = _retailer?.normalizedTypes ?? <String>[];
List<String> cities = _retailer?.getDistinctCities(isAr: isAr) ?? <String>[];
return Wrap(
spacing: 12,
runSpacing: 10,
spacing: 8,
runSpacing: 8,
children: [
// One Type Chip per distinct type - Online (green) and/or In Store (blue).
for (String offerType in offerTypes)
InfoChipWidget(
text: offerType == 'online' ? LocaleKeys.online.tr() : (offerType == 'instore' ? LocaleKeys.inStore.tr() : (offerType.isNotEmpty ? (offerType[0].toUpperCase() + offerType.substring(1)) : offerType)),
icon: offerType == 'online' ? Icons.language_rounded : Icons.storefront_rounded,
text: offerType == 'online'
? LocaleKeys.online.tr()
: (offerType == 'instore'
? LocaleKeys.inStore.tr()
: (offerType.isNotEmpty ? (offerType[0].toUpperCase() + offerType.substring(1)) : offerType)),
icon: offerType == 'online' ? Icons.language_rounded : Icons.storefront_outlined,
backgroundColor: offerType == 'online' ? const Color(0xFFE6F7F0) : const Color(0xFFEFF6FF),
textColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF2563EB),
borderColor: offerType == 'online' ? const Color(0xFF00A651) : const Color(0xFF3B82F6),
horizontalPadding: 10,
verticalPadding: 5,
borderRadius: 12,
iconSize: 14,
),
for (String city in cities)
InfoChipWidget(
text: city,
icon: Icons.location_on_outlined,
backgroundColor: const Color(0xFFF8FAFC),
textColor: const Color(0xFF475569),
borderColor: const Color(0xFFCBD5E1),
horizontalPadding: 10,
verticalPadding: 5,
borderRadius: 12,
iconSize: 14,
),
],
);

@ -542,8 +542,8 @@ class _LoginScreenState extends State<LoginScreen> {
Widget _buildDependentCtaCard(BuildContext context) {
return _buildCtaCard(
context: context,
isSVG: true,
iconPath: 'assets/icons/mazaya_brand.svg',
isSVG: false,
iconPath: 'assets/images/mazaya.png',
title: LocaleKeys.joinMazaya.tr(),
subtitle: LocaleKeys.joinMazayaSubtitle.tr(),
onTap: () => _switchToMode(MemberType.dependent),
@ -555,7 +555,7 @@ class _LoginScreenState extends State<LoginScreen> {
return _buildCtaCard(
context: context,
iconPath: channel == 31 ? 'assets/images/logos/mohem_logo.png' : 'assets/images/logos/hmg_mohem_logo.png',
iconPath: 'assets/images/mohem.png',
title: LocaleKeys.continueAsEmployee.tr(),
subtitle: LocaleKeys.continueAsEmployeeSubtitle.tr(),
onTap: () => _switchToMode(MemberType.employee),

Loading…
Cancel
Save