This commit is contained in:
Phuoc Nguyen
2025-10-10 16:38:07 +07:00
parent e5b247d622
commit b94c158004
177 changed files with 25080 additions and 152 deletions

View File

@@ -0,0 +1,200 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../widgets/product_grid.dart';
import '../widgets/product_search_bar.dart';
import '../providers/products_provider.dart';
import '../providers/selected_category_provider.dart' as product_providers;
import '../providers/filtered_products_provider.dart';
import '../../domain/entities/product.dart';
import '../../../categories/presentation/providers/categories_provider.dart';
/// Products page - displays all products in a grid
class ProductsPage extends ConsumerStatefulWidget {
const ProductsPage({super.key});
@override
ConsumerState<ProductsPage> createState() => _ProductsPageState();
}
class _ProductsPageState extends ConsumerState<ProductsPage> {
ProductSortOption _sortOption = ProductSortOption.nameAsc;
@override
Widget build(BuildContext context) {
final categoriesAsync = ref.watch(categoriesProvider);
final selectedCategory = ref.watch(product_providers.selectedCategoryProvider);
final productsAsync = ref.watch(productsProvider);
// Get filtered products from the provider
final filteredProducts = productsAsync.when(
data: (products) => products,
loading: () => <Product>[],
error: (_, __) => <Product>[],
);
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
actions: [
// Sort button
PopupMenuButton<ProductSortOption>(
icon: const Icon(Icons.sort),
tooltip: 'Sort products',
onSelected: (option) {
setState(() {
_sortOption = option;
});
},
itemBuilder: (context) => [
const PopupMenuItem(
value: ProductSortOption.nameAsc,
child: Row(
children: [
Icon(Icons.sort_by_alpha),
SizedBox(width: 8),
Text('Name (A-Z)'),
],
),
),
const PopupMenuItem(
value: ProductSortOption.nameDesc,
child: Row(
children: [
Icon(Icons.sort_by_alpha),
SizedBox(width: 8),
Text('Name (Z-A)'),
],
),
),
const PopupMenuItem(
value: ProductSortOption.priceAsc,
child: Row(
children: [
Icon(Icons.attach_money),
SizedBox(width: 8),
Text('Price (Low to High)'),
],
),
),
const PopupMenuItem(
value: ProductSortOption.priceDesc,
child: Row(
children: [
Icon(Icons.attach_money),
SizedBox(width: 8),
Text('Price (High to Low)'),
],
),
),
const PopupMenuItem(
value: ProductSortOption.newest,
child: Row(
children: [
Icon(Icons.access_time),
SizedBox(width: 8),
Text('Newest First'),
],
),
),
const PopupMenuItem(
value: ProductSortOption.oldest,
child: Row(
children: [
Icon(Icons.access_time),
SizedBox(width: 8),
Text('Oldest First'),
],
),
),
],
),
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(120),
child: Column(
children: [
// Search bar
const Padding(
padding: EdgeInsets.all(8.0),
child: ProductSearchBar(),
),
// Category filter chips
categoriesAsync.when(
loading: () => const SizedBox.shrink(),
error: (_, __) => const SizedBox.shrink(),
data: (categories) {
if (categories.isEmpty) return const SizedBox.shrink();
return SizedBox(
height: 50,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8),
children: [
// All categories chip
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: FilterChip(
label: const Text('All'),
selected: selectedCategory == null,
onSelected: (_) {
ref
.read(product_providers.selectedCategoryProvider.notifier)
.clearSelection();
},
),
),
// Category chips
...categories.map(
(category) => Padding(
padding: const EdgeInsets.only(right: 8.0),
child: FilterChip(
label: Text(category.name),
selected: selectedCategory == category.id,
onSelected: (_) {
ref
.read(product_providers.selectedCategoryProvider.notifier)
.selectCategory(category.id);
},
),
),
),
],
),
);
},
),
],
),
),
),
body: RefreshIndicator(
onRefresh: () async {
await ref.refresh(productsProvider.future);
await ref.refresh(categoriesProvider.future);
},
child: Column(
children: [
// Results count
if (filteredProducts.isNotEmpty)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'${filteredProducts.length} product${filteredProducts.length == 1 ? '' : 's'} found',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
// Product grid
Expanded(
child: ProductGrid(
sortOption: _sortOption,
),
),
],
),
),
);
}
}