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,112 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../domain/entities/product.dart';
import 'products_provider.dart';
import 'search_query_provider.dart' as search_providers;
import 'selected_category_provider.dart';
part 'filtered_products_provider.g.dart';
/// Filtered products provider
/// Combines products, search query, and category filter to provide filtered results
@riverpod
class FilteredProducts extends _$FilteredProducts {
@override
List<Product> build() {
// Watch all products
final productsAsync = ref.watch(productsProvider);
final products = productsAsync.when(
data: (data) => data,
loading: () => <Product>[],
error: (_, __) => <Product>[],
);
// Watch search query
final searchQuery = ref.watch(search_providers.searchQueryProvider);
// Watch selected category
final selectedCategory = ref.watch(selectedCategoryProvider);
// Apply filters
return _applyFilters(products, searchQuery, selectedCategory);
}
/// Apply search and category filters to products
List<Product> _applyFilters(
List<Product> products,
String searchQuery,
String? categoryId,
) {
var filtered = products;
// Filter by category if selected
if (categoryId != null) {
filtered = filtered.where((p) => p.categoryId == categoryId).toList();
}
// Filter by search query if present
if (searchQuery.isNotEmpty) {
final lowerQuery = searchQuery.toLowerCase();
filtered = filtered.where((p) {
return p.name.toLowerCase().contains(lowerQuery) ||
p.description.toLowerCase().contains(lowerQuery);
}).toList();
}
return filtered;
}
/// Get available products count
int get availableCount => state.where((p) => p.isAvailable).length;
/// Get out of stock products count
int get outOfStockCount => state.where((p) => !p.isAvailable).length;
}
/// Provider for sorted products
/// Adds sorting capability on top of filtered products
@riverpod
class SortedProducts extends _$SortedProducts {
@override
List<Product> build(ProductSortOption sortOption) {
final filteredProducts = ref.watch(filteredProductsProvider);
return _sortProducts(filteredProducts, sortOption);
}
List<Product> _sortProducts(List<Product> products, ProductSortOption option) {
final sorted = List<Product>.from(products);
switch (option) {
case ProductSortOption.nameAsc:
sorted.sort((a, b) => a.name.compareTo(b.name));
break;
case ProductSortOption.nameDesc:
sorted.sort((a, b) => b.name.compareTo(a.name));
break;
case ProductSortOption.priceAsc:
sorted.sort((a, b) => a.price.compareTo(b.price));
break;
case ProductSortOption.priceDesc:
sorted.sort((a, b) => b.price.compareTo(a.price));
break;
case ProductSortOption.newest:
sorted.sort((a, b) => b.createdAt.compareTo(a.createdAt));
break;
case ProductSortOption.oldest:
sorted.sort((a, b) => a.createdAt.compareTo(b.createdAt));
break;
}
return sorted;
}
}
/// Product sort options
enum ProductSortOption {
nameAsc,
nameDesc,
priceAsc,
priceDesc,
newest,
oldest,
}

View File

