add favorite
This commit is contained in:
@@ -58,14 +58,15 @@ class FavoritesPage extends ConsumerWidget {
|
||||
);
|
||||
|
||||
if (confirmed == true && context.mounted) {
|
||||
// Clear all favorites
|
||||
await ref.read(favoritesProvider.notifier).clearAll();
|
||||
// TODO: Implement clear all functionality
|
||||
// For now, we would need to remove each product individually
|
||||
// or add a clearAll method to the repository
|
||||
|
||||
// Show snackbar
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Đã xóa tất cả yêu thích'),
|
||||
content: Text('Chức năng này đang được phát triển'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
@@ -79,6 +80,9 @@ class FavoritesPage extends ConsumerWidget {
|
||||
final favoriteProductsAsync = ref.watch(favoriteProductsProvider);
|
||||
final favoriteCount = ref.watch(favoriteCountProvider);
|
||||
|
||||
// Track if we've loaded data at least once to prevent empty state flash
|
||||
final hasLoadedOnce = favoriteProductsAsync.hasValue || favoriteProductsAsync.hasError;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF4F6F8),
|
||||
appBar: AppBar(
|
||||
@@ -121,26 +125,148 @@ class FavoritesPage extends ConsumerWidget {
|
||||
body: SafeArea(
|
||||
child: favoriteProductsAsync.when(
|
||||
data: (products) {
|
||||
if (products.isEmpty) {
|
||||
// IMPORTANT: Only show empty state after we've confirmed data loaded
|
||||
// This prevents empty state flash during initial load
|
||||
if (products.isEmpty && hasLoadedOnce) {
|
||||
return const _EmptyState();
|
||||
}
|
||||
|
||||
// If products is empty but we haven't loaded yet, show loading
|
||||
if (products.isEmpty && !hasLoadedOnce) {
|
||||
return const _LoadingState();
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
ref.invalidate(favoritesProvider);
|
||||
ref.invalidate(favoriteProductsProvider);
|
||||
// Use the new refresh method from AsyncNotifier
|
||||
await ref.read(favoriteProductsProvider.notifier).refresh();
|
||||
},
|
||||
child: _FavoritesGrid(products: products),
|
||||
);
|
||||
},
|
||||
loading: () => const _LoadingState(),
|
||||
error: (error, stackTrace) => _ErrorState(
|
||||
error: error,
|
||||
onRetry: () {
|
||||
ref.invalidate(favoritesProvider);
|
||||
ref.invalidate(favoriteProductsProvider);
|
||||
},
|
||||
),
|
||||
loading: () {
|
||||
// IMPORTANT: Check for previous data first to prevent empty state flash
|
||||
final previousValue = favoriteProductsAsync.hasValue
|
||||
? favoriteProductsAsync.value
|
||||
: null;
|
||||
|
||||
// If we have previous data, show it while loading new data
|
||||
if (previousValue != null && previousValue.isNotEmpty) {
|
||||
return Stack(
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await ref.read(favoriteProductsProvider.notifier).refresh();
|
||||
},
|
||||
child: _FavoritesGrid(products: previousValue),
|
||||
),
|
||||
const Positioned(
|
||||
top: 16,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text('Đang tải...'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Check if we should show skeleton or empty state
|
||||
// Use favoriteCount as a hint - if it's > 0, we likely have data coming
|
||||
if (favoriteCount > 0) {
|
||||
// Show skeleton loading for better UX
|
||||
return const _LoadingState();
|
||||
}
|
||||
|
||||
// No previous data and no favorites - show skeleton briefly
|
||||
return const _LoadingState();
|
||||
},
|
||||
error: (error, stackTrace) {
|
||||
// Check if we have previous data to show with error
|
||||
final previousValue = favoriteProductsAsync.hasValue
|
||||
? favoriteProductsAsync.value
|
||||
: null;
|
||||
if (previousValue != null && previousValue.isNotEmpty) {
|
||||
// Show previous data with error message
|
||||
return Stack(
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await ref.read(favoriteProductsProvider.notifier).refresh();
|
||||
},
|
||||
child: _FavoritesGrid(products: previousValue),
|
||||
),
|
||||
Positioned(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: Material(
|
||||
color: AppColors.danger,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
color: AppColors.white,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'Không thể tải dữ liệu mới',
|
||||
style: TextStyle(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await ref.read(favoriteProductsProvider.notifier).refresh();
|
||||
},
|
||||
child: const Text(
|
||||
'Thử lại',
|
||||
style: TextStyle(
|
||||
color: AppColors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
// No previous data, show full error state
|
||||
return _ErrorState(
|
||||
error: error,
|
||||
onRetry: () async {
|
||||
await ref.read(favoriteProductsProvider.notifier).refresh();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -188,7 +314,7 @@ class _EmptyState extends StatelessWidget {
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
|
||||
// Subtext
|
||||
Text(
|
||||
const Text(
|
||||
'Thêm sản phẩm vào danh sách yêu thích để xem lại sau',
|
||||
style: TextStyle(fontSize: 14.0, color: AppColors.grey500),
|
||||
textAlign: TextAlign.center,
|
||||
@@ -269,9 +395,9 @@ class _ShimmerCard extends StatelessWidget {
|
||||
// Image placeholder
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.grey100,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(ProductCardSpecs.borderRadius),
|
||||
),
|
||||
),
|
||||
@@ -347,11 +473,11 @@ class _ShimmerCard extends StatelessWidget {
|
||||
///
|
||||
/// Displayed when there's an error loading favorites.
|
||||
class _ErrorState extends StatelessWidget {
|
||||
final Object error;
|
||||
final VoidCallback onRetry;
|
||||
|
||||
const _ErrorState({required this.error, required this.onRetry});
|
||||
|
||||
final Object error;
|
||||
final Future<void> Function() onRetry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
@@ -428,10 +554,10 @@ class _ErrorState extends StatelessWidget {
|
||||
///
|
||||
/// Displays favorite products in a grid layout.
|
||||
class _FavoritesGrid extends StatelessWidget {
|
||||
final List<Product> products;
|
||||
|
||||
const _FavoritesGrid({required this.products});
|
||||
|
||||
final List<Product> products;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.builder(
|
||||
|
||||
Reference in New Issue
Block a user