update filter products
This commit is contained in:
@@ -160,7 +160,7 @@ String _$getUserInfoUseCaseHash() =>
|
||||
///
|
||||
/// userInfoAsync.when(
|
||||
/// data: (userInfo) => Text(userInfo.fullName),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
///
|
||||
@@ -184,7 +184,7 @@ const userInfoProvider = UserInfoProvider._();
|
||||
///
|
||||
/// userInfoAsync.when(
|
||||
/// data: (userInfo) => Text(userInfo.fullName),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
///
|
||||
@@ -206,7 +206,7 @@ final class UserInfoProvider
|
||||
///
|
||||
/// userInfoAsync.when(
|
||||
/// data: (userInfo) => Text(userInfo.fullName),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
///
|
||||
@@ -247,7 +247,7 @@ String _$userInfoHash() => r'ed28fdf0213dfd616592b9735cd291f147867047';
|
||||
///
|
||||
/// userInfoAsync.when(
|
||||
/// data: (userInfo) => Text(userInfo.fullName),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
///
|
||||
|
||||
@@ -20,7 +20,7 @@ part of 'member_card_provider.dart';
|
||||
///
|
||||
/// memberCardAsync.when(
|
||||
/// data: (memberCard) => MemberCardWidget(memberCard: memberCard),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -40,7 +40,7 @@ const memberCardProvider = MemberCardNotifierProvider._();
|
||||
///
|
||||
/// memberCardAsync.when(
|
||||
/// data: (memberCard) => MemberCardWidget(memberCard: memberCard),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -58,7 +58,7 @@ final class MemberCardNotifierProvider
|
||||
///
|
||||
/// memberCardAsync.when(
|
||||
/// data: (memberCard) => MemberCardWidget(memberCard: memberCard),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -96,7 +96,7 @@ String _$memberCardNotifierHash() =>
|
||||
///
|
||||
/// memberCardAsync.when(
|
||||
/// data: (memberCard) => MemberCardWidget(memberCard: memberCard),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -21,7 +21,7 @@ part of 'promotions_provider.dart';
|
||||
///
|
||||
/// promotionsAsync.when(
|
||||
/// data: (promotions) => PromotionSlider(promotions: promotions),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -42,7 +42,7 @@ const promotionsProvider = PromotionsProvider._();
|
||||
///
|
||||
/// promotionsAsync.when(
|
||||
/// data: (promotions) => PromotionSlider(promotions: promotions),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -68,7 +68,7 @@ final class PromotionsProvider
|
||||
///
|
||||
/// promotionsAsync.when(
|
||||
/// data: (promotions) => PromotionSlider(promotions: promotions),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
||||
import 'package:worker/features/products/presentation/providers/product_filters_provider.dart';
|
||||
import 'package:worker/features/products/presentation/providers/products_provider.dart';
|
||||
import 'package:worker/features/products/presentation/widgets/brand_filter_chips.dart';
|
||||
import 'package:worker/features/products/presentation/widgets/product_filter_drawer.dart';
|
||||
@@ -100,6 +101,8 @@ class ProductsPage extends ConsumerWidget {
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
// Sync pending filters with applied filters before opening drawer
|
||||
ref.read(productFiltersProvider.notifier).syncWithApplied();
|
||||
// Open filter drawer from right
|
||||
Scaffold.of(scaffoldContext).openEndDrawer();
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ part of 'product_filter_options_provider.dart';
|
||||
///
|
||||
/// filterOptionsAsync.when(
|
||||
/// data: (options) => ProductFilterDrawer(options: options),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -48,7 +48,7 @@ const productFilterOptionsProvider = ProductFilterOptionsProvider._();
|
||||
///
|
||||
/// filterOptionsAsync.when(
|
||||
/// data: (options) => ProductFilterDrawer(options: options),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -79,7 +79,7 @@ final class ProductFilterOptionsProvider
|
||||
///
|
||||
/// filterOptionsAsync.when(
|
||||
/// data: (options) => ProductFilterDrawer(options: options),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/// Provider: Product Filters State
|
||||
///
|
||||
/// Manages product filter selections.
|
||||
/// Manages product filter selections with separate pending and applied states.
|
||||
/// Pending filters: Updated on every checkbox toggle (no API call)
|
||||
/// Applied filters: Only updated when Apply button is pressed (triggers API)
|
||||
library;
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -54,7 +56,10 @@ class ProductFiltersState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Product Filters Notifier
|
||||
/// Product Filters Notifier (Pending Filters - for UI selection)
|
||||
///
|
||||
/// This provider stores the PENDING filter selections in the drawer.
|
||||
/// Changes here do NOT trigger API calls.
|
||||
class ProductFiltersNotifier extends Notifier<ProductFiltersState> {
|
||||
@override
|
||||
ProductFiltersState build() => const ProductFiltersState();
|
||||
@@ -114,19 +119,52 @@ class ProductFiltersNotifier extends Notifier<ProductFiltersState> {
|
||||
state = state.copyWith(brands: newSet);
|
||||
}
|
||||
|
||||
/// Reset all filters
|
||||
/// Reset all filters (both pending and applied)
|
||||
void reset() {
|
||||
state = const ProductFiltersState();
|
||||
// Also reset applied filters
|
||||
ref.read(appliedProductFiltersProvider.notifier).reset();
|
||||
}
|
||||
|
||||
/// Apply filters (placeholder for future implementation)
|
||||
/// Apply filters - copies pending state to applied state
|
||||
/// This is the ONLY action that triggers API calls
|
||||
void apply() {
|
||||
// TODO: Trigger products provider refresh with filters
|
||||
ref.read(appliedProductFiltersProvider.notifier).applyFilters(state);
|
||||
}
|
||||
|
||||
/// Sync pending state with applied state (when opening drawer)
|
||||
void syncWithApplied() {
|
||||
state = ref.read(appliedProductFiltersProvider);
|
||||
}
|
||||
}
|
||||
|
||||
/// Product Filters Provider
|
||||
/// Applied Product Filters Notifier (Triggers API calls)
|
||||
///
|
||||
/// This provider stores the APPLIED filter state.
|
||||
/// The products provider watches THIS provider, not the pending one.
|
||||
class AppliedProductFiltersNotifier extends Notifier<ProductFiltersState> {
|
||||
@override
|
||||
ProductFiltersState build() => const ProductFiltersState();
|
||||
|
||||
/// Apply filters from pending state
|
||||
void applyFilters(ProductFiltersState filters) {
|
||||
state = filters;
|
||||
}
|
||||
|
||||
/// Reset applied filters
|
||||
void reset() {
|
||||
state = const ProductFiltersState();
|
||||
}
|
||||
}
|
||||
|
||||
/// Product Filters Provider (Pending - for drawer UI)
|
||||
final productFiltersProvider =
|
||||
NotifierProvider<ProductFiltersNotifier, ProductFiltersState>(
|
||||
ProductFiltersNotifier.new,
|
||||
);
|
||||
|
||||
/// Applied Product Filters Provider (Triggers API)
|
||||
final appliedProductFiltersProvider =
|
||||
NotifierProvider<AppliedProductFiltersNotifier, ProductFiltersState>(
|
||||
AppliedProductFiltersNotifier.new,
|
||||
);
|
||||
|
||||
@@ -81,7 +81,8 @@ class Products extends _$Products {
|
||||
|
||||
// Watch dependencies (triggers rebuild when they change)
|
||||
final searchQuery = ref.watch(searchQueryProvider);
|
||||
final filters = ref.watch(productFiltersProvider);
|
||||
// Watch APPLIED filters, not pending filters (API only called on Apply)
|
||||
final filters = ref.watch(appliedProductFiltersProvider);
|
||||
|
||||
// Get repository with injected data sources
|
||||
final repository = await ref.watch(productsRepositoryProvider.future);
|
||||
@@ -148,7 +149,8 @@ class Products extends _$Products {
|
||||
|
||||
// Read dependencies to get current filters (use read, not watch)
|
||||
final searchQuery = ref.read(searchQueryProvider);
|
||||
final filters = ref.read(productFiltersProvider);
|
||||
// Read APPLIED filters, not pending filters
|
||||
final filters = ref.read(appliedProductFiltersProvider);
|
||||
|
||||
// Get repository
|
||||
final repository = await ref.read(productsRepositoryProvider.future);
|
||||
|
||||
@@ -167,7 +167,7 @@ String _$productsRepositoryHash() =>
|
||||
///
|
||||
/// productsAsync.when(
|
||||
/// data: (products) => ProductGrid(products: products),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -187,7 +187,7 @@ const productsProvider = ProductsProvider._();
|
||||
///
|
||||
/// productsAsync.when(
|
||||
/// data: (products) => ProductGrid(products: products),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -205,7 +205,7 @@ final class ProductsProvider
|
||||
///
|
||||
/// productsAsync.when(
|
||||
/// data: (products) => ProductGrid(products: products),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -228,7 +228,7 @@ final class ProductsProvider
|
||||
Products create() => Products();
|
||||
}
|
||||
|
||||
String _$productsHash() => r'a4f416712cdbf2e633622c65b1fdc95686e31fa4';
|
||||
String _$productsHash() => r'502af6c2e9012a619c15fd04bfe778045739e247';
|
||||
|
||||
/// Products Provider
|
||||
///
|
||||
@@ -242,7 +242,7 @@ String _$productsHash() => r'a4f416712cdbf2e633622c65b1fdc95686e31fa4';
|
||||
///
|
||||
/// productsAsync.when(
|
||||
/// data: (products) => ProductGrid(products: products),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -333,7 +333,7 @@ String _$allProductsHash() => r'402d7c6e8d119c7c7eab5e696fb8163831259def';
|
||||
///
|
||||
/// productAsync.when(
|
||||
/// data: (product) => ProductDetailView(product: product),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -352,7 +352,7 @@ const productDetailProvider = ProductDetailFamily._();
|
||||
///
|
||||
/// productAsync.when(
|
||||
/// data: (product) => ProductDetailView(product: product),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -371,7 +371,7 @@ final class ProductDetailProvider
|
||||
///
|
||||
/// productAsync.when(
|
||||
/// data: (product) => ProductDetailView(product: product),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -431,7 +431,7 @@ String _$productDetailHash() => r'ca219f1451f518c84ca1832aacb3c83920f4bfd2';
|
||||
///
|
||||
/// productAsync.when(
|
||||
/// data: (product) => ProductDetailView(product: product),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -458,7 +458,7 @@ final class ProductDetailFamily extends $Family
|
||||
///
|
||||
/// productAsync.when(
|
||||
/// data: (product) => ProductDetailView(product: product),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -13,15 +13,16 @@ import 'package:worker/features/products/presentation/providers/product_filters_
|
||||
/// Brand Filter Chips Widget
|
||||
///
|
||||
/// Displays brands as horizontally scrolling chips.
|
||||
/// Synced with filter drawer - both use productFiltersProvider.brands.
|
||||
/// Chips are single-select (tapping a brand clears others and sets just that one).
|
||||
/// Watches appliedProductFiltersProvider to show currently active filters.
|
||||
/// Tapping a chip directly applies the filter (triggers API call immediately).
|
||||
class BrandFilterChips extends ConsumerWidget {
|
||||
const BrandFilterChips({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final filtersState = ref.watch(productFiltersProvider);
|
||||
// Watch APPLIED filters to show current active state
|
||||
final appliedFilters = ref.watch(appliedProductFiltersProvider);
|
||||
final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
|
||||
|
||||
return filterOptionsAsync.when(
|
||||
@@ -46,9 +47,9 @@ class BrandFilterChips extends ConsumerWidget {
|
||||
// "Tất cả" is selected if no brands are selected
|
||||
// A brand chip is selected if it's the ONLY brand selected
|
||||
final isSelected = brand.value == 'all'
|
||||
? filtersState.brands.isEmpty
|
||||
: (filtersState.brands.length == 1 &&
|
||||
filtersState.brands.contains(brand.value));
|
||||
? appliedFilters.brands.isEmpty
|
||||
: (appliedFilters.brands.length == 1 &&
|
||||
appliedFilters.brands.contains(brand.value));
|
||||
|
||||
return FilterChip(
|
||||
label: Text(
|
||||
@@ -62,27 +63,22 @@ class BrandFilterChips extends ConsumerWidget {
|
||||
selected: isSelected,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
final notifier = ref.read(productFiltersProvider.notifier);
|
||||
// Create new filter state with only the selected brand
|
||||
ProductFiltersState newFilters;
|
||||
|
||||
if (brand.value == 'all') {
|
||||
// Clear all brand filters
|
||||
// Reset all brands by setting to empty set
|
||||
final currentBrands = List<String>.from(filtersState.brands);
|
||||
for (final b in currentBrands) {
|
||||
notifier.toggleBrand(b); // Toggle off each brand
|
||||
}
|
||||
// Clear brand filter, keep other filters
|
||||
newFilters = appliedFilters.copyWith(brands: {});
|
||||
} else {
|
||||
// Single-select: clear all other brands and set only this one
|
||||
final currentBrands = List<String>.from(filtersState.brands);
|
||||
|
||||
// First, clear all existing brands
|
||||
for (final b in currentBrands) {
|
||||
notifier.toggleBrand(b);
|
||||
}
|
||||
|
||||
// Then add the selected brand
|
||||
notifier.toggleBrand(brand.value);
|
||||
// Set only this brand, keep other filters
|
||||
newFilters = appliedFilters.copyWith(brands: {brand.value});
|
||||
}
|
||||
|
||||
// Apply directly to trigger API call
|
||||
ref.read(appliedProductFiltersProvider.notifier).applyFilters(newFilters);
|
||||
|
||||
// Also sync pending filters with applied
|
||||
ref.read(productFiltersProvider.notifier).syncWithApplied();
|
||||
}
|
||||
},
|
||||
backgroundColor: colorScheme.surface,
|
||||
|
||||
Reference in New Issue
Block a user