@@ -0,0 +1,186 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'filtered_products_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Filtered products provider
/// Combines products, search query, and category filter to provide filtered results
@ProviderFor(FilteredProducts)
const filteredProductsProvider = FilteredProductsProvider._();
/// Filtered products provider
/// Combines products, search query, and category filter to provide filtered results
final class FilteredProductsProvider
extends $NotifierProvider<FilteredProducts, List<Product>> {
/// Filtered products provider
/// Combines products, search query, and category filter to provide filtered results
const FilteredProductsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'filteredProductsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$filteredProductsHash();
@$internal
@override
FilteredProducts create() => FilteredProducts();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<Product> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<Product>>(value),
);
}
}
String _$filteredProductsHash() => r'04d66ed1cb868008cf3e6aba6571f7928a48e814';
/// Filtered products provider
/// Combines products, search query, and category filter to provide filtered results
abstract class _$FilteredProducts extends $Notifier<List<Product>> {
List<Product> build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<List<Product>, List<Product>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<List<Product>, List<Product>>,
List<Product>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Provider for sorted products
/// Adds sorting capability on top of filtered products
@ProviderFor(SortedProducts)
const sortedProductsProvider = SortedProductsFamily._();
/// Provider for sorted products
/// Adds sorting capability on top of filtered products
final class SortedProductsProvider
extends $NotifierProvider<SortedProducts, List<Product>> {
/// Provider for sorted products
/// Adds sorting capability on top of filtered products
const SortedProductsProvider._({
required SortedProductsFamily super.from,
required ProductSortOption super.argument,
}) : super(
retry: null,
name: r'sortedProductsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$sortedProductsHash();
@override
String toString() {
return r'sortedProductsProvider'
''
'($argument)';
}
@$internal
@override
SortedProducts create() => SortedProducts();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<Product> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<Product>>(value),
);
}
@override
bool operator ==(Object other) {
return other is SortedProductsProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$sortedProductsHash() => r'653f1e9af8c188631dadbfe9ed7d944c6876d2d3';
/// Provider for sorted products
/// Adds sorting capability on top of filtered products
final class SortedProductsFamily extends $Family
with
$ClassFamilyOverride<
SortedProducts,
List<Product>,
List<Product>,
List<Product>,
ProductSortOption
> {
const SortedProductsFamily._()
: super(
retry: null,
name: r'sortedProductsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
/// Provider for sorted products
/// Adds sorting capability on top of filtered products
SortedProductsProvider call(ProductSortOption sortOption) =>
SortedProductsProvider._(argument: sortOption, from: this);
@override
String toString() => r'sortedProductsProvider';
}
/// Provider for sorted products
/// Adds sorting capability on top of filtered products
abstract class _$SortedProducts extends $Notifier<List<Product>> {
late final _$args = ref.$arg as ProductSortOption;
ProductSortOption get sortOption => _$args;
List<Product> build(ProductSortOption sortOption);
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final ref = this.ref as $Ref<List<Product>, List<Product>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<List<Product>, List<Product>>,
List<Product>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -0,0 +1,57 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../domain/entities/product.dart';
part 'products_provider.g.dart';
/// Provider for products list
@riverpod
class Products extends _$Products {
@override
Future<List<Product>> build() async {
// TODO: Implement with repository
return [];
}
Future<void> refresh() async {
// TODO: Implement refresh logic
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
// Fetch products from repository
return [];
});
}
Future<void> syncProducts() async {
// TODO: Implement sync logic with remote data source
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
// Sync products from API
return [];
});
}
}
/// Provider for search query
@riverpod
class SearchQuery extends _$SearchQuery {
@override
String build() => '';
void setQuery(String query) {
state = query;
}
}
/// Provider for filtered products
@riverpod
List<Product> filteredProducts(Ref ref) {
final products = ref.watch(productsProvider).value ?? [];
final query = ref.watch(searchQueryProvider);
if (query.isEmpty) return products;
return products.where((p) =>
p.name.toLowerCase().contains(query.toLowerCase()) ||
p.description.toLowerCase().contains(query.toLowerCase())
).toList();
}

View File

