This commit is contained in:
2025-10-10 22:49:05 +07:00
parent 02941e2234
commit 38c16bf0b9
49 changed files with 2702 additions and 740 deletions

View File

@@ -0,0 +1,166 @@
import 'package:dio/dio.dart';
import '../models/category_model.dart';
import '../../../../core/network/dio_client.dart';
import '../../../../core/network/api_response.dart';
import '../../../../core/constants/api_constants.dart';
import '../../../../core/errors/exceptions.dart';
/// Category remote data source using API
abstract class CategoryRemoteDataSource {
/// Get all categories (public endpoint - no auth required)
Future<List<CategoryModel>> getAllCategories();
/// Get single category by ID (public endpoint - no auth required)
Future<CategoryModel> getCategoryById(String id);
/// Get category with its products with pagination (public endpoint)
/// Returns Map with 'category' and 'products' with pagination info
Future<Map<String, dynamic>> getCategoryWithProducts(
String id,
int page,
int limit,
);
}
class CategoryRemoteDataSourceImpl implements CategoryRemoteDataSource {
final DioClient client;
CategoryRemoteDataSourceImpl(this.client);
@override
Future<List<CategoryModel>> getAllCategories() async {
try {
final response = await client.get(ApiConstants.categories);
// Parse API response using ApiResponse model
final apiResponse = ApiResponse<List<CategoryModel>>.fromJson(
response.data as Map<String, dynamic>,
(data) => (data as List<dynamic>)
.map((json) => CategoryModel.fromJson(json as Map<String, dynamic>))
.toList(),
);
if (!apiResponse.success) {
throw ServerException(
apiResponse.message ?? 'Failed to fetch categories',
);
}
return apiResponse.data;
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Failed to fetch categories: $e');
}
}
@override
Future<CategoryModel> getCategoryById(String id) async {
try {
final response = await client.get(ApiConstants.categoryById(id));
// Parse API response using ApiResponse model
final apiResponse = ApiResponse<CategoryModel>.fromJson(
response.data as Map<String, dynamic>,
(data) => CategoryModel.fromJson(data as Map<String, dynamic>),
);
if (!apiResponse.success) {
throw ServerException(
apiResponse.message ?? 'Failed to fetch category',
);
}
return apiResponse.data;
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Failed to fetch category: $e');
}
}
@override
Future<Map<String, dynamic>> getCategoryWithProducts(
String id,
int page,
int limit,
) async {
try {
final response = await client.get(
'${ApiConstants.categories}/$id/products',
queryParameters: {
'page': page,
'limit': limit,
},
);
// Parse API response - data contains category with nested products
final apiResponse = ApiResponse<Map<String, dynamic>>.fromJson(
response.data as Map<String, dynamic>,
(data) => data as Map<String, dynamic>,
);
if (!apiResponse.success) {
throw ServerException(
apiResponse.message ?? 'Failed to fetch category with products',
);
}
final responseData = apiResponse.data;
// Extract category info (excluding products array)
final categoryData = Map<String, dynamic>.from(responseData);
final products = categoryData.remove('products') as List<dynamic>? ?? [];
// Create category model from remaining data
final category = CategoryModel.fromJson(categoryData);
return {
'category': category,
'products': products,
'meta': apiResponse.meta?.toJson() ?? {},
};
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Failed to fetch category with products: $e');
}
}
/// Handle Dio errors and convert to custom exceptions
Exception _handleDioError(DioException error) {
switch (error.response?.statusCode) {
case ApiConstants.statusBadRequest:
return ValidationException(
error.response?.data['message'] ?? 'Invalid request',
);
case ApiConstants.statusUnauthorized:
return UnauthorizedException(
error.response?.data['message'] ?? 'Unauthorized access',
);
case ApiConstants.statusForbidden:
return UnauthorizedException(
error.response?.data['message'] ?? 'Access forbidden',
);
case ApiConstants.statusNotFound:
return NotFoundException(
error.response?.data['message'] ?? 'Category not found',
);
case ApiConstants.statusInternalServerError:
case ApiConstants.statusBadGateway:
case ApiConstants.statusServiceUnavailable:
return ServerException(
error.response?.data['message'] ?? 'Server error',
);
default:
if (error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.sendTimeout) {
return NetworkException('Connection timeout');
} else if (error.type == DioExceptionType.connectionError) {
return NetworkException('No internet connection');
}
return ServerException('Unexpected error occurred');
}
}
}

