update perf
This commit is contained in:
@@ -10,6 +10,7 @@ library;
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -569,10 +570,10 @@ Future<AuthInterceptor> authInterceptor(Ref ref, Dio dio) async {
|
||||
@riverpod
|
||||
LoggingInterceptor loggingInterceptor(Ref ref) {
|
||||
// Only enable logging in debug mode
|
||||
const bool isDebug = true; // TODO: Replace with kDebugMode from Flutter
|
||||
const bool isDebug = kDebugMode; // TODO: Replace with kDebugMode from Flutter
|
||||
|
||||
return LoggingInterceptor(
|
||||
enableRequestLogging: false,
|
||||
enableRequestLogging: true,
|
||||
enableResponseLogging: isDebug,
|
||||
enableErrorLogging: isDebug,
|
||||
);
|
||||
|
||||
@@ -189,7 +189,7 @@ final class LoggingInterceptorProvider
|
||||
}
|
||||
|
||||
String _$loggingInterceptorHash() =>
|
||||
r'6afa480caa6fcd723dab769bb01601b8a37e20fd';
|
||||
r'79e90e0eb78663d2645d2d7c467e01bc18a30551';
|
||||
|
||||
/// Provider for ErrorTransformerInterceptor
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'dart:developer' as developer;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -248,13 +249,13 @@ class CustomCurlLoggerInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
final curl = _cURLRepresentation(options);
|
||||
// debugPrint(
|
||||
// '╔╣ CURL Request ╠══════════════════════════════════════════════════',
|
||||
// );
|
||||
// debugPrint(curl);
|
||||
// debugPrint(
|
||||
// '╚═════════════════════════════════════════════════════════════════',
|
||||
// );
|
||||
debugPrint(
|
||||
'╔╣ CURL Request ╠══════════════════════════════════════════════════',
|
||||
);
|
||||
debugPrint(curl);
|
||||
debugPrint(
|
||||
'╚═════════════════════════════════════════════════════════════════',
|
||||
);
|
||||
// Also log to dart:developer for better filtering in DevTools
|
||||
developer.log(curl, name: 'DIO_CURL', time: DateTime.now());
|
||||
handler.next(options);
|
||||
@@ -467,7 +468,7 @@ Future<Dio> dio(Ref ref) async {
|
||||
// Add interceptors in order
|
||||
// 1. Custom Curl interceptor (first to log cURL commands)
|
||||
// Uses debugPrint and developer.log for better visibility
|
||||
..interceptors.add(CustomCurlLoggerInterceptor())
|
||||
// ..interceptors.add(CustomCurlLoggerInterceptor())
|
||||
// 2. Logging interceptor
|
||||
..interceptors.add(ref.watch(loggingInterceptorProvider))
|
||||
// 3. Auth interceptor (add tokens to requests)
|
||||
|
||||
@@ -131,7 +131,7 @@ final class DioProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$dioHash() => r'd15bfe824d6501e5cbd56ff152de978030d97be4';
|
||||
String _$dioHash() => r'f15495e99d11744c245e2be892657748aeeb8ae7';
|
||||
|
||||
/// Provider for DioClient
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class FrappeAuthService {
|
||||
}
|
||||
}
|
||||
|
||||
final url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeLogin}';
|
||||
const url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeLogin}';
|
||||
|
||||
// Build cookie header
|
||||
final storedSession = await getStoredSession();
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
/// Uses Riverpod 3.0 with code generation for type-safe state management.
|
||||
library;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/core/constants/api_constants.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/core/services/frappe_auth_service.dart';
|
||||
import 'package:worker/features/auth/data/datasources/auth_local_datasource.dart';
|
||||
import 'package:worker/features/auth/data/datasources/auth_remote_datasource.dart';
|
||||
import 'package:worker/features/auth/data/models/auth_session_model.dart';
|
||||
import 'package:worker/features/auth/domain/entities/user.dart';
|
||||
|
||||
part 'auth_provider.g.dart';
|
||||
@@ -80,10 +78,6 @@ class Auth extends _$Auth {
|
||||
Future<FrappeAuthService> get _frappeAuthService async =>
|
||||
await ref.read(frappeAuthServiceProvider.future);
|
||||
|
||||
/// Get auth remote data source
|
||||
Future<AuthRemoteDataSource> get _remoteDataSource async =>
|
||||
await ref.read(authRemoteDataSourceProvider.future);
|
||||
|
||||
/// Initialize with saved session if available
|
||||
@override
|
||||
Future<User?> build() async {
|
||||
@@ -170,7 +164,6 @@ class Auth extends _$Auth {
|
||||
}
|
||||
|
||||
final frappeService = await _frappeAuthService;
|
||||
final remoteDataSource = await _remoteDataSource;
|
||||
|
||||
// Get current session (should exist from app startup)
|
||||
final currentSession = await frappeService.getStoredSession();
|
||||
@@ -183,22 +176,8 @@ class Auth extends _$Auth {
|
||||
}
|
||||
}
|
||||
|
||||
// Get stored session again
|
||||
final session = await frappeService.getStoredSession();
|
||||
if (session == null) {
|
||||
throw Exception('Session not available');
|
||||
}
|
||||
|
||||
// Call login API with current session
|
||||
final loginResponse = await remoteDataSource.login(
|
||||
phone: phoneNumber,
|
||||
csrfToken: session['csrfToken']!,
|
||||
sid: session['sid']!,
|
||||
password: password, // Reserved for future use
|
||||
);
|
||||
|
||||
// Update FlutterSecureStorage with new authenticated session
|
||||
await frappeService.login(phoneNumber, password: password);
|
||||
// Call login API and store session
|
||||
final loginResponse = await frappeService.login(phoneNumber, password: password);
|
||||
|
||||
// Save rememberMe preference
|
||||
await _localDataSource.saveRememberMe(rememberMe);
|
||||
|
||||
@@ -272,7 +272,7 @@ final class AuthProvider extends $AsyncNotifierProvider<Auth, User?> {
|
||||
Auth create() => Auth();
|
||||
}
|
||||
|
||||
String _$authHash() => r'f0438cf6eb9eb17c0afc6b23055acd09926b21ae';
|
||||
String _$authHash() => r'd851980cad7a624f00eba69e19d8a4fee22008e7';
|
||||
|
||||
/// Authentication Provider
|
||||
///
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
|
||||
@@ -133,10 +134,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
loading: () => const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
loading: () => _buildPromotionsShimmer(colorScheme),
|
||||
error: (error, stack) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
@@ -241,4 +239,93 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build shimmer loading for promotions section
|
||||
Widget _buildPromotionsShimmer(ColorScheme colorScheme) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title shimmer
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Chương trình ưu đãi',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Cards shimmer
|
||||
SizedBox(
|
||||
height: 210,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: colorScheme.surfaceContainerHighest,
|
||||
highlightColor: colorScheme.surface,
|
||||
child: Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Image placeholder
|
||||
Container(
|
||||
height: 140,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Text placeholders
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 200,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: 140,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
/// Provider: Promotions Provider
|
||||
///
|
||||
/// Manages the state of promotions data using Riverpod.
|
||||
/// Provides access to active promotions throughout the app.
|
||||
/// Uses the same data source as news articles (single API call).
|
||||
///
|
||||
/// Uses AsyncNotifierProvider for automatic loading, error, and data states.
|
||||
library;
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/features/home/data/datasources/home_local_datasource.dart';
|
||||
import 'package:worker/features/home/data/repositories/home_repository_impl.dart';
|
||||
import 'package:worker/features/home/domain/entities/promotion.dart';
|
||||
import 'package:worker/features/home/domain/usecases/get_promotions.dart';
|
||||
import 'package:worker/features/news/presentation/providers/news_provider.dart';
|
||||
|
||||
part 'promotions_provider.g.dart';
|
||||
|
||||
/// Max number of promotions to display on home page
|
||||
const int _maxPromotions = 5;
|
||||
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -31,33 +33,22 @@ part 'promotions_provider.g.dart';
|
||||
/// );
|
||||
/// ```
|
||||
@riverpod
|
||||
class PromotionsNotifier extends _$PromotionsNotifier {
|
||||
@override
|
||||
Future<List<Promotion>> build() async {
|
||||
// Initialize dependencies
|
||||
final localDataSource = const HomeLocalDataSourceImpl();
|
||||
final repository = HomeRepositoryImpl(localDataSource: localDataSource);
|
||||
final useCase = GetPromotions(repository);
|
||||
Future<List<Promotion>> promotions(Ref ref) async {
|
||||
// Use newsArticles provider (same API call, no duplicate request)
|
||||
final articles = await ref.watch(newsArticlesProvider.future);
|
||||
|
||||
// Fetch promotions (only active ones)
|
||||
return await useCase();
|
||||
}
|
||||
// Take max 5 articles and convert to Promotion
|
||||
final limitedArticles = articles.take(_maxPromotions).toList();
|
||||
|
||||
/// Refresh promotions data
|
||||
///
|
||||
/// Forces a refresh from the server (when API is available).
|
||||
/// Updates the cached state with fresh data.
|
||||
Future<void> refresh() async {
|
||||
// Set loading state
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
// Fetch fresh data
|
||||
state = await AsyncValue.guard(() async {
|
||||
final localDataSource = const HomeLocalDataSourceImpl();
|
||||
final repository = HomeRepositoryImpl(localDataSource: localDataSource);
|
||||
final useCase = GetPromotions(repository);
|
||||
|
||||
return await useCase.refresh();
|
||||
});
|
||||
}
|
||||
return limitedArticles.map((article) {
|
||||
final now = DateTime.now();
|
||||
return Promotion(
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
imageUrl: article.imageUrl,
|
||||
startDate: article.publishedDate,
|
||||
endDate: now.add(const Duration(days: 365)), // Always active
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ part of 'promotions_provider.dart';
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -25,13 +26,14 @@ part of 'promotions_provider.dart';
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@ProviderFor(PromotionsNotifier)
|
||||
const promotionsProvider = PromotionsNotifierProvider._();
|
||||
@ProviderFor(promotions)
|
||||
const promotionsProvider = PromotionsProvider._();
|
||||
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -44,12 +46,20 @@ const promotionsProvider = PromotionsNotifierProvider._();
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
final class PromotionsNotifierProvider
|
||||
extends $AsyncNotifierProvider<PromotionsNotifier, List<Promotion>> {
|
||||
|
||||
final class PromotionsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<Promotion>>,
|
||||
List<Promotion>,
|
||||
FutureOr<List<Promotion>>
|
||||
>
|
||||
with $FutureModifier<List<Promotion>>, $FutureProvider<List<Promotion>> {
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
/// Uses the same data source as news articles to avoid duplicate API calls.
|
||||
/// Converts NewsArticle to Promotion entity for display in PromotionSlider.
|
||||
/// Limited to 5 items max.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
@@ -62,7 +72,7 @@ final class PromotionsNotifierProvider
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
const PromotionsNotifierProvider._()
|
||||
const PromotionsProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
@@ -74,48 +84,18 @@ final class PromotionsNotifierProvider
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$promotionsNotifierHash();
|
||||
String debugGetCreateSourceHash() => _$promotionsHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
PromotionsNotifier create() => PromotionsNotifier();
|
||||
}
|
||||
$FutureProviderElement<List<Promotion>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
String _$promotionsNotifierHash() =>
|
||||
r'3cd866c74ba11c6519e9b63521e1757ef117c7a9';
|
||||
|
||||
/// Promotions Provider
|
||||
///
|
||||
/// Fetches and caches the list of active promotions.
|
||||
/// Automatically handles loading, error, and data states.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// // In a ConsumerWidget
|
||||
/// final promotionsAsync = ref.watch(promotionsProvider);
|
||||
///
|
||||
/// promotionsAsync.when(
|
||||
/// data: (promotions) => PromotionSlider(promotions: promotions),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
abstract class _$PromotionsNotifier extends $AsyncNotifier<List<Promotion>> {
|
||||
FutureOr<List<Promotion>> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<AsyncValue<List<Promotion>>, List<Promotion>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<List<Promotion>>, List<Promotion>>,
|
||||
AsyncValue<List<Promotion>>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
FutureOr<List<Promotion>> create(Ref ref) {
|
||||
return promotions(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$promotionsHash() => r'2eac0298d2b84ad5cc50faa6b8a015dbf7b7a1d3';
|
||||
|
||||
@@ -11,7 +11,6 @@ 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_filter_options_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';
|
||||
@@ -36,8 +35,7 @@ class ProductsPage extends ConsumerWidget {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final productsAsync = ref.watch(productsProvider);
|
||||
|
||||
// Preload filter options for better UX when opening filter drawer
|
||||
ref.watch(productFilterOptionsProvider);
|
||||
// Filter options loaded lazily when filter drawer is opened (not here)
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colorScheme.surfaceContainerLowest,
|
||||
@@ -105,8 +103,10 @@ class ProductsPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
// Brand Filter Chips
|
||||
const BrandFilterChips(),
|
||||
// Brand Filter Chips - only show after products are loaded
|
||||
productsAsync.hasValue
|
||||
? const BrandFilterChips()
|
||||
: const SizedBox(height: 48.0),
|
||||
|
||||
// Products Grid
|
||||
Expanded(
|
||||
|
||||
@@ -86,15 +86,25 @@ class Products extends _$Products {
|
||||
// Get repository with injected data sources
|
||||
final repository = await ref.watch(productsRepositoryProvider.future);
|
||||
|
||||
// Fetch first page of products using unified API
|
||||
// Fetch first page of products
|
||||
List<Product> products;
|
||||
|
||||
// Build filter parameters from filter drawer
|
||||
// Check if any filters or search are active
|
||||
final hasFilters = filters.hasActiveFilters;
|
||||
final hasSearch = searchQuery.isNotEmpty;
|
||||
|
||||
if (!hasFilters && !hasSearch) {
|
||||
// No filters/search: Use simple getAllProducts for faster initial load
|
||||
products = await repository.getAllProducts(
|
||||
limitStart: 0,
|
||||
limitPageLength: pageSize,
|
||||
);
|
||||
} else {
|
||||
// Filters/search active: Use getProductsWithFilters
|
||||
final List<String>? itemGroups = filters.productLines.isNotEmpty
|
||||
? filters.productLines.toList()
|
||||
: null;
|
||||
|
||||
// Use brands from productFiltersProvider (shared by chips and drawer)
|
||||
final List<String>? brands = filters.brands.isNotEmpty
|
||||
? filters.brands.toList()
|
||||
: null;
|
||||
@@ -102,24 +112,20 @@ class Products extends _$Products {
|
||||
// Build item attributes from filter drawer (sizes, surfaces, colors)
|
||||
final List<Map<String, String>> itemAttributes = [];
|
||||
|
||||
// Add size attributes
|
||||
for (final size in filters.sizes) {
|
||||
itemAttributes.add({'attribute': 'Kích thước', 'attribute_value': size});
|
||||
}
|
||||
|
||||
// Add surface attributes
|
||||
for (final surface in filters.surfaces) {
|
||||
itemAttributes.add({'attribute': 'Bề mặt', 'attribute_value': surface});
|
||||
}
|
||||
|
||||
// Add color attributes
|
||||
for (final color in filters.colors) {
|
||||
itemAttributes.add({'attribute': 'Màu sắc', 'attribute_value': color});
|
||||
}
|
||||
|
||||
final String? keyword = searchQuery.isNotEmpty ? searchQuery : null;
|
||||
final String? keyword = hasSearch ? searchQuery : null;
|
||||
|
||||
// Use the comprehensive getProductsWithFilters method
|
||||
products = await repository.getProductsWithFilters(
|
||||
limitStart: 0,
|
||||
limitPageLength: pageSize,
|
||||
@@ -128,6 +134,7 @@ class Products extends _$Products {
|
||||
itemAttributes: itemAttributes.isNotEmpty ? itemAttributes : null,
|
||||
searchKeyword: keyword,
|
||||
);
|
||||
}
|
||||
|
||||
// If we got less than pageSize, there are no more products
|
||||
_hasMore = products.length >= pageSize;
|
||||
@@ -149,38 +156,45 @@ class Products extends _$Products {
|
||||
// Calculate pagination parameters
|
||||
final limitStart = _currentPage * pageSize;
|
||||
|
||||
// Build filter parameters (same logic as build() method)
|
||||
// Check if any filters or search are active
|
||||
final hasFilters = filters.hasActiveFilters;
|
||||
final hasSearch = searchQuery.isNotEmpty;
|
||||
|
||||
List<Product> newProducts;
|
||||
|
||||
if (!hasFilters && !hasSearch) {
|
||||
// No filters/search: Use simple getAllProducts
|
||||
newProducts = await repository.getAllProducts(
|
||||
limitStart: limitStart,
|
||||
limitPageLength: pageSize,
|
||||
);
|
||||
} else {
|
||||
// Filters/search active: Use getProductsWithFilters
|
||||
final List<String>? itemGroups = filters.productLines.isNotEmpty
|
||||
? filters.productLines.toList()
|
||||
: null;
|
||||
|
||||
// Use brands from productFiltersProvider (shared by chips and drawer)
|
||||
final List<String>? brands = filters.brands.isNotEmpty
|
||||
? filters.brands.toList()
|
||||
: null;
|
||||
|
||||
// Build item attributes from filter drawer (sizes, surfaces, colors)
|
||||
final List<Map<String, String>> itemAttributes = [];
|
||||
|
||||
// Add size attributes
|
||||
for (final size in filters.sizes) {
|
||||
itemAttributes.add({'attribute': 'Kích thước', 'attribute_value': size});
|
||||
}
|
||||
|
||||
// Add surface attributes
|
||||
for (final surface in filters.surfaces) {
|
||||
itemAttributes.add({'attribute': 'Bề mặt', 'attribute_value': surface});
|
||||
}
|
||||
|
||||
// Add color attributes
|
||||
for (final color in filters.colors) {
|
||||
itemAttributes.add({'attribute': 'Màu sắc', 'attribute_value': color});
|
||||
}
|
||||
|
||||
final String? keyword = searchQuery.isNotEmpty ? searchQuery : null;
|
||||
final String? keyword = hasSearch ? searchQuery : null;
|
||||
|
||||
// Fetch next page using unified API
|
||||
final newProducts = await repository.getProductsWithFilters(
|
||||
newProducts = await repository.getProductsWithFilters(
|
||||
limitStart: limitStart,
|
||||
limitPageLength: pageSize,
|
||||
itemGroups: itemGroups,
|
||||
@@ -188,6 +202,7 @@ class Products extends _$Products {
|
||||
itemAttributes: itemAttributes.isNotEmpty ? itemAttributes : null,
|
||||
searchKeyword: keyword,
|
||||
);
|
||||
}
|
||||
|
||||
// If we got less than pageSize, there are no more products
|
||||
_hasMore = newProducts.length >= pageSize;
|
||||
|
||||
@@ -228,7 +228,7 @@ final class ProductsProvider
|
||||
Products create() => Products();
|
||||
}
|
||||
|
||||
String _$productsHash() => r'6c55b22e75b912281feff3a68f84e488ccb7ab79';
|
||||
String _$productsHash() => r'a4f416712cdbf2e633622c65b1fdc95686e31fa4';
|
||||
|
||||
/// Products Provider
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user