@@ -0,0 +1,164 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'products_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Provider for products list
@ProviderFor(Products)
const productsProvider = ProductsProvider._();
/// Provider for products list
final class ProductsProvider
extends $AsyncNotifierProvider<Products, List<Product>> {
/// Provider for products list
const ProductsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'productsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$productsHash();
@$internal
@override
Products create() => Products();
}
String _$productsHash() => r'9e1d3aaa1d9cf0b4ff03fdfaf4512a7a15336d51';
/// Provider for products list
abstract class _$Products extends $AsyncNotifier<List<Product>> {
FutureOr<List<Product>> build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<AsyncValue<List<Product>>, List<Product>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<List<Product>>, List<Product>>,
AsyncValue<List<Product>>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Provider for search query
@ProviderFor(SearchQuery)
const searchQueryProvider = SearchQueryProvider._();
/// Provider for search query
final class SearchQueryProvider extends $NotifierProvider<SearchQuery, String> {
/// Provider for search query
const SearchQueryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'searchQueryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchQueryHash();
@$internal
@override
SearchQuery create() => SearchQuery();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(String value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<String>(value),
);
}
}
String _$searchQueryHash() => r'2c146927785523a0ddf51b23b777a9be4afdc092';
/// Provider for search query
abstract class _$SearchQuery extends $Notifier<String> {
String build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<String, String>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<String, String>,
String,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Provider for filtered products
@ProviderFor(filteredProducts)
const filteredProductsProvider = FilteredProductsProvider._();
/// Provider for filtered products
final class FilteredProductsProvider
extends $FunctionalProvider<List<Product>, List<Product>, List<Product>>
with $Provider<List<Product>> {
/// Provider for filtered products
const FilteredProductsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'filteredProductsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$filteredProductsHash();
@$internal
@override
$ProviderElement<List<Product>> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
List<Product> create(Ref ref) {
return filteredProducts(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<Product> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<Product>>(value),
);
}
}
String _$filteredProductsHash() => r'e4e0c549c454576fc599713a5237435a8dd4b277';

View File

@@ -0,0 +1,27 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'search_query_provider.g.dart';
/// Search query state provider
/// Manages the current search query string for product filtering
@riverpod
class SearchQuery extends _$SearchQuery {
@override
String build() {
// Initialize with empty search query
return '';
}
/// Update search query
void setQuery(String query) {
state = query.trim();
}
/// Clear search query
void clear() {
state = '';
}
/// Check if search is active
bool get isSearching => state.isNotEmpty;
}

View File

@@ -0,0 +1,71 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'search_query_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Search query state provider
/// Manages the current search query string for product filtering
@ProviderFor(SearchQuery)
const searchQueryProvider = SearchQueryProvider._();
/// Search query state provider
/// Manages the current search query string for product filtering
final class SearchQueryProvider extends $NotifierProvider<SearchQuery, String> {
/// Search query state provider
/// Manages the current search query string for product filtering
const SearchQueryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'searchQueryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchQueryHash();
@$internal
@override
SearchQuery create() => SearchQuery();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(String value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<String>(value),
);
}
}
String _$searchQueryHash() => r'62191c640ca9424065338a26c1af5c4695a46ef5';
/// Search query state provider
/// Manages the current search query string for product filtering
abstract class _$SearchQuery extends $Notifier<String> {
String build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<String, String>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<String, String>,
String,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -0,0 +1,30 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_category_provider.g.dart';
/// Selected category state provider
/// Manages the currently selected category for product filtering
@riverpod
class SelectedCategory extends _$SelectedCategory {
@override
String? build() {
// Initialize with no category selected (show all products)
return null;
}
/// Select a category
void selectCategory(String? categoryId) {
state = categoryId;
}
/// Clear category selection (show all products)
void clearSelection() {
state = null;
}
/// Check if a category is selected
bool get hasSelection => state != null;
/// Check if specific category is selected
bool isSelected(String categoryId) => state == categoryId;
}

View File

@@ -0,0 +1,72 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_category_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Selected category state provider
/// Manages the currently selected category for product filtering
@ProviderFor(SelectedCategory)
const selectedCategoryProvider = SelectedCategoryProvider._();
/// Selected category state provider
/// Manages the currently selected category for product filtering
final class SelectedCategoryProvider
extends $NotifierProvider<SelectedCategory, String?> {
/// Selected category state provider
/// Manages the currently selected category for product filtering
const SelectedCategoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedCategoryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedCategoryHash();
@$internal
@override
SelectedCategory create() => SelectedCategory();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(String? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<String?>(value),
);
}
}
String _$selectedCategoryHash() => r'73e33604e69d2e9f9127f21e6784c5fe8ddf4869';
/// Selected category state provider
/// Manages the currently selected category for product filtering
abstract class _$SelectedCategory extends $Notifier<String?> {
String? build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<String?, String?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<String?, String?>,
String?,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}