diff --git a/assets/icons/filter_icon.svg b/assets/icons/filter_icon.svg new file mode 100644 index 00000000..72042c3f --- /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 20eb8386..dc656598 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 85804dba..2a6c0d7b 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 a4ce9333..c9041967 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 ed298155..1fb1db42 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 5b30deb1..dd8c8f0c 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 00a4096c..cf784ceb 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,7 +794,22 @@ 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'; static const visitMerchantWebsite = 'visitMerchantWebsite'; static const merchantWebsiteNotAvailable = 'merchantWebsiteNotAvailable'; } - 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 0c9e7c1c..7760b1c8 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 cdeceaf0..39434077 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 7000b244..88ec533e 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