update products
This commit is contained in:
10
lib/core/providers/dio_client_provider.dart
Normal file
10
lib/core/providers/dio_client_provider.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import '../network/dio_client.dart';
|
||||||
|
|
||||||
|
part 'dio_client_provider.g.dart';
|
||||||
|
|
||||||
|
/// Provider for DioClient singleton
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
DioClient dioClient(Ref ref) {
|
||||||
|
return DioClient();
|
||||||
|
}
|
||||||
55
lib/core/providers/dio_client_provider.g.dart
Normal file
55
lib/core/providers/dio_client_provider.g.dart
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'dio_client_provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
/// Provider for DioClient singleton
|
||||||
|
|
||||||
|
@ProviderFor(dioClient)
|
||||||
|
const dioClientProvider = DioClientProvider._();
|
||||||
|
|
||||||
|
/// Provider for DioClient singleton
|
||||||
|
|
||||||
|
final class DioClientProvider
|
||||||
|
extends $FunctionalProvider<DioClient, DioClient, DioClient>
|
||||||
|
with $Provider<DioClient> {
|
||||||
|
/// Provider for DioClient singleton
|
||||||
|
const DioClientProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'dioClientProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$dioClientHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<DioClient> $createElement($ProviderPointer pointer) =>
|
||||||
|
$ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DioClient create(Ref ref) {
|
||||||
|
return dioClient(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(DioClient value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<DioClient>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$dioClientHash() => r'895f0dc2f8d5eab562ad65390e5c6d4a1f722b0d';
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
/// Export all core providers
|
/// Export all core providers
|
||||||
export 'network_info_provider.dart';
|
export 'network_info_provider.dart';
|
||||||
export 'sync_status_provider.dart';
|
export 'sync_status_provider.dart';
|
||||||
|
export 'dio_client_provider.dart';
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class EmptyState extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
icon ?? Icons.inbox_outlined,
|
icon ?? Icons.inbox_outlined,
|
||||||
size: 80,
|
size: 50,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ final class AuthProvider extends $NotifierProvider<Auth, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$authHash() => r'4b053a7691f573316a8957577dd27a3ed73d89be';
|
String _$authHash() => r'73c9e7b70799eba2904eb6fc65454332d4146a33';
|
||||||
|
|
||||||
/// Auth state notifier provider
|
/// Auth state notifier provider
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import '../models/product_model.dart';
|
import '../models/product_model.dart';
|
||||||
import '../../../../core/network/dio_client.dart';
|
import '../../../../core/network/dio_client.dart';
|
||||||
import '../../../../core/constants/api_constants.dart';
|
import '../../../../core/constants/api_constants.dart';
|
||||||
|
import '../../../../core/errors/exceptions.dart';
|
||||||
|
|
||||||
/// Product remote data source using API
|
/// Product remote data source using API
|
||||||
abstract class ProductRemoteDataSource {
|
abstract class ProductRemoteDataSource {
|
||||||
Future<List<ProductModel>> getAllProducts();
|
Future<List<ProductModel>> getAllProducts({
|
||||||
|
int page = 1,
|
||||||
|
int limit = 20,
|
||||||
|
String? categoryId,
|
||||||
|
String? search,
|
||||||
|
});
|
||||||
Future<ProductModel> getProductById(String id);
|
Future<ProductModel> getProductById(String id);
|
||||||
Future<List<ProductModel>> searchProducts(String query);
|
Future<List<ProductModel>> searchProducts(String query, {int page = 1, int limit = 20});
|
||||||
|
Future<List<ProductModel>> getProductsByCategory(String categoryId, {int page = 1, int limit = 20});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProductRemoteDataSourceImpl implements ProductRemoteDataSource {
|
class ProductRemoteDataSourceImpl implements ProductRemoteDataSource {
|
||||||
@@ -15,25 +22,107 @@ class ProductRemoteDataSourceImpl implements ProductRemoteDataSource {
|
|||||||
ProductRemoteDataSourceImpl(this.client);
|
ProductRemoteDataSourceImpl(this.client);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<ProductModel>> getAllProducts() async {
|
Future<List<ProductModel>> getAllProducts({
|
||||||
final response = await client.get(ApiConstants.products);
|
int page = 1,
|
||||||
final List<dynamic> data = response.data['products'] ?? [];
|
int limit = 20,
|
||||||
|
String? categoryId,
|
||||||
|
String? search,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final queryParams = <String, dynamic>{
|
||||||
|
'page': page,
|
||||||
|
'limit': limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (categoryId != null) {
|
||||||
|
queryParams['categoryId'] = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search != null && search.isNotEmpty) {
|
||||||
|
queryParams['search'] = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
ApiConstants.products,
|
||||||
|
queryParameters: queryParams,
|
||||||
|
);
|
||||||
|
|
||||||
|
// API returns: { success: true, data: [...products...], meta: {...} }
|
||||||
|
if (response.data['success'] == true) {
|
||||||
|
final List<dynamic> data = response.data['data'] ?? [];
|
||||||
return data.map((json) => ProductModel.fromJson(json)).toList();
|
return data.map((json) => ProductModel.fromJson(json)).toList();
|
||||||
|
} else {
|
||||||
|
throw ServerException(response.data['message'] ?? 'Failed to fetch products');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ServerException) rethrow;
|
||||||
|
throw ServerException('Failed to fetch products: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ProductModel> getProductById(String id) async {
|
Future<ProductModel> getProductById(String id) async {
|
||||||
|
try {
|
||||||
final response = await client.get(ApiConstants.productById(id));
|
final response = await client.get(ApiConstants.productById(id));
|
||||||
return ProductModel.fromJson(response.data);
|
|
||||||
|
// API returns: { success: true, data: {...product...} }
|
||||||
|
if (response.data['success'] == true) {
|
||||||
|
return ProductModel.fromJson(response.data['data']);
|
||||||
|
} else {
|
||||||
|
throw ServerException(response.data['message'] ?? 'Product not found');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ServerException) rethrow;
|
||||||
|
throw ServerException('Failed to fetch product: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<ProductModel>> searchProducts(String query) async {
|
Future<List<ProductModel>> searchProducts(String query, {int page = 1, int limit = 20}) async {
|
||||||
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
ApiConstants.searchProducts,
|
ApiConstants.searchProducts,
|
||||||
queryParameters: {'q': query},
|
queryParameters: {
|
||||||
|
'q': query,
|
||||||
|
'page': page,
|
||||||
|
'limit': limit,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
final List<dynamic> data = response.data['products'] ?? [];
|
|
||||||
|
// API returns: { success: true, data: [...products...], meta: {...} }
|
||||||
|
if (response.data['success'] == true) {
|
||||||
|
final List<dynamic> data = response.data['data'] ?? [];
|
||||||
return data.map((json) => ProductModel.fromJson(json)).toList();
|
return data.map((json) => ProductModel.fromJson(json)).toList();
|
||||||
|
} else {
|
||||||
|
throw ServerException(response.data['message'] ?? 'Failed to search products');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ServerException) rethrow;
|
||||||
|
throw ServerException('Failed to search products: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ProductModel>> getProductsByCategory(String categoryId, {int page = 1, int limit = 20}) async {
|
||||||
|
try {
|
||||||
|
final response = await client.get(
|
||||||
|
ApiConstants.productsByCategory(categoryId),
|
||||||
|
queryParameters: {
|
||||||
|
'page': page,
|
||||||
|
'limit': limit,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// API returns: { success: true, data: [...products...], meta: {...} }
|
||||||
|
if (response.data['success'] == true) {
|
||||||
|
final List<dynamic> data = response.data['data'] ?? [];
|
||||||
|
return data.map((json) => ProductModel.fromJson(json)).toList();
|
||||||
|
} else {
|
||||||
|
throw ServerException(response.data['message'] ?? 'Failed to fetch products by category');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ServerException) rethrow;
|
||||||
|
throw ServerException('Failed to fetch products by category: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ class ProductModel extends HiveObject {
|
|||||||
return ProductModel(
|
return ProductModel(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
description: json['description'] as String,
|
description: json['description'] as String? ?? '',
|
||||||
price: (json['price'] as num).toDouble(),
|
price: (json['price'] as num).toDouble(),
|
||||||
imageUrl: json['imageUrl'] as String?,
|
imageUrl: json['imageUrl'] as String?,
|
||||||
categoryId: json['categoryId'] as String,
|
categoryId: json['categoryId'] as String,
|
||||||
stockQuantity: json['stockQuantity'] as int,
|
stockQuantity: json['stockQuantity'] as int? ?? 0,
|
||||||
isAvailable: json['isAvailable'] as bool,
|
isAvailable: json['isAvailable'] as bool? ?? true,
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
);
|
);
|
||||||
|
|||||||
43
lib/features/products/data/providers/product_providers.dart
Normal file
43
lib/features/products/data/providers/product_providers.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:hive_ce/hive.dart';
|
||||||
|
import '../datasources/product_local_datasource.dart';
|
||||||
|
import '../datasources/product_remote_datasource.dart';
|
||||||
|
import '../repositories/product_repository_impl.dart';
|
||||||
|
import '../models/product_model.dart';
|
||||||
|
import '../../domain/repositories/product_repository.dart';
|
||||||
|
import '../../../../core/providers/providers.dart';
|
||||||
|
import '../../../../core/constants/storage_constants.dart';
|
||||||
|
|
||||||
|
part 'product_providers.g.dart';
|
||||||
|
|
||||||
|
/// Provider for product Hive box
|
||||||
|
@riverpod
|
||||||
|
Box<ProductModel> productBox(Ref ref) {
|
||||||
|
return Hive.box<ProductModel>(StorageConstants.productsBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provider for product local data source
|
||||||
|
@riverpod
|
||||||
|
ProductLocalDataSource productLocalDataSource(Ref ref) {
|
||||||
|
final box = ref.watch(productBoxProvider);
|
||||||
|
return ProductLocalDataSourceImpl(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provider for product remote data source
|
||||||
|
@riverpod
|
||||||
|
ProductRemoteDataSource productRemoteDataSource(Ref ref) {
|
||||||
|
final dioClient = ref.watch(dioClientProvider);
|
||||||
|
return ProductRemoteDataSourceImpl(dioClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provider for product repository
|
||||||
|
@riverpod
|
||||||
|
ProductRepository productRepository(Ref ref) {
|
||||||
|
final localDataSource = ref.watch(productLocalDataSourceProvider);
|
||||||
|
final remoteDataSource = ref.watch(productRemoteDataSourceProvider);
|
||||||
|
|
||||||
|
return ProductRepositoryImpl(
|
||||||
|
localDataSource: localDataSource,
|
||||||
|
remoteDataSource: remoteDataSource,
|
||||||
|
);
|
||||||
|
}
|
||||||
219
lib/features/products/data/providers/product_providers.g.dart
Normal file
219
lib/features/products/data/providers/product_providers.g.dart
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'product_providers.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
/// Provider for product Hive box
|
||||||
|
|
||||||
|
@ProviderFor(productBox)
|
||||||
|
const productBoxProvider = ProductBoxProvider._();
|
||||||
|
|
||||||
|
/// Provider for product Hive box
|
||||||
|
|
||||||
|
final class ProductBoxProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
Box<ProductModel>,
|
||||||
|
Box<ProductModel>,
|
||||||
|
Box<ProductModel>
|
||||||
|
>
|
||||||
|
with $Provider<Box<ProductModel>> {
|
||||||
|
/// Provider for product Hive box
|
||||||
|
const ProductBoxProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'productBoxProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$productBoxHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<Box<ProductModel>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Box<ProductModel> create(Ref ref) {
|
||||||
|
return productBox(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(Box<ProductModel> value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<Box<ProductModel>>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$productBoxHash() => r'68cd21ea28cfc716f34daef17849a0393cdb2b80';
|
||||||
|
|
||||||
|
/// Provider for product local data source
|
||||||
|
|
||||||
|
@ProviderFor(productLocalDataSource)
|
||||||
|
const productLocalDataSourceProvider = ProductLocalDataSourceProvider._();
|
||||||
|
|
||||||
|
/// Provider for product local data source
|
||||||
|
|
||||||
|
final class ProductLocalDataSourceProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
ProductLocalDataSource,
|
||||||
|
ProductLocalDataSource,
|
||||||
|
ProductLocalDataSource
|
||||||
|
>
|
||||||
|
with $Provider<ProductLocalDataSource> {
|
||||||
|
/// Provider for product local data source
|
||||||
|
const ProductLocalDataSourceProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'productLocalDataSourceProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$productLocalDataSourceHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<ProductLocalDataSource> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProductLocalDataSource create(Ref ref) {
|
||||||
|
return productLocalDataSource(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(ProductLocalDataSource value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<ProductLocalDataSource>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$productLocalDataSourceHash() =>
|
||||||
|
r'ef4673055777e8dc8a8419a80548b319789d99f9';
|
||||||
|
|
||||||
|
/// Provider for product remote data source
|
||||||
|
|
||||||
|
@ProviderFor(productRemoteDataSource)
|
||||||
|
const productRemoteDataSourceProvider = ProductRemoteDataSourceProvider._();
|
||||||
|
|
||||||
|
/// Provider for product remote data source
|
||||||
|
|
||||||
|
final class ProductRemoteDataSourceProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
ProductRemoteDataSource,
|
||||||
|
ProductRemoteDataSource,
|
||||||
|
ProductRemoteDataSource
|
||||||
|
>
|
||||||
|
with $Provider<ProductRemoteDataSource> {
|
||||||
|
/// Provider for product remote data source
|
||||||
|
const ProductRemoteDataSourceProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'productRemoteDataSourceProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$productRemoteDataSourceHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<ProductRemoteDataSource> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProductRemoteDataSource create(Ref ref) {
|
||||||
|
return productRemoteDataSource(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(ProductRemoteDataSource value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<ProductRemoteDataSource>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$productRemoteDataSourceHash() =>
|
||||||
|
r'954798907bb0c9baade27b84eaba612a5dec8f68';
|
||||||
|
|
||||||
|
/// Provider for product repository
|
||||||
|
|
||||||
|
@ProviderFor(productRepository)
|
||||||
|
const productRepositoryProvider = ProductRepositoryProvider._();
|
||||||
|
|
||||||
|
/// Provider for product repository
|
||||||
|
|
||||||
|
final class ProductRepositoryProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
ProductRepository,
|
||||||
|
ProductRepository,
|
||||||
|
ProductRepository
|
||||||
|
>
|
||||||
|
with $Provider<ProductRepository> {
|
||||||
|
/// Provider for product repository
|
||||||
|
const ProductRepositoryProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'productRepositoryProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$productRepositoryHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<ProductRepository> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProductRepository create(Ref ref) {
|
||||||
|
return productRepository(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(ProductRepository value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<ProductRepository>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$productRepositoryHash() => r'7c5c5b274ce459add6449c29be822ea04503d3dc';
|
||||||
@@ -25,6 +25,13 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
final selectedCategory = ref.watch(product_providers.selectedCategoryProvider);
|
final selectedCategory = ref.watch(product_providers.selectedCategoryProvider);
|
||||||
final productsAsync = ref.watch(productsProvider);
|
final productsAsync = ref.watch(productsProvider);
|
||||||
|
|
||||||
|
// Debug: Log product loading state
|
||||||
|
productsAsync.whenOrNull(
|
||||||
|
data: (products) => debugPrint('Products loaded: ${products.length} items'),
|
||||||
|
loading: () => debugPrint('Products loading...'),
|
||||||
|
error: (error, stack) => debugPrint('Products error: $error'),
|
||||||
|
);
|
||||||
|
|
||||||
// Get filtered products from the provider
|
// Get filtered products from the provider
|
||||||
final filteredProducts = productsAsync.when(
|
final filteredProducts = productsAsync.when(
|
||||||
data: (products) => products,
|
data: (products) => products,
|
||||||
@@ -168,9 +175,11 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: productsAsync.when(
|
||||||
|
data: (products) => RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await ref.refresh(productsProvider.future);
|
// Force sync with API
|
||||||
|
await ref.read(productsProvider.notifier).syncProducts();
|
||||||
await ref.refresh(categoriesProvider.future);
|
await ref.refresh(categoriesProvider.future);
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -195,6 +204,23 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, stack) => Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('Error loading products: $error'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => ref.refresh(productsProvider),
|
||||||
|
child: const Text('Retry'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,92 @@
|
|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import '../../domain/entities/product.dart';
|
import '../../domain/entities/product.dart';
|
||||||
|
import '../../data/providers/product_providers.dart';
|
||||||
|
import '../../../../core/providers/providers.dart';
|
||||||
|
|
||||||
part 'products_provider.g.dart';
|
part 'products_provider.g.dart';
|
||||||
|
|
||||||
/// Provider for products list
|
/// Provider for products list with API-first approach
|
||||||
@riverpod
|
@riverpod
|
||||||
class Products extends _$Products {
|
class Products extends _$Products {
|
||||||
@override
|
@override
|
||||||
Future<List<Product>> build() async {
|
Future<List<Product>> build() async {
|
||||||
// TODO: Implement with repository
|
// API-first: Try to load from API first
|
||||||
return [];
|
final repository = ref.watch(productRepositoryProvider);
|
||||||
|
final networkInfo = ref.watch(networkInfoProvider);
|
||||||
|
|
||||||
|
// Check if online
|
||||||
|
final isConnected = await networkInfo.isConnected;
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
|
// Try API first
|
||||||
|
try {
|
||||||
|
final syncResult = await repository.syncProducts();
|
||||||
|
return syncResult.fold(
|
||||||
|
(failure) {
|
||||||
|
// API failed, fallback to cache
|
||||||
|
print('API failed, falling back to cache: ${failure.message}');
|
||||||
|
return _loadFromCache();
|
||||||
|
},
|
||||||
|
(products) => products,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// API error, fallback to cache
|
||||||
|
print('API error, falling back to cache: $e');
|
||||||
|
return _loadFromCache();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Offline, load from cache
|
||||||
|
print('Offline, loading from cache');
|
||||||
|
return _loadFromCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load products from local cache
|
||||||
|
Future<List<Product>> _loadFromCache() async {
|
||||||
|
final repository = ref.read(productRepositoryProvider);
|
||||||
|
final result = await repository.getAllProducts();
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) {
|
||||||
|
print('Cache load failed: ${failure.message}');
|
||||||
|
return <Product>[];
|
||||||
|
},
|
||||||
|
(products) => products,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh products from local storage
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
// TODO: Implement refresh logic
|
|
||||||
state = const AsyncValue.loading();
|
state = const AsyncValue.loading();
|
||||||
state = await AsyncValue.guard(() async {
|
state = await AsyncValue.guard(() async {
|
||||||
// Fetch products from repository
|
final repository = ref.read(productRepositoryProvider);
|
||||||
return [];
|
final result = await repository.getAllProducts();
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(products) => products,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sync products from API and update local storage
|
||||||
Future<void> syncProducts() async {
|
Future<void> syncProducts() async {
|
||||||
// TODO: Implement sync logic with remote data source
|
final networkInfo = ref.read(networkInfoProvider);
|
||||||
|
final isConnected = await networkInfo.isConnected;
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
throw Exception('No internet connection');
|
||||||
|
}
|
||||||
|
|
||||||
state = const AsyncValue.loading();
|
state = const AsyncValue.loading();
|
||||||
state = await AsyncValue.guard(() async {
|
state = await AsyncValue.guard(() async {
|
||||||
// Sync products from API
|
final repository = ref.read(productRepositoryProvider);
|
||||||
return [];
|
final result = await repository.syncProducts();
|
||||||
|
|
||||||
|
return result.fold(
|
||||||
|
(failure) => throw Exception(failure.message),
|
||||||
|
(products) => products,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ part of 'products_provider.dart';
|
|||||||
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
// ignore_for_file: type=lint, type=warning
|
// ignore_for_file: type=lint, type=warning
|
||||||
/// Provider for products list
|
/// Provider for products list with API-first approach
|
||||||
|
|
||||||
@ProviderFor(Products)
|
@ProviderFor(Products)
|
||||||
const productsProvider = ProductsProvider._();
|
const productsProvider = ProductsProvider._();
|
||||||
|
|
||||||
/// Provider for products list
|
/// Provider for products list with API-first approach
|
||||||
final class ProductsProvider
|
final class ProductsProvider
|
||||||
extends $AsyncNotifierProvider<Products, List<Product>> {
|
extends $AsyncNotifierProvider<Products, List<Product>> {
|
||||||
/// Provider for products list
|
/// Provider for products list with API-first approach
|
||||||
const ProductsProvider._()
|
const ProductsProvider._()
|
||||||
: super(
|
: super(
|
||||||
from: null,
|
from: null,
|
||||||
@@ -36,9 +36,9 @@ final class ProductsProvider
|
|||||||
Products create() => Products();
|
Products create() => Products();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$productsHash() => r'9e1d3aaa1d9cf0b4ff03fdfaf4512a7a15336d51';
|
String _$productsHash() => r'0ff8c2de46bb4b1e29678cc811ec121c9fb4c8eb';
|
||||||
|
|
||||||
/// Provider for products list
|
/// Provider for products list with API-first approach
|
||||||
|
|
||||||
abstract class _$Products extends $AsyncNotifier<List<Product>> {
|
abstract class _$Products extends $AsyncNotifier<List<Product>> {
|
||||||
FutureOr<List<Product>> build();
|
FutureOr<List<Product>> build();
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
|
import 'core/constants/storage_constants.dart';
|
||||||
|
import 'features/products/data/models/product_model.dart';
|
||||||
|
import 'features/categories/data/models/category_model.dart';
|
||||||
|
import 'features/home/data/models/cart_item_model.dart';
|
||||||
|
import 'features/home/data/models/transaction_model.dart';
|
||||||
|
import 'features/settings/data/models/app_settings_model.dart';
|
||||||
|
|
||||||
/// Main entry point of the application
|
/// Main entry point of the application
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -12,18 +18,18 @@ void main() async {
|
|||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
|
|
||||||
// Register Hive adapters
|
// Register Hive adapters
|
||||||
// TODO: Register adapters after running code generation
|
Hive.registerAdapter(ProductModelAdapter());
|
||||||
// Hive.registerAdapter(ProductModelAdapter());
|
Hive.registerAdapter(CategoryModelAdapter());
|
||||||
// Hive.registerAdapter(CategoryModelAdapter());
|
Hive.registerAdapter(CartItemModelAdapter());
|
||||||
// Hive.registerAdapter(CartItemModelAdapter());
|
Hive.registerAdapter(TransactionModelAdapter());
|
||||||
// Hive.registerAdapter(AppSettingsModelAdapter());
|
Hive.registerAdapter(AppSettingsModelAdapter());
|
||||||
|
|
||||||
// Open Hive boxes
|
// Open Hive boxes
|
||||||
// TODO: Open boxes after registering adapters
|
await Hive.openBox<ProductModel>(StorageConstants.productsBox);
|
||||||
// await Hive.openBox<ProductModel>(StorageConstants.productsBox);
|
await Hive.openBox<CategoryModel>(StorageConstants.categoriesBox);
|
||||||
// await Hive.openBox<CategoryModel>(StorageConstants.categoriesBox);
|
await Hive.openBox<CartItemModel>(StorageConstants.cartBox);
|
||||||
// await Hive.openBox<CartItemModel>(StorageConstants.cartBox);
|
await Hive.openBox<TransactionModel>(StorageConstants.transactionsBox);
|
||||||
// await Hive.openBox<AppSettingsModel>(StorageConstants.settingsBox);
|
await Hive.openBox<AppSettingsModel>(StorageConstants.settingsBox);
|
||||||
|
|
||||||
// Run the app with Riverpod (no GetIt needed - using Riverpod for DI)
|
// Run the app with Riverpod (no GetIt needed - using Riverpod for DI)
|
||||||
runApp(
|
runApp(
|
||||||
|
|||||||
Reference in New Issue
Block a user