retailer screen added.

wala_plus
Sultan khan 2 weeks ago
parent 782ad51823
commit ca6ce85d7d

@ -7,6 +7,7 @@ import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_categories_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
class OffersAndDiscountsApiClient {
static final OffersAndDiscountsApiClient _instance = OffersAndDiscountsApiClient._internal();
@ -75,10 +76,19 @@ class OffersAndDiscountsApiClient {
String url = ApiConsts.walaPlusCategories;
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = "2537367522";
}
String? tokenID = AppState().postParamsObject?.tokenID;
if (tokenID == null || tokenID.isEmpty || tokenID == "adm!n") {
tokenID = "@dm!n";
}
String? logInTokenID = AppState().postParamsObject?.logInTokenID;
if (logInTokenID == null || logInTokenID.isEmpty) {
logInTokenID = "@dm!n";
}
Map<String, dynamic> postParams = {
"VersionID": 20,
@ -93,9 +103,15 @@ class OffersAndDiscountsApiClient {
(response) {
List<WalaPlusCategoryModel> categories = [];
if (response != null && response['WalaPlus_Categories'] != null) {
response['WalaPlus_Categories'].forEach((v) {
categories.add(WalaPlusCategoryModel.fromJson(v));
});
var cats = response['WalaPlus_Categories'];
if (cats is String) {
cats = json.decode(cats);
}
if (cats is List) {
cats.forEach((v) {
categories.add(WalaPlusCategoryModel.fromJson(v));
});
}
}
return categories;
},
@ -103,4 +119,118 @@ class OffersAndDiscountsApiClient {
postParams,
);
}
Future<WalaPlusRetailersResponse?> getWalaPlusRetailers({
String? categoryId,
int page = 1,
int perPage = 10,
String? geoLocation,
BuildContext? context,
}) async {
String url = ApiConsts.walaPlusRetailers;
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = "2537367522";
}
String? tokenID = AppState().postParamsObject?.tokenID;
if (tokenID == null || tokenID.isEmpty) {
tokenID = "adm!n";
}
String? logInTokenID = AppState().postParamsObject?.logInTokenID;
if (logInTokenID == null || logInTokenID.isEmpty) {
logInTokenID = "@dm!n";
}
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"LogInTokenID": logInTokenID,
"TokenID": tokenID,
"P_USER_NAME": pUserName,
"WP_CategoryID": (categoryId == "all" || categoryId == null) ? "" : categoryId,
"WP_Page": page,
"WP_Per_page": perPage,
};
if (geoLocation != null && geoLocation.isNotEmpty) {
postParams["WP_Geo_Location"] = geoLocation;
}
return await ApiClient().postJsonForObject(
(response) {
if (response != null && response['WalaPlus_Retailers'] != null) {
var val = response['WalaPlus_Retailers'];
if (val is String) {
val = json.decode(val);
}
if (val is Map<String, dynamic>) {
return WalaPlusRetailersResponse.fromJson(val);
} else if (val is Map) {
return WalaPlusRetailersResponse.fromJson(Map<String, dynamic>.from(val));
}
}
return null;
},
url,
postParams,
);
}
Future<WalaPlusRetailerModel?> getWalaPlusRetailerById({
required String retailerId,
BuildContext? context,
}) async {
String url = ApiConsts.walaPlusRetailersByID;
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = "2537367522";
}
String? tokenID = AppState().postParamsObject?.tokenID;
if (tokenID == null || tokenID.isEmpty) {
tokenID = "adm!n";
}
String? logInTokenID = AppState().postParamsObject?.logInTokenID;
if (logInTokenID == null || logInTokenID.isEmpty) {
logInTokenID = "@dm!n";
}
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"LogInTokenID": logInTokenID,
"TokenID": tokenID,
"RetailerID": retailerId,
"P_USER_NAME": pUserName,
};
return await ApiClient().postJsonForObject(
(response) {
if (response != null && response['WalaPlus_RetailersByID'] != null) {
var val = response['WalaPlus_RetailersByID'];
if (val is String) {
val = json.decode(val);
}
if (val is List && val.isNotEmpty) {
var item = val.first;
if (item is Map<String, dynamic>) {
return WalaPlusRetailerModel.fromJson(item);
} else if (item is Map) {
return WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(item));
}
}
}
return null;
},
url,
postParams,
);
}
}

