You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
13 KiB
Dart
387 lines
13 KiB
Dart
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/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<SavedRetailersScreen> createState() => _SavedRetailersScreenState();
|
|
}
|
|
|
|
class _SavedRetailersScreenState extends State<SavedRetailersScreen> {
|
|
List<WalaPlusRetailerModel> favoriteRetailers = <WalaPlusRetailerModel>[];
|
|
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<void> 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 ?? <WalaPlusRetailerModel>[];
|
|
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 = <WalaPlusRetailerModel>[];
|
|
} finally {
|
|
if (mounted) setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> 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<WalaPlusRetailerModel> newRetailers =
|
|
response?.retailers ?? <WalaPlusRetailerModel>[];
|
|
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<void> _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,
|
|
),
|
|
bottomNavigationBar: const MazayaBottomNav(currentTab: MazayaTab.saved),
|
|
body: Column(
|
|
children: <Widget>[
|
|
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>[
|
|
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: <Widget>[
|
|
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<TextEditingValue>(
|
|
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: <Widget>[
|
|
WalaPlusRetailerGrid(
|
|
isLoading: isLoading,
|
|
retailers: favoriteRetailers,
|
|
isAr: isAr,
|
|
isLoadingMore: isLoadingMore,
|
|
onTapRetailer: _openRetailer,
|
|
onFavoriteTap: _toggleFavorite,
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|