View File

@@ -1,6 +1,7 @@
/// Export all categories data sources
///
/// Contains local data sources for categories
/// Contains local and remote data sources for categories
library;
export 'category_local_datasource.dart';
export 'category_remote_datasource.dart';

View File

@@ -27,6 +27,9 @@ class CategoryModel extends HiveObject {
@HiveField(6)
final DateTime createdAt;
@HiveField(7)
final DateTime updatedAt;
CategoryModel({
required this.id,
required this.name,
@@ -35,6 +38,7 @@ class CategoryModel extends HiveObject {
this.color,
required this.productCount,
required this.createdAt,
required this.updatedAt,
});
/// Convert to domain entity
@@ -47,6 +51,7 @@ class CategoryModel extends HiveObject {
color: color,
productCount: productCount,
createdAt: createdAt,
updatedAt: updatedAt,
);
}
@@ -60,6 +65,7 @@ class CategoryModel extends HiveObject {
color: category.color,
productCount: category.productCount,
createdAt: category.createdAt,
updatedAt: category.updatedAt,
);
}
@@ -71,8 +77,9 @@ class CategoryModel extends HiveObject {
description: json['description'] as String?,
iconPath: json['iconPath'] as String?,
color: json['color'] as String?,
productCount: json['productCount'] as int,
productCount: json['productCount'] as int? ?? 0,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
);
}
@@ -86,6 +93,7 @@ class CategoryModel extends HiveObject {
'color': color,
'productCount': productCount,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
}
@@ -98,6 +106,7 @@ class CategoryModel extends HiveObject {
String? color,
int? productCount,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return CategoryModel(
id: id ?? this.id,
@@ -107,6 +116,7 @@ class CategoryModel extends HiveObject {
color: color ?? this.color,
productCount: productCount ?? this.productCount,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

View File

@@ -24,13 +24,14 @@ class CategoryModelAdapter extends TypeAdapter<CategoryModel> {
color: fields[4] as String?,
productCount: (fields[5] as num).toInt(),
createdAt: fields[6] as DateTime,
updatedAt: fields[7] as DateTime,
);
}
@override
void write(BinaryWriter writer, CategoryModel obj) {
writer
..writeByte(7)
..writeByte(8)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -44,7 +45,9 @@ class CategoryModelAdapter extends TypeAdapter<CategoryModel> {
..writeByte(5)
..write(obj.productCount)
..writeByte(6)
..write(obj.createdAt);
..write(obj.createdAt)
..writeByte(7)
..write(obj.updatedAt);
}
@override

View File

@@ -9,6 +9,7 @@ class Category extends Equatable {
final String? color;
final int productCount;
final DateTime createdAt;
final DateTime updatedAt;
const Category({
required this.id,
@@ -18,6 +19,7 @@ class Category extends Equatable {
this.color,
required this.productCount,
required this.createdAt,
required this.updatedAt,
});
@override
@@ -29,5 +31,6 @@ class Category extends Equatable {
color,
productCount,
createdAt,
updatedAt,
];
}

View File

@@ -28,7 +28,7 @@ class CategoriesPage extends ConsumerWidget {
),
body: RefreshIndicator(
onRefresh: () async {
await ref.refresh(categoriesProvider.future);
ref.read(categoriesProvider.notifier).refresh();
},
child: categoriesAsync.when(
loading: () => const Center(

View File

@@ -1,5 +1,9 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../domain/entities/category.dart';
import '../../data/models/category_model.dart';
import '../../../products/data/models/product_model.dart';
import '../../../products/domain/entities/product.dart';
import 'category_remote_datasource_provider.dart';
part 'categories_provider.g.dart';
@@ -8,33 +12,182 @@ part 'categories_provider.g.dart';
class Categories extends _$Categories {
@override
Future<List<Category>> build() async {
// TODO: Implement with repository
return [];
return await _fetchCategories();
}
Future<List<Category>> _fetchCategories() async {
final datasource = ref.read(categoryRemoteDataSourceProvider);
final categoryModels = await datasource.getAllCategories();
return categoryModels.map((model) => model.toEntity()).toList();
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
// Fetch categories from repository
return [];
});
}
Future<void> syncCategories() async {
// TODO: Implement sync logic with remote data source
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
// Sync categories from API
return [];
return await _fetchCategories();
});
}
}
/// Provider for selected category
/// Provider for single category by ID
@riverpod
class SelectedCategory extends _$SelectedCategory {
Future<Category> category(Ref ref, String id) async {
final datasource = ref.read(categoryRemoteDataSourceProvider);
final categoryModel = await datasource.getCategoryById(id);
return categoryModel.toEntity();
}
/// Pagination state for category products
class CategoryProductsState {
final Category category;
final List<Product> products;
final int currentPage;
final int totalPages;
final int totalItems;
final bool hasMore;
final bool isLoadingMore;
const CategoryProductsState({
required this.category,
required this.products,
required this.currentPage,
required this.totalPages,
required this.totalItems,
required this.hasMore,
this.isLoadingMore = false,
});
CategoryProductsState copyWith({
Category? category,
List<Product>? products,
int? currentPage,
int? totalPages,
int? totalItems,
bool? hasMore,
bool? isLoadingMore,
}) {
return CategoryProductsState(
category: category ?? this.category,
products: products ?? this.products,
currentPage: currentPage ?? this.currentPage,
totalPages: totalPages ?? this.totalPages,
totalItems: totalItems ?? this.totalItems,
hasMore: hasMore ?? this.hasMore,
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
);
}
}
/// Provider for category with its products (with pagination)
@riverpod
class CategoryWithProducts extends _$CategoryWithProducts {
static const int _limit = 20;
@override
String? build() => null;
Future<CategoryProductsState> build(String categoryId) async {
return await _fetchCategoryWithProducts(categoryId: categoryId, page: 1);
}
Future<CategoryProductsState> _fetchCategoryWithProducts({
required String categoryId,
required int page,
}) async {
final datasource = ref.read(categoryRemoteDataSourceProvider);
final response = await datasource.getCategoryWithProducts(
categoryId,
page,
_limit,
);
// Extract data
final CategoryModel categoryModel = response['category'] as CategoryModel;
final List<dynamic> productsJson = response['products'] as List<dynamic>;
final meta = response['meta'] as Map<String, dynamic>;
// Convert category to entity
final category = categoryModel.toEntity();
// Convert products to entities
final products = productsJson
.map((json) => ProductModel.fromJson(json as Map<String, dynamic>))
.map((model) => model.toEntity())
.toList();
// Extract pagination info
final currentPage = meta['currentPage'] as int? ?? page;
final totalPages = meta['totalPages'] as int? ?? 1;
final totalItems = meta['totalItems'] as int? ?? products.length;
final hasMore = currentPage < totalPages;
return CategoryProductsState(
category: category,
products: products,
currentPage: currentPage,
totalPages: totalPages,
totalItems: totalItems,
hasMore: hasMore,
);
}
/// Load more products (next page)
Future<void> loadMore() async {
final currentState = state.value;
if (currentState == null || !currentState.hasMore) return;
// Set loading more flag
state = AsyncValue.data(
currentState.copyWith(isLoadingMore: true),
);
// Fetch next page
final nextPage = currentState.currentPage + 1;
try {
final newState = await _fetchCategoryWithProducts(
categoryId: currentState.category.id,
page: nextPage,
);
// Append new products to existing ones
state = AsyncValue.data(
newState.copyWith(
products: [...currentState.products, ...newState.products],
isLoadingMore: false,
),
);
} catch (error, stackTrace) {
// Restore previous state on error
state = AsyncValue.data(
currentState.copyWith(isLoadingMore: false),
);
state = AsyncValue.error(error, stackTrace);
}
}
/// Refresh category and products
Future<void> refresh() async {
final currentState = state.value;
if (currentState == null) return;
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return await _fetchCategoryWithProducts(
categoryId: currentState.category.id,
page: 1,
);
});
}
}
/// Provider for selected category state
/// This is used in the products feature for filtering
@riverpod
class SelectedCategoryInCategories extends _$SelectedCategoryInCategories {
@override
String? build() {
return null;
}
void select(String? categoryId) {
state = categoryId;
@@ -43,4 +196,8 @@ class SelectedCategory extends _$SelectedCategory {
void clear() {
state = null;
}
bool get hasSelection => state != null;
bool isSelected(String categoryId) => state == categoryId;
}

View File

@@ -36,7 +36,7 @@ final class CategoriesProvider
Categories create() => Categories();
}
String _$categoriesHash() => r'aa7afc38a5567b0f42ff05ca23b287baa4780cbe';
String _$categoriesHash() => r'5156d31a6d7b9457c4735b66e170b262140758e2';
/// Provider for categories list
@@ -59,32 +59,223 @@ abstract class _$Categories extends $AsyncNotifier<List<Category>> {
}
}
/// Provider for selected category
/// Provider for single category by ID
@ProviderFor(SelectedCategory)
const selectedCategoryProvider = SelectedCategoryProvider._();
@ProviderFor(category)
const categoryProvider = CategoryFamily._();
/// Provider for selected category
final class SelectedCategoryProvider
extends $NotifierProvider<SelectedCategory, String?> {
/// Provider for selected category
const SelectedCategoryProvider._()
/// Provider for single category by ID
final class CategoryProvider
extends
$FunctionalProvider<AsyncValue<Category>, Category, FutureOr<Category>>
with $FutureModifier<Category>, $FutureProvider<Category> {
/// Provider for single category by ID
const CategoryProvider._({
required CategoryFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'categoryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$categoryHash();
@override
String toString() {
return r'categoryProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<Category> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<Category> create(Ref ref) {
final argument = this.argument as String;
return category(ref, argument);
}
@override
bool operator ==(Object other) {
return other is CategoryProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$categoryHash() => r'e26dd362e42a1217a774072f453a64c7a6195e73';
/// Provider for single category by ID
final class CategoryFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<Category>, String> {
const CategoryFamily._()
: super(
retry: null,
name: r'categoryProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
/// Provider for single category by ID
CategoryProvider call(String id) =>
CategoryProvider._(argument: id, from: this);
@override
String toString() => r'categoryProvider';
}
/// Provider for category with its products (with pagination)
@ProviderFor(CategoryWithProducts)
const categoryWithProductsProvider = CategoryWithProductsFamily._();
/// Provider for category with its products (with pagination)
final class CategoryWithProductsProvider
extends
$AsyncNotifierProvider<CategoryWithProducts, CategoryProductsState> {
/// Provider for category with its products (with pagination)
const CategoryWithProductsProvider._({
required CategoryWithProductsFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'categoryWithProductsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$categoryWithProductsHash();
@override
String toString() {
return r'categoryWithProductsProvider'
''
'($argument)';
}
@$internal
@override
CategoryWithProducts create() => CategoryWithProducts();
@override
bool operator ==(Object other) {
return other is CategoryWithProductsProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$categoryWithProductsHash() =>
r'a5ea35fad4e711ea855e4874f9135145d7d44b67';
/// Provider for category with its products (with pagination)
final class CategoryWithProductsFamily extends $Family
with
$ClassFamilyOverride<
CategoryWithProducts,
AsyncValue<CategoryProductsState>,
CategoryProductsState,
FutureOr<CategoryProductsState>,
String
> {
const CategoryWithProductsFamily._()
: super(
retry: null,
name: r'categoryWithProductsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
/// Provider for category with its products (with pagination)
CategoryWithProductsProvider call(String categoryId) =>
CategoryWithProductsProvider._(argument: categoryId, from: this);
@override
String toString() => r'categoryWithProductsProvider';
}
/// Provider for category with its products (with pagination)
abstract class _$CategoryWithProducts
extends $AsyncNotifier<CategoryProductsState> {
late final _$args = ref.$arg as String;
String get categoryId => _$args;
FutureOr<CategoryProductsState> build(String categoryId);
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final ref =
this.ref
as $Ref<AsyncValue<CategoryProductsState>, CategoryProductsState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<
AsyncValue<CategoryProductsState>,
CategoryProductsState
>,
AsyncValue<CategoryProductsState>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Provider for selected category state
/// This is used in the products feature for filtering
@ProviderFor(SelectedCategoryInCategories)
const selectedCategoryInCategoriesProvider =
SelectedCategoryInCategoriesProvider._();
/// Provider for selected category state
/// This is used in the products feature for filtering
final class SelectedCategoryInCategoriesProvider
extends $NotifierProvider<SelectedCategoryInCategories, String?> {
/// Provider for selected category state
/// This is used in the products feature for filtering
const SelectedCategoryInCategoriesProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedCategoryProvider',
name: r'selectedCategoryInCategoriesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedCategoryHash();
String debugGetCreateSourceHash() => _$selectedCategoryInCategoriesHash();
@$internal
@override
SelectedCategory create() => SelectedCategory();
SelectedCategoryInCategories create() => SelectedCategoryInCategories();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(String? value) {
@@ -95,11 +286,13 @@ final class SelectedCategoryProvider
}
}
String _$selectedCategoryHash() => r'a47cd2de07ad285d4b73b2294ba954cb1cdd8e4c';
String _$selectedCategoryInCategoriesHash() =>
r'510d79a73dcfeba5efa886f5f95f7470dbd09a47';
/// Provider for selected category
/// Provider for selected category state
/// This is used in the products feature for filtering
abstract class _$SelectedCategory extends $Notifier<String?> {
abstract class _$SelectedCategoryInCategories extends $Notifier<String?> {
String? build();
@$mustCallSuper
@override

View File

@@ -1,14 +0,0 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../data/datasources/category_local_datasource.dart';
import '../../../../core/database/hive_database.dart';
import '../../data/models/category_model.dart';
part 'category_datasource_provider.g.dart';
/// Provider for category local data source
/// This is kept alive as it's a dependency injection provider
@Riverpod(keepAlive: true)
CategoryLocalDataSource categoryLocalDataSource(Ref ref) {
final box = HiveDatabase.instance.getBox<CategoryModel>('categories');
return CategoryLocalDataSourceImpl(box);
}

View File

@@ -1,65 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'category_datasource_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Provider for category local data source
/// This is kept alive as it's a dependency injection provider
@ProviderFor(categoryLocalDataSource)
const categoryLocalDataSourceProvider = CategoryLocalDataSourceProvider._();
/// Provider for category local data source
/// This is kept alive as it's a dependency injection provider
final class CategoryLocalDataSourceProvider
extends
$FunctionalProvider<
CategoryLocalDataSource,
CategoryLocalDataSource,
CategoryLocalDataSource
>
with $Provider<CategoryLocalDataSource> {
/// Provider for category local data source
/// This is kept alive as it's a dependency injection provider
const CategoryLocalDataSourceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'categoryLocalDataSourceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$categoryLocalDataSourceHash();
@$internal
@override
$ProviderElement<CategoryLocalDataSource> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
CategoryLocalDataSource create(Ref ref) {
return categoryLocalDataSource(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(CategoryLocalDataSource value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<CategoryLocalDataSource>(value),
);
}
}
String _$categoryLocalDataSourceHash() =>
r'1f8412f2dc76a348873f1da4f76ae4a08991f269';

View File

@@ -1,35 +0,0 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../../products/presentation/providers/products_provider.dart';
part 'category_product_count_provider.g.dart';
/// Provider that calculates product count for a specific category
/// Uses family pattern to create a provider for each category ID
@riverpod
int categoryProductCount(Ref ref, String categoryId) {
final productsAsync = ref.watch(productsProvider);
return productsAsync.when(
data: (products) => products.where((p) => p.categoryId == categoryId).length,
loading: () => 0,
error: (_, __) => 0,
);
}
/// Provider that returns all category product counts as a map
/// Useful for displaying product counts on all category cards at once
@riverpod
Map<String, int> allCategoryProductCounts(Ref ref) {
final productsAsync = ref.watch(productsProvider);
return productsAsync.when(
data: (products) {
// Group products by category and count
final counts = <String, int>{};
for (final product in products) {
counts[product.categoryId] = (counts[product.categoryId] ?? 0) + 1;
}
return counts;
},
loading: () => {},
error: (_, __) => {},
);
}

View File

@@ -1,156 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'category_product_count_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Provider that calculates product count for a specific category
/// Uses family pattern to create a provider for each category ID
@ProviderFor(categoryProductCount)
const categoryProductCountProvider = CategoryProductCountFamily._();
/// Provider that calculates product count for a specific category
/// Uses family pattern to create a provider for each category ID
final class CategoryProductCountProvider
extends $FunctionalProvider<int, int, int>
with $Provider<int> {
/// Provider that calculates product count for a specific category
/// Uses family pattern to create a provider for each category ID
const CategoryProductCountProvider._({
required CategoryProductCountFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'categoryProductCountProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$categoryProductCountHash();
@override
String toString() {
return r'categoryProductCountProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<int> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
int create(Ref ref) {
final argument = this.argument as String;
return categoryProductCount(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(int value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<int>(value),
);
}
@override
bool operator ==(Object other) {
return other is CategoryProductCountProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$categoryProductCountHash() =>
r'2d51eea21a4d018964d10ee00d0957a2c38d28c6';
/// Provider that calculates product count for a specific category
/// Uses family pattern to create a provider for each category ID
final class CategoryProductCountFamily extends $Family
with $FunctionalFamilyOverride<int, String> {
const CategoryProductCountFamily._()
: super(
retry: null,
name: r'categoryProductCountProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
/// Provider that calculates product count for a specific category
/// Uses family pattern to create a provider for each category ID
CategoryProductCountProvider call(String categoryId) =>
CategoryProductCountProvider._(argument: categoryId, from: this);
@override
String toString() => r'categoryProductCountProvider';
}
/// Provider that returns all category product counts as a map
/// Useful for displaying product counts on all category cards at once
@ProviderFor(allCategoryProductCounts)
const allCategoryProductCountsProvider = AllCategoryProductCountsProvider._();
/// Provider that returns all category product counts as a map
/// Useful for displaying product counts on all category cards at once
final class AllCategoryProductCountsProvider
extends
$FunctionalProvider<
Map<String, int>,
Map<String, int>,
Map<String, int>
>
with $Provider<Map<String, int>> {
/// Provider that returns all category product counts as a map
/// Useful for displaying product counts on all category cards at once
const AllCategoryProductCountsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'allCategoryProductCountsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$allCategoryProductCountsHash();
@$internal
@override
$ProviderElement<Map<String, int>> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
Map<String, int> create(Ref ref) {
return allCategoryProductCounts(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Map<String, int> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Map<String, int>>(value),
);
}
}
String _$allCategoryProductCountsHash() =>
r'a4ecc281916772ac74327333bd76e7b6463a0992';

View File

@@ -0,0 +1,13 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../data/datasources/category_remote_datasource.dart';
import '../../../../core/providers/core_providers.dart';
part 'category_remote_datasource_provider.g.dart';
/// Provider for category remote data source
/// This is kept alive as it's a dependency injection provider
@Riverpod(keepAlive: true)
CategoryRemoteDataSource categoryRemoteDataSource(Ref ref) {
final dioClient = ref.watch(dioClientProvider);
return CategoryRemoteDataSourceImpl(dioClient);
}

View File

@@ -0,0 +1,65 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'category_remote_datasource_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Provider for category remote data source
/// This is kept alive as it's a dependency injection provider
@ProviderFor(categoryRemoteDataSource)
const categoryRemoteDataSourceProvider = CategoryRemoteDataSourceProvider._();
/// Provider for category remote data source
/// This is kept alive as it's a dependency injection provider
final class CategoryRemoteDataSourceProvider
extends
$FunctionalProvider<
CategoryRemoteDataSource,
CategoryRemoteDataSource,
CategoryRemoteDataSource
>
with $Provider<CategoryRemoteDataSource> {
/// Provider for category remote data source
/// This is kept alive as it's a dependency injection provider
const CategoryRemoteDataSourceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'categoryRemoteDataSourceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$categoryRemoteDataSourceHash();
@$internal
@override
$ProviderElement<CategoryRemoteDataSource> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
CategoryRemoteDataSource create(Ref ref) {
return categoryRemoteDataSource(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(CategoryRemoteDataSource value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<CategoryRemoteDataSource>(value),
);
}
}
String _$categoryRemoteDataSourceHash() =>
r'45f2893a6fdff7c49802a32a792a94972bb84b06';

View File

@@ -3,11 +3,8 @@
/// Contains Riverpod providers for category state management
library;
export 'category_datasource_provider.dart';
export 'categories_provider.dart';
export 'category_product_count_provider.dart';
// Export datasource providers
export 'category_remote_datasource_provider.dart';
// Note: SelectedCategory provider is defined in categories_provider.dart
// but we avoid exporting it separately to prevent ambiguous exports with
// the products feature. Use selectedCategoryProvider directly from
// categories_provider.dart or from products feature.
// Export state providers
export 'categories_provider.dart';