@ -41,6 +41,8 @@ class ApiConsts {
// WalaPlus Endpoints
static String walaPlusCategories = erpRest + "WalaPlus_Categories";
static String walaPlusRetailers = erpRest + "WalaPlus_Retailers";
static String walaPlusRetailersByID = erpRest + "WalaPlus_RetailersByID";
static String ssoAuthRedirection = ssoBaseUrl + "/api/auth/connect";

@ -15,6 +15,7 @@ import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen_for_dependet.dart';
import 'package:mohem_flutter_app/ui/landing/wala_plus_retailer_details_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart';
import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart';
@ -99,6 +100,7 @@ class AppRoutes {
static const String loginVerification = "/loginVerification";
static const String dashboard = "/dashboard";
static const String dashboardScreenForDependet = "/dashboardScreenForDependet";
static const String walaPlusRetailerDetails = "/walaPlusRetailerDetails";
static const String todayAttendance = "/todayAttendance";
static const String subMenuScreen = "/submenuScreen";
static const String addEitScreen = "/addeitScreen";
@ -234,6 +236,7 @@ class AppRoutes {
verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(),
dashboard: (BuildContext context) => DashboardScreen(),
dashboardScreenForDependet: (BuildContext context) => const DashboardScreenForDependet(),
walaPlusRetailerDetails: (BuildContext context) => const WalaPlusRetailerDetailsScreen(),
survey: (BuildContext context) => SurveyScreen(),
advertisement: (BuildContext context) => ITGAdsScreen(),

@ -56,10 +56,12 @@ class OtpDialog {
String? _code;
dynamic setState;
bool stopTimer = false;
bool isCodeExpired = false;
// static String signature;
void displayDialog(BuildContext context) async {
otpFieldClear.value = "";
showDialog(
context: context,
barrierColor: Colors.black.withOpacity(0.63),
@ -96,7 +98,20 @@ class OtpDialog {
LocaleKeys.otp.tr().toText14(),
LocaleKeys.verification.tr().toText24(isBold: true),
6.height,
(LocaleKeys.pleaseEnterTheVerificationCodeSentTo.tr() + ' xxxxxxxx' + mobileNo.toString().substring(mobileNo.toString().length - 3)).toText16(),
Builder(
builder: (context) {
String maskedNumber = "";
if (mobileNo != null) {
String numStr = mobileNo.toString();
if (numStr.length >= 3) {
maskedNumber = ' xxxxxxxx' + numStr.substring(numStr.length - 3);
} else {
maskedNumber = ' ' + numStr;
}
}
return (LocaleKeys.pleaseEnterTheVerificationCodeSentTo.tr() + maskedNumber).toText16();
},
),
18.height,
ValueListenableBuilder<String>(
builder: (BuildContext context, String value, Widget? child) {
@ -128,7 +143,7 @@ class OtpDialog {
),
),
10.height,
stopTimer
isCodeExpired
? Row(
children: [
Expanded(
@ -164,18 +179,17 @@ class OtpDialog {
),
18.height,
DefaultButton(
stopTimer ? LocaleKeys.resend.tr() : LocaleKeys.cancel.tr(),
isCodeExpired ? LocaleKeys.resend.tr() : LocaleKeys.cancel.tr(),
() async {
if (stopTimer) {
if (isCodeExpired) {
hideSMSBox(context);
onResendCode();
} else {
stopTimer = true;
// onFailure();
hideSMSBox(context);
}
},
colors: stopTimer
colors: isCodeExpired
? null
: [
MyColors.pinkDarkColor,
@ -259,6 +273,7 @@ class OtpDialog {
if (stopTimer) return;
try {
setState(() {
isCodeExpired = true;
stopTimer = true;
});
} catch (_) {}
@ -272,8 +287,8 @@ class OtpDialog {
}
void _onOtpCallBack(String otpCode, bool? isAutofill) {
if (isCodeExpired) return;
if (otpCode.length == 4) {
stopTimer = true;
otpFieldClear.value = otpCode;
onSuccess(otpCode, _pinPutController);
}

@ -1,6 +1,6 @@
class GetMobileLoginInfoListModel {
int? iD;
int? employeeID;
String? employeeID;
int? channelID;
int? companyID;
String? deviceType;

@ -0,0 +1,305 @@
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 = [];
if (json['retailers'] != null && json['retailers'] is List) {
json['retailers'].forEach((v) {
retailersList.add(WalaPlusRetailerModel.fromJson(v));
});
}
return WalaPlusRetailersResponse(
page: json['page'] is int ? json['page'] : int.tryParse(json['page']?.toString() ?? ''),
pagesCount: json['pages_count'] is int ? json['pages_count'] : int.tryParse(json['pages_count']?.toString() ?? ''),
perPage: json['per_page'] is int ? json['per_page'] : int.tryParse(json['per_page']?.toString() ?? ''),
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 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<WalaPlusRetailerLocation>? locations;
List<WalaPlusCategoryModel>? categories;
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.locations,
this.categories,
});
factory WalaPlusRetailerModel.fromJson(Map<String, dynamic> json) {
List<WalaPlusRetailerLocation> locationsList = [];
if (json['locations'] != null && json['locations'] is List) {
json['locations'].forEach((v) {
locationsList.add(WalaPlusRetailerLocation.fromJson(v));
});
}
List<WalaPlusCategoryModel> categoriesList = [];
if (json['categories'] != null && json['categories'] is List) {
json['categories'].forEach((v) {
categoriesList.add(WalaPlusCategoryModel.fromJson(v));
});
}
WalaPlusRetailerContact? contactObj;
if (json['contact'] != null) {
if (json['contact'] is Map) {
contactObj = WalaPlusRetailerContact.fromJson(Map<String, dynamic>.from(json['contact']));
} else if (json['contact'] is String && json['contact'].toString().trim().isNotEmpty) {
contactObj = WalaPlusRetailerContact(website: json['contact'].toString());
}
}
return WalaPlusRetailerModel(
id: json['id']?.toString(),
name: json['name']?.toString(),
offerTitle: json['offer_title']?.toString(),
offerType: json['offer_type']?.toString(),
description: json['description']?.toString(),
info: json['info']?.toString(),
image: json['image']?.toString(),
imageThumb: json['image_thumb']?.toString(),
logo: json['logo']?.toString(),
logoThumb: json['logo_thumb']?.toString(),
website: json['website']?.toString(),
type: json['type']?.toString(),
usage: json['usage'],
weight: json['weight'],
geoId: json['geo_id'],
pin: json['pin']?.toString(),
barcodeStandard: json['barcode_standard']?.toString(),
arHowToUse: json['ar_how_to_use']?.toString(),
enHowToUse: json['en_how_to_use']?.toString(),
howToUse: json['how_to_use']?.toString(),
contact: contactObj,
offerWeight: json['offer_weight'],
locations: locationsList,
categories: categoriesList,
);
}
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;
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 resolvedWebsite {
if (contact?.website != null && contact!.website!.trim().isNotEmpty) {
return contact!.website!.trim();
}
if (website != null && website!.trim().isNotEmpty) {
return website!.trim();
}
return null;
}
String get displayCity {
if (locations != null && locations!.isNotEmpty) {
for (var loc in locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty) {
return loc.city!.trim();
}
}
}
if (type?.toLowerCase() == 'online') {
return 'Online';
}
return '';
}
String get displayUsage {
if (usage != null && usage.toString().isNotEmpty) {
return 'Usage : $usage';
}
if (type != null && type!.isNotEmpty) {
if (type!.toLowerCase() == 'online') return 'Online';
if (type!.toLowerCase() == 'store') return 'In Store';
return type!;
}
return '';
}
}
class WalaPlusRetailerContact {
List<String>? emails;
List<String>? phones;
String? website;
WalaPlusRetailerContact({this.emails, this.phones, this.website});
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()));
}
List<String> phonesList = [];
if (json['phones'] != null && json['phones'] is List) {
json['phones'].forEach((v) => phonesList.add(v.toString()));
}
return WalaPlusRetailerContact(
emails: emailsList,
phones: phonesList,
website: json['website']?.toString(),
);
}
Map<String, dynamic> toJson() {
return {
'emails': emails,
'phones': phones,
'website': website,
};
}
}
class WalaPlusRetailerLocation {
String? address;
String? city;
String? cityNameAr;
String? cityNameEn;
String? contact;
String? countryNameAr;
String? countryNameEn;
double? latitude;
double? longitude;
dynamic placeId;
WalaPlusRetailerLocation({
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(
address: json['address']?.toString(),
city: json['city']?.toString(),
cityNameAr: json['city_name_ar']?.toString(),
cityNameEn: json['city_name_en']?.toString(),
contact: json['contact']?.toString(),
countryNameAr: json['country_name_ar']?.toString(),
countryNameEn: json['country_name_en']?.toString(),
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['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;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,630 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.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/consts.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:shimmer/shimmer.dart';
import 'package:url_launcher/url_launcher.dart';
class WalaPlusRetailerDetailsScreen extends StatefulWidget {
final WalaPlusRetailerModel? retailer;
final String? retailerId;
const WalaPlusRetailerDetailsScreen({
Key? key,
this.retailer,
this.retailerId,
}) : super(key: key);
@override
State<WalaPlusRetailerDetailsScreen> createState() => _WalaPlusRetailerDetailsScreenState();
}
class _WalaPlusRetailerDetailsScreenState extends State<WalaPlusRetailerDetailsScreen> {
WalaPlusRetailerModel? _retailer;
bool _isLoading = false;
String? _effectiveRetailerId;
@override
void initState() {
super.initState();
_retailer = widget.retailer;
_effectiveRetailerId = widget.retailerId ?? widget.retailer?.id;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_retailer == null && _effectiveRetailerId == null) {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is WalaPlusRetailerModel) {
_retailer = args;
_effectiveRetailerId = args.id;
} else if (args is String) {
_effectiveRetailerId = args;
}
}
if (_retailer == null && _effectiveRetailerId != null && !_isLoading) {
_fetchRetailerDetails();
}
}
Future<void> _fetchRetailerDetails() async {
if (_effectiveRetailerId == null) return;
setState(() {
_isLoading = true;
});
try {
WalaPlusRetailerModel? result = await OffersAndDiscountsApiClient().getWalaPlusRetailerById(
retailerId: _effectiveRetailerId!,
context: context,
);
if (result != null && mounted) {
setState(() {
_retailer = result;
});
}
} catch (e) {
debugPrint("Error fetching WalaPlus retailer by ID: $e");
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
void _onUseOffer() async {
String? website = _retailer?.resolvedWebsite;
if (website != null && website.isNotEmpty) {
try {
Uri uri = Uri.parse(website);
if (!uri.hasScheme) {
uri = Uri.parse("https://$website");
}
await launchUrl(uri, mode: LaunchMode.externalApplication);
return;
} catch (e) {
debugPrint("Error launching website: $e");
}
}
// If promo pin exists, display a dialog with the promo code to copy
if (_retailer?.pin != null && _retailer!.pin!.isNotEmpty) {
_showPromoCodeDialog(_retailer!.pin!);
return;
}
// Otherwise show terms/usage dialog
_showTermsDialog();
}
void _showPromoCodeDialog(String pin) {
bool isAr = AppState().isArabic(context);
showDialog(
context: context,
builder: (cxt) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: Text(
isAr ? "كود الخصم" : "Promo Code",
style: const TextStyle(fontWeight: FontWeight.bold),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFFF3F4F6),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF20A4A8), width: 1.2),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
pin,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
letterSpacing: 2,
color: Color(0xFF1E293B),
),
),
IconButton(
icon: const Icon(Icons.copy_rounded, color: Color(0xFF20A4A8)),
onPressed: () {
Clipboard.setData(ClipboardData(text: pin));
Navigator.pop(cxt);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(isAr ? "تم نسخ الكود بنجاح" : "Promo code copied"),
duration: const Duration(seconds: 2),
),
);
},
),
],
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(cxt),
child: Text(isAr ? "إغلاق" : "Close", style: const TextStyle(color: Color(0xFF20A4A8))),
),
],
),
);
}
void _showTermsDialog() {
bool isAr = AppState().isArabic(context);
String content = _retailer?.howToUse ??
_retailer?.arHowToUse ??
_retailer?.enHowToUse ??
_retailer?.usage?.toString() ??
(isAr ? "تطبق الشروط والأحكام الخاصة بالعرض." : "Terms and conditions apply for this offer.");
// Clean html tags if simple
content = content.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ').trim();
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (cxt) => SafeArea(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
isAr ? "الشروط والأحكام" : "Terms and Conditions",
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(cxt),
),
],
),
const SizedBox(height: 12),
Text(
content,
style: const TextStyle(fontSize: 14, color: Color(0xFF4B5563), height: 1.5),
),
const SizedBox(height: 20),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
String title = _retailer?.name ?? (isAr ? "تفاصيل العرض" : "Offer Details");
return Scaffold(
backgroundColor: const Color(0xFFF8F9FA),
appBar: AppBarWidget(
context,
title: title,
showHomeButton: true,
onHomeTapped: () {
if (ApiConsts.isDependetLogin) {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboardScreenForDependet));
} else {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
}
},
),
body: _isLoading && _retailer == null
? _buildLoadingSkeleton()
: Column(
children: [
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0xFFF1F5F9), width: 1.2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 1. Top Banner Image with rounded corners
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SizedBox(
height: 190,
width: double.infinity,
child: (_retailer?.image != null && _retailer!.image!.isNotEmpty)
? Image.network(
_retailer!.image!,
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,
// 3. Retailer Name (Centered, Bold)
Center(
child: Text(
_retailer?.name ?? "",
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
),
18.height,
// 4. 3 Info Badges Row (Store Type, Validity, City)
_buildInfoBadgesRow(isAr),
22.height,
// 5. Description Section
Text(
isAr ? "الوصف:" : "description:",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
8.height,
Text(
_getCleanDescription(),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Color(0xFF4B5563),
height: 1.45,
),
),
16.height,
// 6. Terms and Conditions Link (Right aligned)
Align(
alignment: isAr ? Alignment.centerLeft : Alignment.centerRight,
child: InkWell(
onTap: _showTermsDialog,
child: Text(
isAr ? "الشروط والأحكام" : "terms and conditions",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF20A4A8),
decoration: TextDecoration.underline,
),
),
),
),
],
),
),
),
),
// 7. Bottom Action Button
Container(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 20),
color: Colors.white,
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _onUseOffer,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF20A4A8),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
isAr ? "استخدام العرض" : "Use Offer",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
),
),
],
),
);
}
Widget _buildBrandStrip(bool isAr) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Left: Human Resources branding
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: const [
Text(
"الموارد البشرية",
style: TextStyle(
color: Color(0xFFED1C2B),
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
Text(
"Human Resources",
style: TextStyle(
color: Color(0xFFED1C2B),
fontSize: 8.5,
fontWeight: FontWeight.w600,
),
),
],
),
// Center: Retailer Logo
if (_retailer?.logo != null && _retailer!.logo!.isNotEmpty)
Image.network(
_retailer!.logo!,
height: 36,
width: 36,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => const SizedBox(width: 36, height: 36),
)
else
const SizedBox(width: 36, height: 36),
// Right: Dr Sulaiman Al Habib branding with red cross
Row(
mainAxisSize: MainAxisSize.min,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: const [
Text(
"سليمان الحبيب",
style: TextStyle(
color: Color(0xFF4B5563),
fontSize: 9,
fontWeight: FontWeight.w700,
),
),
Text(
"DR SULAIMAN AL HABIB",
style: TextStyle(
color: Color(0xFF4B5563),
fontSize: 8,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(width: 5),
SvgPicture.asset(
"assets/icons/mazaya_brand.svg",
height: 22,
fit: BoxFit.contain,
),
],
),
],
);
}
Widget _buildInfoBadgesRow(bool isAr) {
String storeTypeText = (_retailer?.type?.toLowerCase() == 'online')
? (isAr ? "عبر الإنترنت" : "online")
: (isAr ? "في المتجر" : "in store");
IconData storeIcon = (_retailer?.type?.toLowerCase() == 'online')
? Icons.language_rounded
: Icons.storefront_rounded;
String cityText = _retailer?.displayCity ?? "";
if (cityText.isEmpty) {
cityText = isAr ? "الرياض" : "Riyadh";
}
String validityText = isAr ? "٣ أشهر" : "3 Month";
return Row(
children: [
Expanded(
child: _buildInfoCard(
icon: storeIcon,
label: storeTypeText,
),
),
12.width,
Expanded(
child: _buildInfoCard(
icon: Icons.check_circle_outline_rounded,
label: validityText,
),
),
12.width,
Expanded(
child: _buildInfoCard(
icon: Icons.location_on_rounded,
label: cityText,
),
),
],
);
}
Widget _buildInfoCard({required IconData icon, required String label}) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 8),
decoration: BoxDecoration(
color: const Color(0xFFF4F6F9),
borderRadius: BorderRadius.circular(14),
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: const Color(0xFF20A4A8), size: 26),
const SizedBox(height: 8),
Text(
label,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF334155),
),
),
],
),
);
}
String _getCleanDescription() {
String desc = _retailer?.description ?? _retailer?.info ?? _retailer?.offerTitle ?? "";
if (desc.isEmpty) {
return AppState().isArabic(context) ? "لا توجد تفاصيل إضافية متاحة." : "No additional details available.";
}
// Remove HTML tags if string contains them
return desc.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ').trim();
}
Widget _buildLoadingSkeleton() {
return Shimmer.fromColors(
baseColor: const Color(0xFFE2E8F0),
highlightColor: const Color(0xFFF8FAFC),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 190,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
),
const SizedBox(height: 16),
Center(
child: Container(
height: 24,
width: 160,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
),
),
const SizedBox(height: 20),
Row(
children: List.generate(
3,
(index) => Expanded(
child: Container(
height: 75,
margin: EdgeInsets.symmetric(horizontal: index == 1 ? 8 : 0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
),
),
),
),
),
const SizedBox(height: 24),
Container(
height: 16,
width: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 10),
Container(
height: 14,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 6),
Container(
height: 14,
width: 220,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
),
],
),
),
),
);
}
}
Loading…
Cancel
Save