import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/colors.dart'; import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart'; import 'package:mohem_flutter_app/ui/mazaya/widgets/mazaya_bottom_nav.dart'; import 'package:mohem_flutter_app/ui/mazaya/widgets/wala_plus_retailer_grid.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; // Mazaya Saved tab - full-screen version of what was FavoriteMerchantsBottomSheet (a modal // bottom sheet). Same fetch/search/pagination/toggle-favorite logic, now its own pushed route // with the shared MazayaBottomNav instead of a dismissible sheet - matches Discover's "own // route, fetches fresh on mount" pattern. class SavedRetailersScreen extends StatefulWidget { const SavedRetailersScreen({Key? key}) : super(key: key); @override State createState() => _SavedRetailersScreenState(); } class _SavedRetailersScreenState extends State { List favoriteRetailers = []; bool isLoading = true; bool isLoadingMore = false; bool hasMore = true; bool _isFetchingPage = false; int currentPage = 1; int totalPages = 1; final int pageSize = 10; String searchQuery = ""; Timer? _debounceTimer; final TextEditingController _searchController = TextEditingController(); final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); _scrollController.addListener(_scrollListener); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) fetchFavorites(); }); } @override void dispose() { _debounceTimer?.cancel(); _scrollController.removeListener(_scrollListener); _scrollController.dispose(); _searchController.dispose(); super.dispose(); } void _scrollListener() { if (!_scrollController.hasClients) return; double maxScroll = _scrollController.position.maxScrollExtent; double currentScroll = _scrollController.position.pixels; if (currentScroll >= maxScroll - 35) { if (!isLoading && !isLoadingMore && !_isFetchingPage && hasMore) { fetchNextPage(); } } } String? _getUserReferenceForFavorites() { String ref = OffersAndDiscountsApiClient().resolveUserReference(); if (ref.trim().isNotEmpty) return ref.trim(); String? userName = AppState().getUserName; if (userName != null && userName.trim().isNotEmpty) return userName.trim(); return null; } Future fetchFavorites({String? query}) async { if (query != null) searchQuery = query; setState(() { isLoading = true; currentPage = 1; hasMore = true; }); try { String? queryParam = searchQuery.trim().isNotEmpty ? searchQuery.trim() : null; String? userRef = _getUserReferenceForFavorites(); WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient() .getWalaPlusRetailers( categoryId: "", page: 1, perPage: pageSize, offerType: null, query: queryParam, sortBy: null, radius: null, minDiscount: null, context: null, userReference: userRef, ); favoriteRetailers = response?.retailers ?? []; for (WalaPlusRetailerModel r in favoriteRetailers) { r.isFavorite = true; } currentPage = 1; totalPages = response?.pagesCount ?? 1; hasMore = currentPage < totalPages && (response?.retailers?.isNotEmpty ?? false); } catch (e) { debugPrint("MazayaSavedScreen: error fetching favorites: $e"); favoriteRetailers = []; } finally { if (mounted) setState(() => isLoading = false); } } Future fetchNextPage() async { if (isLoading || isLoadingMore || _isFetchingPage || !hasMore) return; _isFetchingPage = true; setState(() => isLoadingMore = true); try { int nextPage = currentPage + 1; String? queryParam = searchQuery.trim().isNotEmpty ? searchQuery.trim() : null; String? userRef = _getUserReferenceForFavorites(); WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient() .getWalaPlusRetailers( categoryId: "", page: nextPage, perPage: pageSize, offerType: null, query: queryParam, sortBy: null, radius: null, minDiscount: null, context: null, userReference: userRef, ); List newRetailers = response?.retailers ?? []; if (newRetailers.isNotEmpty) { for (WalaPlusRetailerModel r in newRetailers) { r.isFavorite = true; } currentPage = nextPage; totalPages = response?.pagesCount ?? totalPages; hasMore = currentPage < totalPages; favoriteRetailers.addAll(newRetailers); } else { hasMore = false; } } catch (e) { debugPrint("MazayaSavedScreen: error fetching next page: $e"); } finally { _isFetchingPage = false; if (mounted) setState(() => isLoadingMore = false); } } void _onSearchChanged(String query) { _debounceTimer?.cancel(); searchQuery = query; if (query.trim().isEmpty) { fetchFavorites(query: ""); } else { _debounceTimer = Timer(const Duration(milliseconds: 600), () { fetchFavorites(query: query.trim()); }); } } Future _toggleFavorite(WalaPlusRetailerModel item) async { String retailerId = item.id ?? ""; if (retailerId.isEmpty) return; bool currentFav = item.isFavorite ?? true; bool targetFav = !currentFav; setState(() { item.isFavorite = targetFav; if (!targetFav) { favoriteRetailers.removeWhere( (WalaPlusRetailerModel r) => r.id == item.id, ); } }); Utils.showLoading(context); try { GenericResponseModel? response; String? userRef = _getUserReferenceForFavorites(); if (targetFav) { response = await OffersAndDiscountsApiClient().addWalaPlusFavorite( retailerId: retailerId, context: context, userReference: userRef, ); } else { response = await OffersAndDiscountsApiClient().removeWalaPlusFavorite( retailerId: retailerId, context: context, userReference: userRef, ); } Utils.hideLoading(context); if (response != null && response.errorMessage == null) { bool isAr = AppState().isArabic(context); String msg = isAr ? (targetFav ? LocaleKeys.favoriteAddedSuccess.tr() : LocaleKeys.favoriteRemovedSuccess.tr()) : (response.wpFavoritesAddRemoveMessage ?? (targetFav ? LocaleKeys.favoriteAddedSuccess.tr() : LocaleKeys.favoriteRemovedSuccess.tr())); Utils.showToast(msg, isSuccess: true); } else { Utils.showToast( response?.errorEndUserMessage ?? response?.errorMessage ?? LocaleKeys.favoriteError.tr(), ); await fetchFavorites(query: _searchController.text); } } catch (e) { Utils.hideLoading(context); debugPrint("MazayaSavedScreen: error toggling favorite: $e"); Utils.showToast(LocaleKeys.favoriteError.tr()); await fetchFavorites(query: _searchController.text); } } void _openRetailer(WalaPlusRetailerModel item) async { await Navigator.pushNamed( context, AppRoutes.retailerDetailsScreen, arguments: item, ); if (mounted) { await fetchFavorites(query: _searchController.text); } } @override Widget build(BuildContext context) { bool isAr = AppState().isArabic(context); return Scaffold( backgroundColor: Colors.white, appBar: AppBarWidget( context, title: LocaleKeys.favoriteOffers.tr(), showHomeButton: false, // Reached from the Mazaya bottom nav - same as Discover, no back button for a // dependent-login session since this is one of their root tabs, not a pushed screen. showBackButton: false, ), bottomNavigationBar: const MazayaBottomNav(currentTab: MazayaTab.saved), body: Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Container( width: double.infinity, height: 70, padding: const EdgeInsets.only( top: 14, right: 16, bottom: 14, left: 16, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFE8E8E8), width: 1.0), boxShadow: [ BoxShadow( color: const Color(0xFF000000).withOpacity(0.0314), offset: const Offset(0, 2), blurRadius: 5, spreadRadius: 0, ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ LocaleKeys.search.tr().toText12( isBold: true, color: const Color(0xFF2A333C), ), const SizedBox(height: 2), TextField( controller: _searchController, onChanged: _onSearchChanged, onSubmitted: (String val) { _debounceTimer?.cancel(); fetchFavorites(query: val.trim()); }, textInputAction: TextInputAction.search, decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.zero, hintText: LocaleKeys.searchItems.tr(), hintStyle: const TextStyle( color: Color(0xFFACAFB1), fontSize: 14, fontWeight: FontWeight.w400, height: 1.2, ), border: InputBorder.none, suffixIcon: ValueListenableBuilder( valueListenable: _searchController, builder: ( BuildContext context, TextEditingValue value, Widget? child, ) { if (value.text.isEmpty) return const SizedBox.shrink(); return InkWell( child: const Icon( Icons.clear, size: 18, color: Color(0xFF848486), ), onTap: () { _searchController.clear(); _onSearchChanged(""); }, ); }, ), suffixIconConstraints: const BoxConstraints( maxWidth: 24, maxHeight: 28, ), ), style: const TextStyle( color: Color(0xFF2A333C), fontSize: 14, fontWeight: FontWeight.w400, ), ), ], ), ), ), Expanded( child: RefreshIndicator( onRefresh: () => fetchFavorites(query: _searchController.text), color: MyColors.textMixColor, child: SingleChildScrollView( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics( parent: BouncingScrollPhysics(), ), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), child: Column( children: [ WalaPlusRetailerGrid( isLoading: isLoading, retailers: favoriteRetailers, isAr: isAr, isLoadingMore: isLoadingMore, onTapRetailer: _openRetailer, onFavoriteTap: _toggleFavorite, ), const SizedBox(height: 20), ], ), ), ), ), ], ), ); } }