update filter products
This commit is contained in:
@@ -65,7 +65,7 @@ String _$connectivityHash() => r'6d67af0ea4110f6ee0246dd332f90f8901380eda';
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -81,7 +81,7 @@ const connectivityStreamProvider = ConnectivityStreamProvider._();
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -104,7 +104,7 @@ final class ConnectivityStreamProvider
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -219,7 +219,7 @@ String _$currentConnectivityHash() =>
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -235,7 +235,7 @@ const isOnlineProvider = IsOnlineProvider._();
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@@ -251,7 +251,7 @@ final class IsOnlineProvider
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// loading: () => const CustomLoadingIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@@ -52,13 +52,39 @@ class AnalyticsService {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// E-commerce Events
|
||||
// ============================================================================
|
||||
|
||||
/// Log view item event - when user views product detail
|
||||
static Future<void> logViewItem({
|
||||
required String productId,
|
||||
required String productName,
|
||||
required double price,
|
||||
String? brand,
|
||||
String? category,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logViewItem(
|
||||
currency: 'VND',
|
||||
value: price,
|
||||
items: [
|
||||
AnalyticsEventItem(
|
||||
itemId: productId,
|
||||
itemName: productName,
|
||||
price: price,
|
||||
itemBrand: brand,
|
||||
itemCategory: category,
|
||||
),
|
||||
],
|
||||
);
|
||||
debugPrint('📊 Analytics: view_item - $productName');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log add to cart event
|
||||
///
|
||||
/// [productId] - Product SKU or ID
|
||||
/// [productName] - Product display name
|
||||
/// [price] - Unit price in VND
|
||||
/// [quantity] - Quantity added
|
||||
/// [category] - Optional product category
|
||||
static Future<void> logAddToCart({
|
||||
required String productId,
|
||||
required String productName,
|
||||
@@ -85,4 +111,252 @@ class AnalyticsService {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log remove from cart event
|
||||
static Future<void> logRemoveFromCart({
|
||||
required String productId,
|
||||
required String productName,
|
||||
required double price,
|
||||
required int quantity,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logRemoveFromCart(
|
||||
currency: 'VND',
|
||||
value: price * quantity,
|
||||
items: [
|
||||
AnalyticsEventItem(
|
||||
itemId: productId,
|
||||
itemName: productName,
|
||||
price: price,
|
||||
quantity: quantity,
|
||||
),
|
||||
],
|
||||
);
|
||||
debugPrint('📊 Analytics: remove_from_cart - $productName');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log view cart event
|
||||
static Future<void> logViewCart({
|
||||
required double cartValue,
|
||||
required List<AnalyticsEventItem> items,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logViewCart(
|
||||
currency: 'VND',
|
||||
value: cartValue,
|
||||
items: items,
|
||||
);
|
||||
debugPrint('📊 Analytics: view_cart - ${items.length} items');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log begin checkout event
|
||||
static Future<void> logBeginCheckout({
|
||||
required double value,
|
||||
required List<AnalyticsEventItem> items,
|
||||
String? coupon,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logBeginCheckout(
|
||||
currency: 'VND',
|
||||
value: value,
|
||||
items: items,
|
||||
coupon: coupon,
|
||||
);
|
||||
debugPrint('📊 Analytics: begin_checkout - $value VND');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log purchase event - when order is completed
|
||||
static Future<void> logPurchase({
|
||||
required String orderId,
|
||||
required double value,
|
||||
required List<AnalyticsEventItem> items,
|
||||
double? shipping,
|
||||
double? tax,
|
||||
String? coupon,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logPurchase(
|
||||
currency: 'VND',
|
||||
transactionId: orderId,
|
||||
value: value,
|
||||
items: items,
|
||||
shipping: shipping,
|
||||
tax: tax,
|
||||
coupon: coupon,
|
||||
);
|
||||
debugPrint('📊 Analytics: purchase - Order $orderId');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Search & Discovery Events
|
||||
// ============================================================================
|
||||
|
||||
/// Log search event
|
||||
static Future<void> logSearch({
|
||||
required String searchTerm,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logSearch(searchTerm: searchTerm);
|
||||
debugPrint('📊 Analytics: search - $searchTerm');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log select item event - when user taps on a product in list
|
||||
static Future<void> logSelectItem({
|
||||
required String productId,
|
||||
required String productName,
|
||||
String? listName,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logSelectItem(
|
||||
itemListName: listName,
|
||||
items: [
|
||||
AnalyticsEventItem(
|
||||
itemId: productId,
|
||||
itemName: productName,
|
||||
),
|
||||
],
|
||||
);
|
||||
debugPrint('📊 Analytics: select_item - $productName');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Loyalty & Rewards Events
|
||||
// ============================================================================
|
||||
|
||||
/// Log earn points event
|
||||
static Future<void> logEarnPoints({
|
||||
required int points,
|
||||
required String source,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logEarnVirtualCurrency(
|
||||
virtualCurrencyName: 'loyalty_points',
|
||||
value: points.toDouble(),
|
||||
);
|
||||
debugPrint('📊 Analytics: earn_points - $points from $source');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log spend points event - when user redeems points
|
||||
static Future<void> logSpendPoints({
|
||||
required int points,
|
||||
required String itemName,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logSpendVirtualCurrency(
|
||||
virtualCurrencyName: 'loyalty_points',
|
||||
value: points.toDouble(),
|
||||
itemName: itemName,
|
||||
);
|
||||
debugPrint('📊 Analytics: spend_points - $points for $itemName');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// User Events
|
||||
// ============================================================================
|
||||
|
||||
/// Log login event
|
||||
static Future<void> logLogin({
|
||||
String? method,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logLogin(loginMethod: method ?? 'phone');
|
||||
debugPrint('📊 Analytics: login - $method');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log sign up event
|
||||
static Future<void> logSignUp({
|
||||
String? method,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logSignUp(signUpMethod: method ?? 'phone');
|
||||
debugPrint('📊 Analytics: sign_up - $method');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log share event
|
||||
static Future<void> logShare({
|
||||
required String contentType,
|
||||
required String itemId,
|
||||
String? method,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logShare(
|
||||
contentType: contentType,
|
||||
itemId: itemId,
|
||||
method: method ?? 'unknown',
|
||||
);
|
||||
debugPrint('📊 Analytics: share - $contentType $itemId');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Custom Events
|
||||
// ============================================================================
|
||||
|
||||
/// Log custom event
|
||||
static Future<void> logEvent({
|
||||
required String name,
|
||||
Map<String, Object>? parameters,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.logEvent(name: name, parameters: parameters);
|
||||
debugPrint('📊 Analytics: $name');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Set user ID for analytics
|
||||
static Future<void> setUserId(String? userId) async {
|
||||
try {
|
||||
await _analytics.setUserId(id: userId);
|
||||
debugPrint('📊 Analytics: setUserId - $userId');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Set user property
|
||||
static Future<void> setUserProperty({
|
||||
required String name,
|
||||
required String? value,
|
||||
}) async {
|
||||
try {
|
||||
await _analytics.setUserProperty(name: name, value: value);
|
||||
debugPrint('📊 Analytics: setUserProperty - $name: $value');
|
||||
} catch (e) {
|
||||
debugPrint('📊 Analytics error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
// Set only this brand, keep other filters
|
||||
newFilters = appliedFilters.copyWith(brands: {brand.value});
|
||||
}
|
||||
|
||||
// Then add the selected brand
|
||||
notifier.toggleBrand(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