diff --git a/assets/icons/filter_icon.svg b/assets/icons/filter_icon.svg
new file mode 100644
index 0000000..72042c3
--- /dev/null
+++ b/assets/icons/filter_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json
index 20eb838..dc65659 100644
--- a/assets/langs/ar-SA.json
+++ b/assets/langs/ar-SA.json
@@ -810,6 +810,22 @@
"shareAccessGuide": "مشاركة دليل الوصول",
"nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة",
"multipleCities": "مدن متعددة",
+ "filters": "الفلاتر",
+ "availability": "التوفر",
+ "chooseOneOrBoth": "اختر واحدًا أو كليهما",
+ "sortBy": "ترتيب حسب",
+ "recommended": "موصى به",
+ "nearest": "الأقرب",
+ "highestDiscount": "أعلى خصم",
+ "newest": "الأحدث",
+ "distance": "المسافة",
+ "fromYourCurrentLocation": "من موقعك الحالي",
+ "any": "الكل",
+ "minimumDiscount": "الحد الأدنى للخصم",
+ "reset": "إعادة تعيين",
+ "applyFilters": "تطبيق الفلاتر",
+ "distanceKm": "{} كم",
+ "discountPercentPlus": "{}%+",
"visitMerchantWebsite": "زيارة موقع التاجر",
"merchantWebsiteNotAvailable": "موقع التاجر غير متوفر"
}
diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json
index 85804db..2a6c0d7 100644
--- a/assets/langs/en-US.json
+++ b/assets/langs/en-US.json
@@ -810,6 +810,22 @@
"shareAccessGuide": "Share Access Guide",
"nationalIdOrIqamaNumber": "National ID or Iqama Number",
"multipleCities": "Multiple Cities",
+ "filters": "Filters",
+ "availability": "Availability",
+ "chooseOneOrBoth": "Choose one or both",
+ "sortBy": "Sort by",
+ "recommended": "Recommended",
+ "nearest": "Nearest",
+ "highestDiscount": "Highest discount",
+ "newest": "Newest",
+ "distance": "Distance",
+ "fromYourCurrentLocation": "From your current location",
+ "any": "Any",
+ "minimumDiscount": "Minimum discount",
+ "reset": "Reset",
+ "applyFilters": "Apply filters",
+ "distanceKm": "{} km",
+ "discountPercentPlus": "{}%+",
"visitMerchantWebsite": "Visit Merchant Website",
"merchantWebsiteNotAvailable": "Merchant website not available"
}
diff --git a/lib/api/offers_and_discounts_api_client.dart b/lib/api/offers_and_discounts_api_client.dart
index a4ce933..c904196 100644
--- a/lib/api/offers_and_discounts_api_client.dart
+++ b/lib/api/offers_and_discounts_api_client.dart
@@ -181,7 +181,9 @@ class OffersAndDiscountsApiClient {
map.removeWhere((key, value) => value == null);
}
- Future> getWalaPlusCategories({BuildContext? context, String? mobileNumber}) async {
+ // Pass parentId to fetch the subcategories under a given main category (WP_ParentId) -
+ // omit it (the default) to fetch the top-level main categories, same as before.
+ Future> getWalaPlusCategories({BuildContext? context, String? mobileNumber, String? parentId}) async {
String url = ApiConsts.walaPlusCategories;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
@@ -196,6 +198,9 @@ class OffersAndDiscountsApiClient {
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
+ if (parentId != null && parentId.trim().isNotEmpty) {
+ postParams["WP_Parent_Id"] = parentId.trim();
+ }
_cleanWalaPlusParams(postParams);
return await ApiClient().postJsonForObject(
diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart
index ed29815..1fb1db4 100644
--- a/lib/classes/consts.dart
+++ b/lib/classes/consts.dart
@@ -4,12 +4,12 @@ class ApiConsts {
// static String baseUrl = "http://10.201.214.101:2027"; // Local server
// static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server
- // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
+ static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "http://10.201.204.101:2024";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://hmgwebservices.com"; // Live server
- static String baseUrl = "https://mohemm.hmg.com"; // New Live server
+ // static String baseUrl = "https://mohemm.hmg.com"; // New Live server
static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server
//
// static String baseUrl = "https://uat.hmgwebservices.com"; //
diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart
index 5b30deb..dd8c8f0 100644
--- a/lib/generated/codegen_loader.g.dart
+++ b/lib/generated/codegen_loader.g.dart
@@ -821,7 +821,23 @@ class CodegenLoader extends AssetLoader{
"microphoneAccessRequired": "يحتاج التطبيق إلى إذن الوصول إلى الميكروفون لتسجيل الصوت.",
"shareAccessGuide": "مشاركة دليل الوصول",
"nationalIdOrIqamaNumber": "رقم الهوية الوطنية أو الإقامة",
- "multipleCities": "مدن متعددة"
+ "multipleCities": "مدن متعددة",
+ "filters": "الفلاتر",
+ "availability": "التوفر",
+ "chooseOneOrBoth": "اختر واحدًا أو كليهما",
+ "sortBy": "ترتيب حسب",
+ "recommended": "موصى به",
+ "nearest": "الأقرب",
+ "highestDiscount": "أعلى خصم",
+ "newest": "الأحدث",
+ "distance": "المسافة",
+ "fromYourCurrentLocation": "من موقعك الحالي",
+ "any": "الكل",
+ "minimumDiscount": "الحد الأدنى للخصم",
+ "reset": "إعادة تعيين",
+ "applyFilters": "تطبيق الفلاتر",
+ "distanceKm": "{} كم",
+ "discountPercentPlus": "{}%+"
};
static const Map _en_US = {
"mohemm": "Mohemm",
@@ -1630,7 +1646,23 @@ static const Map _en_US = {
"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",
+ "filters": "Filters",
+ "availability": "Availability",
+ "chooseOneOrBoth": "Choose one or both",
+ "sortBy": "Sort by",
+ "recommended": "Recommended",
+ "nearest": "Nearest",
+ "highestDiscount": "Highest discount",
+ "newest": "Newest",
+ "distance": "Distance",
+ "fromYourCurrentLocation": "From your current location",
+ "any": "Any",
+ "minimumDiscount": "Minimum discount",
+ "reset": "Reset",
+ "applyFilters": "Apply filters",
+ "distanceKm": "{} km",
+ "discountPercentPlus": "{}%+"
};
static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
}
diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart
index c06f3ab..b1d108e 100644
--- a/lib/generated/locale_keys.g.dart
+++ b/lib/generated/locale_keys.g.dart
@@ -2,7 +2,7 @@
// ignore_for_file: constant_identifier_names
-abstract class LocaleKeys {
+abstract class LocaleKeys {
static const mohemm = 'mohemm';
static const english = 'english';
static const arabic = 'arabic';
@@ -258,7 +258,7 @@ abstract class LocaleKeys {
static const Payroll = 'Payroll';
static const civilIdentityNumber = 'civilIdentityNumber';
static const dateOfBirth = 'dateOfBirth';
- static const maritalStatus = 'maritalStatus ';
+ static const maritalStatus = 'maritalStatus ';
static const fullName = 'fullName';
static const remove = 'remove';
static const submit = 'submit';
@@ -289,7 +289,7 @@ abstract class LocaleKeys {
static const enterNewInfo = 'enterNewInfo';
static const endDate = 'endDate';
static const removeThisMember = 'removeThisMember';
- static const wantUpdateThisMember = 'wantUpdateThisMember ';
+ static const wantUpdateThisMember = 'wantUpdateThisMember ';
static const addNewFamilyMember = 'addNewFamilyMember';
static const addRow = 'addRow';
static const pleaseSelect = 'pleaseSelect';
@@ -794,5 +794,20 @@ abstract class LocaleKeys {
static const shareAccessGuide = 'shareAccessGuide';
static const nationalIdOrIqamaNumber = 'nationalIdOrIqamaNumber';
static const multipleCities = 'multipleCities';
+ static const filters = 'filters';
+ static const availability = 'availability';
+ static const chooseOneOrBoth = 'chooseOneOrBoth';
+ static const sortBy = 'sortBy';
+ static const recommended = 'recommended';
+ static const nearest = 'nearest';
+ static const highestDiscount = 'highestDiscount';
+ static const newest = 'newest';
+ static const distance = 'distance';
+ static const fromYourCurrentLocation = 'fromYourCurrentLocation';
+ static const any = 'any';
+ static const minimumDiscount = 'minimumDiscount';
+ static const reset = 'reset';
+ static const applyFilters = 'applyFilters';
+ static const distanceKm = 'distanceKm';
+ static const discountPercentPlus = 'discountPercentPlus';
}
-
diff --git a/lib/models/offers_and_discounts/wala_plus_retailer_model.dart b/lib/models/offers_and_discounts/wala_plus_retailer_model.dart
index 0c9e7c1..7760b1c 100644
--- a/lib/models/offers_and_discounts/wala_plus_retailer_model.dart
+++ b/lib/models/offers_and_discounts/wala_plus_retailer_model.dart
@@ -557,7 +557,6 @@ class WalaPlusRetailerModel {
// Multiple distinct cities across this retailer's locations -> "Multiple Cities" instead of
// just picking the first one found, so the badge doesn't silently hide the other branches.
String getDisplayCity({bool isAr = false}) {
- log("locations: ${locations?.map((e) => e.toJson()).toList()}");
if (locations == null || locations!.isEmpty) return '';
Set distinctCities = {};
diff --git a/lib/ui/landing/dashboard_screen_for_dependet.dart b/lib/ui/landing/dashboard_screen_for_dependet.dart
index cdeceaf..3943407 100644
--- a/lib/ui/landing/dashboard_screen_for_dependet.dart
+++ b/lib/ui/landing/dashboard_screen_for_dependet.dart
@@ -18,6 +18,7 @@ import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/models/post_params_model.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
+import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:mohem_flutter_app/widgets/info_chip_widget.dart';
import 'package:shimmer/shimmer.dart';
@@ -34,6 +35,22 @@ class _DashboardScreenForDependetState extends State
List categoriesList = [];
String currentCategoryId = "";
String selectedTypeFilter = "all";
+
+ // Subcategories of whichever main category is currently selected - fetched via
+ // getWalaPlusCategories(parentId: ...) whenever a main category card is tapped. Empty/cleared
+ // when "All Categories" (or nothing) is selected, since there's no specific parent to ask for.
+ List subcategoriesList = [];
+ bool isSubcategoriesLoading = false;
+ String? _selectedSubcategoryId;
+
+ // Filters bottom sheet state. Availability is single-select and functional - it drives the
+ // same selectedTypeFilter/offerType param the old chip row used. Sort/Distance/Minimum
+ // discount still have no backend support, so those remain UI-only for now. Committed on
+ // "Apply filters"; sort/distance/discount are persisted here so reopening the sheet shows
+ // the last-applied selection (availability's source of truth is selectedTypeFilter itself).
+ String _filterSortBy = 'nearest';
+ String _filterDistance = '5km';
+ String _filterMinDiscount = 'any';
bool isNearestOffers = true;
bool isLoading = true;
bool isLoadingMore = false;
@@ -49,23 +66,6 @@ class _DashboardScreenForDependetState extends State
final ScrollController _scrollController = ScrollController();
bool _hasRegisteredMobileLoginInfo = false;
- List get _availableTypeFilters {
- Set types = {};
- for (WalaPlusRetailerModel r in retailersList) {
- String? t = r.normalizedType;
- if (t != null && t.isNotEmpty) {
- types.add(t);
- }
- }
- List list = ['all', 'instore', 'online'];
- for (String t in types) {
- if (!list.contains(t)) {
- list.add(t);
- }
- }
- return list;
- }
-
String _getTypeFilterTitle(String type) {
if (type == 'all') {
return LocaleKeys.allCategories.tr();
@@ -239,6 +239,45 @@ class _DashboardScreenForDependetState extends State
}
}
+ // Fetches the subcategories under a main category (WP_ParentId). "All Categories" (or no
+ // category) has no specific parent, so that just clears the list instead of calling the API.
+ Future fetchSubcategories(String? parentId) async {
+ if (parentId == null || parentId.isEmpty || parentId == "all") {
+ setState(() {
+ subcategoriesList = [];
+ _selectedSubcategoryId = null;
+ });
+ return;
+ }
+
+ setState(() {
+ isSubcategoriesLoading = true;
+ });
+
+ try {
+ List list = await OffersAndDiscountsApiClient().getWalaPlusCategories(context: context, parentId: parentId);
+ if (mounted) {
+ setState(() {
+ subcategoriesList = list;
+ _selectedSubcategoryId = null;
+ });
+ }
+ } catch (e) {
+ debugPrint("Error fetching WalaPlus subcategories: $e");
+ if (mounted) {
+ setState(() {
+ subcategoriesList = [];
+ });
+ }
+ } finally {
+ if (mounted) {
+ setState(() {
+ isSubcategoriesLoading = false;
+ });
+ }
+ }
+ }
+
Future fetchOffers({String? categoryId, String? query}) async {
String targetCategoryId = categoryId ?? (currentCategoryId.isEmpty ? "all" : currentCategoryId);
if (query != null) {
@@ -592,7 +631,7 @@ class _DashboardScreenForDependetState extends State
if (categoriesList.isNotEmpty) _buildCategoriesList(isAr) else _buildCategoriesShimmer(),
// Type Filter Chips (All, In Store, Online)
- _buildTypeFilterChips(isAr),
+ _buildFiltersRow(isAr),
const SizedBox(height: 16),
@@ -711,6 +750,7 @@ class _DashboardScreenForDependetState extends State
currentCategoryId = item.id ?? "";
});
fetchOffers(categoryId: currentCategoryId);
+ fetchSubcategories(currentCategoryId);
}
},
child: SizedBox(
@@ -736,54 +776,280 @@ class _DashboardScreenForDependetState extends State
);
}
- Widget _buildTypeFilterChips(bool isAr) {
- List types = _availableTypeFilters;
+ // Replaces the old All/In Store/Online chip row: a fixed "Filters" chip (opens the filters
+ // bottom sheet) + a divider, then a horizontally scrollable row of subcategory chips fetched
+ // from the API for whichever main category is currently selected (see fetchSubcategories).
+ Widget _buildFiltersRow(bool isAr) {
+ // "All Categories" (or nothing selected) has no subcategories to show at all - only a
+ // specific main category, once it has actually loaded some, gets the divider + chip row.
+ bool showSubcategoriesRow = currentCategoryId.isNotEmpty && currentCategoryId != "all" && (isSubcategoriesLoading || subcategoriesList.isNotEmpty);
return Padding(
padding: const EdgeInsets.only(top: 14, bottom: 2),
child: SizedBox(
height: 38,
- child: ListView.separated(
- scrollDirection: Axis.horizontal,
- physics: const BouncingScrollPhysics(),
- itemCount: types.length,
- separatorBuilder: (BuildContext _, int __) => const SizedBox(width: 8),
- itemBuilder: (BuildContext context, int index) {
- String typeKey = types[index];
- bool isSelected = selectedTypeFilter == typeKey;
- String title = _getTypeFilterTitle(typeKey);
- IconData icon = _getTypeFilterIcon(typeKey);
-
- return InkWell(
+ child: Row(
+ children: [
+ InkWell(
borderRadius: BorderRadius.circular(6),
- onTap: () {
- if (selectedTypeFilter != typeKey) {
- setState(() {
- selectedTypeFilter = typeKey;
- });
- fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId);
- }
- },
+ onTap: _showFiltersBottomSheet,
child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
alignment: Alignment.center,
- decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), color: isSelected ? MyColors.darkIconColor : MyColors.lightGreyEAColor),
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), color: MyColors.lightGreyEAColor),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
- Icon(icon, size: 14, color: isSelected ? MyColors.white : MyColors.black),
+ SvgPicture.asset("assets/icons/filter_icon.svg", width: 14, height: 14, colorFilter: const ColorFilter.mode(MyColors.black, BlendMode.srcIn)),
const SizedBox(width: 5),
- title.toText12(color: isSelected ? MyColors.white : MyColors.black),
+ LocaleKeys.filters.tr().toText12(color: MyColors.black),
],
),
),
- );
- },
+ ),
+ if (showSubcategoriesRow) ...[
+ Container(margin: const EdgeInsets.symmetric(horizontal: 10), width: 1, height: 20, color: const Color(0xFFE2E8F0)),
+ Expanded(
+ child:
+ isSubcategoriesLoading
+ ? const Center(child: SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF94A3B8))))
+ : ListView.separated(
+ scrollDirection: Axis.horizontal,
+ physics: const BouncingScrollPhysics(),
+ // +1 for the leading "All" chip (clears the subcategory selection,
+ // falling back to just the parent main category).
+ itemCount: subcategoriesList.length + 1,
+ separatorBuilder: (BuildContext _, int __) => const SizedBox(width: 8),
+ itemBuilder: (BuildContext context, int index) {
+ bool isAllChip = index == 0;
+ WalaPlusCategoryModel? subcategory = isAllChip ? null : subcategoriesList[index - 1];
+ bool isSelected = isAllChip ? _selectedSubcategoryId == null : _selectedSubcategoryId == subcategory!.id;
+ String label = isAllChip ? LocaleKeys.allCategories.tr() : (subcategory!.name ?? "");
+
+ return InkWell(
+ borderRadius: BorderRadius.circular(6),
+ onTap: () {
+ setState(() {
+ _selectedSubcategoryId = isAllChip ? null : (isSelected ? null : subcategory!.id);
+ });
+ // Selecting a subcategory filters offers to just that subcategory;
+ // "All" (or deselecting) falls back to the parent main category.
+ fetchOffers(categoryId: _selectedSubcategoryId ?? currentCategoryId);
+ },
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ alignment: Alignment.center,
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), color: isSelected ? MyColors.darkIconColor : MyColors.lightGreyEAColor),
+ child: label.toText12(color: isSelected ? MyColors.white : MyColors.black),
+ ),
+ );
+ },
+ ),
+ ),
+ ],
+ ],
),
),
);
}
+ // Matches the shared Filters bottom sheet design. Availability is single-select and
+ // functional, driving selectedTypeFilter/offerType exactly like the old chip row did.
+ // Sort/Distance/Minimum discount still have no backend support, so those remain UI-only.
+ void _showFiltersBottomSheet() {
+ String draftAvailability = selectedTypeFilter;
+ String draftSortBy = _filterSortBy;
+ String draftDistance = _filterDistance;
+ String draftMinDiscount = _filterMinDiscount;
+
+ const Color darkBg = Color(0xFF202B33);
+ const Color borderColor = Color(0xFFE2E8F0);
+
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ backgroundColor: Colors.transparent,
+ builder: (BuildContext sheetContext) {
+ return StatefulBuilder(
+ builder: (BuildContext context, StateSetter setSheetState) {
+ Widget buildOption(String label, bool isSelected, VoidCallback onTap, {IconData? icon}) {
+ return Expanded(
+ child: InkWell(
+ borderRadius: BorderRadius.circular(8),
+ onTap: onTap,
+ child: Container(
+ height: 44,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ color: isSelected ? darkBg : Colors.white,
+ border: Border.all(color: isSelected ? darkBg : borderColor),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (icon != null) ...[Icon(icon, size: 14, color: isSelected ? Colors.white : const Color(0xFF64748B)), const SizedBox(width: 5)],
+ Flexible(child: label.toText13(isBold: true, color: isSelected ? Colors.white : darkBg, maxlines: 1)),
+ if (isSelected) ...[const SizedBox(width: 5), const Icon(Icons.check, size: 14, color: Colors.white)],
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget sectionHeader(String title, {String? hint}) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ title.toText14(isBold: true, color: darkBg),
+ if (hint != null) hint.toText12(color: const Color(0xFF94A3B8)),
+ ],
+ );
+ }
+
+ return SafeArea(
+ top: false,
+ child: Container(
+ padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
+ decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24))),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Center(
+ child: Container(width: 44, height: 4.5, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration(color: const Color(0xFFCBD5E1), borderRadius: BorderRadius.circular(2.5))),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ LocaleKeys.filters.tr().toText20(isBold: true, color: darkBg),
+ InkWell(
+ borderRadius: BorderRadius.circular(16),
+ onTap: () => Navigator.pop(sheetContext),
+ child: Container(
+ width: 32,
+ height: 32,
+ alignment: Alignment.center,
+ decoration: const BoxDecoration(shape: BoxShape.circle, color: Color(0xFFF1F5F9)),
+ child: const Icon(Icons.close, size: 18, color: darkBg),
+ ),
+ ),
+ ],
+ ),
+ 20.height,
+
+ // Availability - single select, drives the real offerType filter.
+ LocaleKeys.availability.tr().toText14(isBold: true, color: darkBg),
+ 10.height,
+ Row(
+ children: [
+ buildOption(LocaleKeys.allCategories.tr(), draftAvailability == 'all', () => setSheetState(() => draftAvailability = 'all')),
+ const SizedBox(width: 10),
+ buildOption(LocaleKeys.online.tr(), draftAvailability == 'online', () => setSheetState(() => draftAvailability = 'online')),
+ const SizedBox(width: 10),
+ buildOption(LocaleKeys.inStore.tr(), draftAvailability == 'instore', () => setSheetState(() => draftAvailability = 'instore')),
+ ],
+ ),
+ 20.height,
+
+ // Sort by - single select, Nearest and Newest only.
+ LocaleKeys.sortBy.tr().toText14(isBold: true, color: darkBg),
+ 10.height,
+ Row(
+ children: [
+ buildOption(LocaleKeys.nearest.tr(), draftSortBy == 'nearest', () => setSheetState(() => draftSortBy = 'nearest'), icon: Icons.location_on_outlined),
+ const SizedBox(width: 10),
+ buildOption(LocaleKeys.newest.tr(), draftSortBy == 'newest', () => setSheetState(() => draftSortBy = 'newest')),
+ ],
+ ),
+ 20.height,
+
+ // Distance - single select.
+ sectionHeader(LocaleKeys.distance.tr(), hint: LocaleKeys.fromYourCurrentLocation.tr()),
+ 10.height,
+ Row(
+ children: [
+ buildOption(LocaleKeys.any.tr(), draftDistance == 'any', () => setSheetState(() => draftDistance = 'any')),
+ const SizedBox(width: 8),
+ buildOption(LocaleKeys.distanceKm.tr(args: ['5']), draftDistance == '5km', () => setSheetState(() => draftDistance = '5km')),
+ const SizedBox(width: 8),
+ buildOption(LocaleKeys.distanceKm.tr(args: ['10']), draftDistance == '10km', () => setSheetState(() => draftDistance = '10km')),
+ const SizedBox(width: 8),
+ buildOption(LocaleKeys.distanceKm.tr(args: ['25']), draftDistance == '25km', () => setSheetState(() => draftDistance = '25km')),
+ ],
+ ),
+ 20.height,
+
+ // Minimum discount - single select.
+ LocaleKeys.minimumDiscount.tr().toText14(isBold: true, color: darkBg),
+ 10.height,
+ Row(
+ children: [
+ buildOption(LocaleKeys.any.tr(), draftMinDiscount == 'any', () => setSheetState(() => draftMinDiscount = 'any')),
+ const SizedBox(width: 8),
+ buildOption(LocaleKeys.discountPercentPlus.tr(args: ['10']), draftMinDiscount == '10', () => setSheetState(() => draftMinDiscount = '10')),
+ const SizedBox(width: 8),
+ buildOption(LocaleKeys.discountPercentPlus.tr(args: ['20']), draftMinDiscount == '20', () => setSheetState(() => draftMinDiscount = '20')),
+ const SizedBox(width: 8),
+ buildOption(LocaleKeys.discountPercentPlus.tr(args: ['30']), draftMinDiscount == '30', () => setSheetState(() => draftMinDiscount = '30')),
+ ],
+ ),
+ 20.height,
+ const Divider(height: 1, thickness: 1, color: borderColor),
+ 16.height,
+
+ Row(
+ children: [
+ Expanded(
+ child: DefaultButton(
+ LocaleKeys.reset.tr(),
+ () {
+ setSheetState(() {
+ draftAvailability = 'all';
+ draftSortBy = 'newest';
+ draftDistance = 'any';
+ draftMinDiscount = 'any';
+ });
+ setState(() {
+ selectedTypeFilter = draftAvailability;
+ _filterSortBy = draftSortBy;
+ _filterDistance = draftDistance;
+ _filterMinDiscount = draftMinDiscount;
+ });
+ Navigator.pop(sheetContext);
+ fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId);
+ },
+ colors: const [Colors.white, Colors.white],
+ border: Border.all(color: borderColor),
+ textColor: darkBg,
+ ),
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: DefaultButton(LocaleKeys.applyFilters.tr(), () {
+ setState(() {
+ selectedTypeFilter = draftAvailability;
+ _filterSortBy = draftSortBy;
+ _filterDistance = draftDistance;
+ _filterMinDiscount = draftMinDiscount;
+ });
+ Navigator.pop(sheetContext);
+ fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId);
+ }),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ },
+ );
+ }
+
Widget _buildCategoryCard(WalaPlusCategoryModel item, bool isSelected) {
const double cardSize = 84.0;
const double borderRadiusVal = 18.0;
diff --git a/lib/ui/landing/wala_plus_retailer_details_screen.dart b/lib/ui/landing/wala_plus_retailer_details_screen.dart
index 7000b24..88ec533 100644
--- a/lib/ui/landing/wala_plus_retailer_details_screen.dart
+++ b/lib/ui/landing/wala_plus_retailer_details_screen.dart
@@ -22,6 +22,7 @@ import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dar
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:mohem_flutter_app/widgets/info_chip_widget.dart';
import 'package:permission_handler/permission_handler.dart';
+import 'package:shimmer/shimmer.dart';
import 'package:url_launcher/url_launcher.dart';
class WalaPlusRetailerDetailsScreen extends StatefulWidget {
@@ -173,10 +174,7 @@ class _WalaPlusRetailerDetailsScreenState extends State AppInAppWebViewScreen(
- url: url.startsWith("http") ? url : "https://$url",
- title: _retailer?.name ?? LocaleKeys.visitMerchantWebsite.tr(),
- ),
+ builder: (BuildContext context) => AppInAppWebViewScreen(url: url.startsWith("http") ? url : "https://$url", title: _retailer?.name ?? LocaleKeys.visitMerchantWebsite.tr()),
),
);
} catch (err) {
@@ -521,7 +519,7 @@ class _WalaPlusRetailerDetailsScreenState extends State(Color(0xFF168A89))),
@@ -575,17 +573,12 @@ class _WalaPlusRetailerDetailsScreenState extends State[
+ Container(
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Banner
+ block(width: double.infinity, height: 190, radius: 16),
+ 14.height,
+ // Brand strip
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(children: [block(width: 36, height: 36, radius: 18), const SizedBox(width: 10), block(width: 90, height: 14)]),
+ block(width: 48, height: 48, radius: 10),
+ ],
+ ),
+ 18.height,
+ // Retailer name
+ block(width: 180, height: 20),
+ 18.height,
+ // Info badges row
+ Row(children: [block(width: 90, height: 28, radius: 8), const SizedBox(width: 8), block(width: 70, height: 28, radius: 8)]),
+ ],
+ ),
+ ),
+ 16.height,
+ // Locations section placeholder
+ Container(
+ width: double.infinity,
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [block(width: 120, height: 16), 14.height, block(width: double.infinity, height: 56, radius: 12), 10.height, block(width: double.infinity, height: 56, radius: 12)],
+ ),
+ ),
+ 16.height,
+ // Contact section placeholder
+ Container(
+ width: double.infinity,
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ block(width: 120, height: 16),
+ 14.height,
+ Row(children: [block(width: 32, height: 32, radius: 16), const SizedBox(width: 10), block(width: 160, height: 14)]),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
Widget _buildBrandStrip(bool isAr) {
String? logoUrl = _retailer?.displayLogo;
return Row(
@@ -711,11 +774,10 @@ class _WalaPlusRetailerDetailsScreenState extends State