runable
This commit is contained in:
200
lib/features/products/presentation/pages/products_page.dart
Normal file
200
lib/features/products/presentation/pages/products_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user