|
|
|
|
@ -23,10 +23,6 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
|
|
|
|
|
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
|
|
|
|
|
import 'package:shimmer/shimmer.dart';
|
|
|
|
|
|
|
|
|
|
// A single, fixed-category brand-list section (Travel Essentials, Home & Kitchen, ...). Swapping
|
|
|
|
|
// which category a section shows is a one-line change - just the categoryId passed to the
|
|
|
|
|
// constructor where the section is declared - since the fetch, the title resolution, and the UI
|
|
|
|
|
// binding all read off this one object instead of each having their own copy of the id.
|
|
|
|
|
class _MazayaCategorySection {
|
|
|
|
|
final String categoryId;
|
|
|
|
|
bool isLoading = true;
|
|
|
|
|
@ -39,11 +35,7 @@ class _MazayaCategorySection {
|
|
|
|
|
_MazayaCategorySection(this.categoryId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mazaya Discover screen (Figma node 890:2050, "Mazaya Deals - Curated for you"). Reached from
|
|
|
|
|
// the Mazaya banner on the main dashboard. Most sections are wired to the WalaPlus APIs
|
|
|
|
|
// (categories + per-category retailers). "Exclusive offers" instead reuses the legacy Offers &
|
|
|
|
|
// Discounts (ITG) API/implementation (lib/ui/screens/offers_and_discounts) - see
|
|
|
|
|
// _fetchExclusiveOffers and WalaPlusRetailerModel.fromExclusiveOffer.
|
|
|
|
|
|
|
|
|
|
class MazayaDiscoverScreen extends StatefulWidget {
|
|
|
|
|
const MazayaDiscoverScreen({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
@ -67,8 +59,8 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
// Fixed-category sections - to point a section at a different category, change only the id
|
|
|
|
|
// passed here. Everything else (fetch, title, UI) reads off the _MazayaCategorySection object.
|
|
|
|
|
final _MazayaCategorySection _travelSection = _MazayaCategorySection('p03');
|
|
|
|
|
final _MazayaCategorySection _homeKitchenSection = _MazayaCategorySection(
|
|
|
|
|
'Bw',
|
|
|
|
|
final _MazayaCategorySection _foodAndDrinksSection = _MazayaCategorySection(
|
|
|
|
|
'bZa',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
@ -99,7 +91,7 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
if (mounted) {
|
|
|
|
|
_fetchLatestOnMazaya();
|
|
|
|
|
_fetchCategorySection(_travelSection);
|
|
|
|
|
_fetchCategorySection(_homeKitchenSection);
|
|
|
|
|
_fetchCategorySection(_foodAndDrinksSection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -131,11 +123,6 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetches one fixed-category section (Travel Essentials, Home & Kitchen, ...) by whichever
|
|
|
|
|
// categoryId is set on the given _MazayaCategorySection. Title data comes from the first
|
|
|
|
|
// matching entry in each returned retailer's own embedded `categories` list (each retailer
|
|
|
|
|
// carries its full category info straight from the API) - used as a fallback by _sectionTitle
|
|
|
|
|
// when the id isn't found in the top-level categories list.
|
|
|
|
|
Future<void> _fetchCategorySection(_MazayaCategorySection section) async {
|
|
|
|
|
setState(() => section.isLoading = true);
|
|
|
|
|
try {
|
|
|
|
|
@ -174,11 +161,6 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "Latest on Mazaya" = newest retailers across all categories (WP_Sort_By=newest, WP_CategoryID
|
|
|
|
|
// omitted entirely - not just empty) - same WP_Sort_By param already wired for the main
|
|
|
|
|
// retailers grid's Sort by filter. No category filter here on purpose: the Figma mock itself
|
|
|
|
|
// mixes categories in this section (a logistics brand next to a food brand), confirming it's a
|
|
|
|
|
// cross-category feed.
|
|
|
|
|
Future<void> _fetchLatestOnMazaya() async {
|
|
|
|
|
setState(() => _isLoadingLatest = true);
|
|
|
|
|
try {
|
|
|
|
|
@ -202,11 +184,6 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolved live rather than stored once, since the two sources load independently and either
|
|
|
|
|
// can arrive first: the top-level categories list (the primary/most reliable source - present
|
|
|
|
|
// even if the section's category currently has zero retailers) and that category's own data
|
|
|
|
|
// embedded in each retailer (kept as a fallback for when the id isn't in the top-level list).
|
|
|
|
|
// fallbackTitle only shows while neither source has resolved a name yet.
|
|
|
|
|
String _sectionTitle(_MazayaCategorySection section, String fallbackTitle) {
|
|
|
|
|
bool isAr = mounted ? AppState().isArabic(context) : false;
|
|
|
|
|
for (WalaPlusCategoryModel c in _categories) {
|
|
|
|
|
@ -225,9 +202,6 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
return fallbackTitle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same id-first/resolvedName-fallback lookup as _sectionTitle, but returns the full category
|
|
|
|
|
// model so "See all" can hand RetailersListScreen the actual category to pre-select (it re-syncs
|
|
|
|
|
// this against its own category list once that loads, so an id match is all that's required).
|
|
|
|
|
WalaPlusCategoryModel _categoryForSection(
|
|
|
|
|
_MazayaCategorySection section,
|
|
|
|
|
String fallbackTitle,
|
|
|
|
|
@ -241,11 +215,6 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "Exclusive offers" reuses the legacy Offers & Discounts (ITG) API/implementation
|
|
|
|
|
// (lib/ui/screens/offers_and_discounts) rather than a WalaPlus endpoint - categoryID 0 is the
|
|
|
|
|
// same "all categories" value that screen's own initial load uses. Each result is mapped onto
|
|
|
|
|
// WalaPlusRetailerModel (see fromExclusiveOffer) so it renders through the same card/grid/detail
|
|
|
|
|
// screen as every other Mazaya section, flagged via isExclusive.
|
|
|
|
|
Future<void> _fetchExclusiveOffers() async {
|
|
|
|
|
setState(() => _isLoadingExclusiveOffers = true);
|
|
|
|
|
try {
|
|
|
|
|
@ -305,7 +274,7 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
setState(() => selectedCity = AppState().selectedMazayaCity);
|
|
|
|
|
_fetchLatestOnMazaya();
|
|
|
|
|
_fetchCategorySection(_travelSection);
|
|
|
|
|
_fetchCategorySection(_homeKitchenSection);
|
|
|
|
|
_fetchCategorySection(_foodAndDrinksSection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -325,7 +294,7 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
setState(() => selectedCity = AppState().selectedMazayaCity);
|
|
|
|
|
_fetchLatestOnMazaya();
|
|
|
|
|
_fetchCategorySection(_travelSection);
|
|
|
|
|
_fetchCategorySection(_homeKitchenSection);
|
|
|
|
|
_fetchCategorySection(_foodAndDrinksSection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -349,7 +318,7 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
setState(() => selectedCity = AppState().selectedMazayaCity);
|
|
|
|
|
_fetchLatestOnMazaya();
|
|
|
|
|
_fetchCategorySection(_travelSection);
|
|
|
|
|
_fetchCategorySection(_homeKitchenSection);
|
|
|
|
|
_fetchCategorySection(_foodAndDrinksSection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -358,11 +327,43 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
_fetchCategories(),
|
|
|
|
|
_fetchLatestOnMazaya(),
|
|
|
|
|
_fetchCategorySection(_travelSection),
|
|
|
|
|
_fetchCategorySection(_homeKitchenSection),
|
|
|
|
|
_fetchCategorySection(_foodAndDrinksSection),
|
|
|
|
|
_fetchExclusiveOffers(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _openCitySelector() {
|
|
|
|
|
showMazayaCitySelectionBottomSheet(
|
|
|
|
|
context,
|
|
|
|
|
selectedCityId: selectedCity?.cityId,
|
|
|
|
|
onCitySelected: (city) {
|
|
|
|
|
WalaPlusCityModel? newCity = city.isAllCities ? null : city;
|
|
|
|
|
AppState().setSelectedMazayaCity = newCity;
|
|
|
|
|
if (newCity != null) {
|
|
|
|
|
Utils.saveStringFromPrefs(
|
|
|
|
|
"MAZAYA_SELECTED_CITY",
|
|
|
|
|
jsonEncode(newCity.toJson()),
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
Utils.removeStringFromPrefs("MAZAYA_SELECTED_CITY");
|
|
|
|
|
}
|
|
|
|
|
setState(() => selectedCity = newCity);
|
|
|
|
|
_fetchLatestOnMazaya();
|
|
|
|
|
_fetchCategorySection(_travelSection);
|
|
|
|
|
_fetchCategorySection(_foodAndDrinksSection);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _resetToAllCities() {
|
|
|
|
|
AppState().setSelectedMazayaCity = null;
|
|
|
|
|
Utils.removeStringFromPrefs("MAZAYA_SELECTED_CITY");
|
|
|
|
|
setState(() => selectedCity = null);
|
|
|
|
|
_fetchLatestOnMazaya();
|
|
|
|
|
_fetchCategorySection(_travelSection);
|
|
|
|
|
_fetchCategorySection(_foodAndDrinksSection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _clearDependentPrefs() async {
|
|
|
|
|
await Utils.removeStringFromPrefs("P_DEP_IS_LOGGED_IN");
|
|
|
|
|
await Utils.removeStringFromPrefs("P_DEP_NATIONAL_ID");
|
|
|
|
|
@ -531,39 +532,40 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
_buildBrandSection(
|
|
|
|
|
title: _sectionTitle(
|
|
|
|
|
_travelSection,
|
|
|
|
|
LocaleKeys.carServices.tr(),
|
|
|
|
|
_foodAndDrinksSection,
|
|
|
|
|
LocaleKeys.homeAndKitchen.tr(),
|
|
|
|
|
),
|
|
|
|
|
subtitle: LocaleKeys.fromEverydayCareToYourNextAdventure.tr(),
|
|
|
|
|
isLoading: _travelSection.isLoading,
|
|
|
|
|
retailers: _travelSection.retailers,
|
|
|
|
|
backgroundColor: const Color(0xFFEFF2EF),
|
|
|
|
|
subtitle: LocaleKeys.curatedPicksForYourFood.tr(),
|
|
|
|
|
isLoading: _foodAndDrinksSection.isLoading,
|
|
|
|
|
retailers: _foodAndDrinksSection.retailers,
|
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
|
onSeeAll:
|
|
|
|
|
() => _openRetailersGrid(
|
|
|
|
|
category: _categoryForSection(
|
|
|
|
|
_travelSection,
|
|
|
|
|
LocaleKeys.carServices.tr(),
|
|
|
|
|
_foodAndDrinksSection,
|
|
|
|
|
LocaleKeys.homeAndKitchen.tr(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
_buildBrandSection(
|
|
|
|
|
title: _sectionTitle(
|
|
|
|
|
_homeKitchenSection,
|
|
|
|
|
LocaleKeys.homeAndKitchen.tr(),
|
|
|
|
|
_travelSection,
|
|
|
|
|
LocaleKeys.carServices.tr(),
|
|
|
|
|
),
|
|
|
|
|
subtitle: LocaleKeys.curatedPicksForYourHomeAndKitchen.tr(),
|
|
|
|
|
isLoading: _homeKitchenSection.isLoading,
|
|
|
|
|
retailers: _homeKitchenSection.retailers,
|
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
|
subtitle: LocaleKeys.fromEverydayCareToYourNextAdventure.tr(),
|
|
|
|
|
isLoading: _travelSection.isLoading,
|
|
|
|
|
retailers: _travelSection.retailers,
|
|
|
|
|
backgroundColor: const Color(0xFFEFF2EF),
|
|
|
|
|
onSeeAll:
|
|
|
|
|
() => _openRetailersGrid(
|
|
|
|
|
category: _categoryForSection(
|
|
|
|
|
_homeKitchenSection,
|
|
|
|
|
LocaleKeys.homeAndKitchen.tr(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
category: _categoryForSection(
|
|
|
|
|
_travelSection,
|
|
|
|
|
LocaleKeys.carServices.tr(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
],
|
|
|
|
|
@ -630,57 +632,69 @@ class _MazayaDiscoverScreenState extends State<MazayaDiscoverScreen> {
|
|
|
|
|
color: const Color(0xFF78817F),
|
|
|
|
|
weight: FontWeight.w500,
|
|
|
|
|
),
|
|
|
|
|
InkWell(
|
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
|
|
|
|
onTap:
|
|
|
|
|
() => showMazayaCitySelectionBottomSheet(
|
|
|
|
|
context,
|
|
|
|
|
selectedCityId: selectedCity?.cityId,
|
|
|
|
|
onCitySelected: (city) {
|
|
|
|
|
WalaPlusCityModel? newCity = city.isAllCities ? null : city;
|
|
|
|
|
AppState().setSelectedMazayaCity = newCity;
|
|
|
|
|
if (newCity != null) {
|
|
|
|
|
Utils.saveStringFromPrefs(
|
|
|
|
|
"MAZAYA_SELECTED_CITY",
|
|
|
|
|
jsonEncode(newCity.toJson()),
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
Utils.removeStringFromPrefs("MAZAYA_SELECTED_CITY");
|
|
|
|
|
}
|
|
|
|
|
setState(() => selectedCity = newCity);
|
|
|
|
|
_fetchLatestOnMazaya();
|
|
|
|
|
_fetchCategorySection(_travelSection);
|
|
|
|
|
_fetchCategorySection(_homeKitchenSection);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
SvgPicture.asset(
|
|
|
|
|
"assets/icons/mazaya_map_pin.svg",
|
|
|
|
|
width: 12,
|
|
|
|
|
height: 12,
|
|
|
|
|
colorFilter: const ColorFilter.mode(
|
|
|
|
|
Color(0xFF147D6B),
|
|
|
|
|
BlendMode.srcIn,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 3),
|
|
|
|
|
(selectedCity?.getDisplayName(isAr) ??
|
|
|
|
|
LocaleKeys.allCitiesOption.tr())
|
|
|
|
|
.toText12(color: const Color(0xFF147D6B)),
|
|
|
|
|
const SizedBox(width: 3),
|
|
|
|
|
SvgPicture.asset(
|
|
|
|
|
"assets/icons/mazaya_chevron_down.svg",
|
|
|
|
|
width: 12,
|
|
|
|
|
height: 12,
|
|
|
|
|
colorFilter: const ColorFilter.mode(
|
|
|
|
|
Color(0xFF147D6B),
|
|
|
|
|
BlendMode.srcIn,
|
|
|
|
|
Builder(
|
|
|
|
|
builder: (context) {
|
|
|
|
|
bool hasSelectedCity =
|
|
|
|
|
selectedCity != null && !selectedCity!.isAllCities;
|
|
|
|
|
return Row(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
InkWell(
|
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
|
|
|
|
onTap: _openCitySelector,
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
SvgPicture.asset(
|
|
|
|
|
"assets/icons/mazaya_map_pin.svg",
|
|
|
|
|
width: 12,
|
|
|
|
|
height: 12,
|
|
|
|
|
colorFilter: const ColorFilter.mode(
|
|
|
|
|
Color(0xFF147D6B),
|
|
|
|
|
BlendMode.srcIn,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 3),
|
|
|
|
|
(selectedCity?.getDisplayName(isAr) ??
|
|
|
|
|
LocaleKeys.allCitiesOption.tr())
|
|
|
|
|
.toText12(color: const Color(0xFF147D6B)),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 3),
|
|
|
|
|
if (hasSelectedCity)
|
|
|
|
|
InkWell(
|
|
|
|
|
borderRadius: BorderRadius.circular(12),
|
|
|
|
|
onTap: _resetToAllCities,
|
|
|
|
|
child: const Padding(
|
|
|
|
|
padding: EdgeInsets.all(2),
|
|
|
|
|
child: Icon(
|
|
|
|
|
Icons.close_rounded,
|
|
|
|
|
size: 14,
|
|
|
|
|
color: Color(0xFF147D6B),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
else
|
|
|
|
|
InkWell(
|
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
|
|
|
|
onTap: _openCitySelector,
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
|
|
|
|
child: SvgPicture.asset(
|
|
|
|
|
"assets/icons/mazaya_chevron_down.svg",
|
|
|
|
|
width: 12,
|
|
|
|
|
height: 12,
|
|
|
|
|
colorFilter: const ColorFilter.mode(
|
|
|
|
|
Color(0xFF147D6B),
|
|
|
|
|
BlendMode.srcIn,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
|