update cart
This commit is contained in:
285
lib/features/cart/data/repositories/cart_repository_impl.dart
Normal file
285
lib/features/cart/data/repositories/cart_repository_impl.dart
Normal file
@@ -0,0 +1,285 @@
|
||||
/// Repository Implementation: Cart Repository
|
||||
///
|
||||
/// Implements the cart repository interface with:
|
||||
/// - API-first strategy with local fallback
|
||||
/// - Automatic sync between API and local storage
|
||||
/// - Offline queue support
|
||||
/// - Error handling and recovery
|
||||
library;
|
||||
|
||||
import 'package:worker/core/errors/exceptions.dart';
|
||||
import 'package:worker/features/cart/data/datasources/cart_local_datasource.dart';
|
||||
import 'package:worker/features/cart/data/datasources/cart_remote_datasource.dart';
|
||||
import 'package:worker/features/cart/data/models/cart_item_model.dart';
|
||||
import 'package:worker/features/cart/domain/entities/cart_item.dart';
|
||||
import 'package:worker/features/cart/domain/repositories/cart_repository.dart';
|
||||
|
||||
/// Cart Repository Implementation
|
||||
///
|
||||
/// Strategy: API-first with local fallback
|
||||
/// 1. Try API request first
|
||||
/// 2. On success, sync to local storage
|
||||
/// 3. On failure, fallback to local data (for reads)
|
||||
/// 4. Queue failed writes for later sync
|
||||
class CartRepositoryImpl implements CartRepository {
|
||||
CartRepositoryImpl({
|
||||
required CartRemoteDataSource remoteDataSource,
|
||||
required CartLocalDataSource localDataSource,
|
||||
}) : _remoteDataSource = remoteDataSource,
|
||||
_localDataSource = localDataSource;
|
||||
|
||||
final CartRemoteDataSource _remoteDataSource;
|
||||
final CartLocalDataSource _localDataSource;
|
||||
|
||||
@override
|
||||
Future<List<CartItem>> addToCart({
|
||||
required List<String> itemIds,
|
||||
required List<double> quantities,
|
||||
required List<double> prices,
|
||||
}) async {
|
||||
try {
|
||||
// Validate input
|
||||
if (itemIds.length != quantities.length || itemIds.length != prices.length) {
|
||||
throw const ValidationException(
|
||||
'Item IDs, quantities, and prices must have the same length',
|
||||
);
|
||||
}
|
||||
|
||||
// Build API request items
|
||||
final items = <Map<String, dynamic>>[];
|
||||
for (int i = 0; i < itemIds.length; i++) {
|
||||
items.add({
|
||||
'item_id': itemIds[i],
|
||||
'quantity': quantities[i],
|
||||
'amount': prices[i],
|
||||
});
|
||||
}
|
||||
|
||||
// Try API first
|
||||
try {
|
||||
final cartItemModels = await _remoteDataSource.addToCart(items: items);
|
||||
|
||||
// Sync to local storage
|
||||
await _localDataSource.saveCartItems(cartItemModels);
|
||||
|
||||
// Convert to domain entities
|
||||
return cartItemModels.map(_modelToEntity).toList();
|
||||
} on NetworkException catch (e) {
|
||||
// If no internet, add to local cart only
|
||||
if (e is NoInternetException || e is TimeoutException) {
|
||||
// Add items to local cart
|
||||
for (int i = 0; i < itemIds.length; i++) {
|
||||
final cartItemModel = _createCartItemModel(
|
||||
productId: itemIds[i],
|
||||
quantity: quantities[i],
|
||||
unitPrice: prices[i],
|
||||
);
|
||||
await _localDataSource.addCartItem(cartItemModel);
|
||||
}
|
||||
|
||||
// TODO: Queue for sync when online
|
||||
|
||||
// Return local cart items
|
||||
final localItems = await _localDataSource.getCartItems();
|
||||
return localItems.map(_modelToEntity).toList();
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
} on StorageException {
|
||||
rethrow;
|
||||
} on ValidationException {
|
||||
rethrow;
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} on NetworkException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
throw UnknownException('Failed to add items to cart', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> removeFromCart({
|
||||
required List<String> itemIds,
|
||||
}) async {
|
||||
try {
|
||||
// Try API first
|
||||
try {
|
||||
final success = await _remoteDataSource.removeFromCart(itemIds: itemIds);
|
||||
|
||||
if (success) {
|
||||
// Sync to local storage
|
||||
await _localDataSource.removeCartItems(itemIds);
|
||||
}
|
||||
|
||||
return success;
|
||||
} on NetworkException catch (e) {
|
||||
// If no internet, remove from local cart only
|
||||
if (e is NoInternetException || e is TimeoutException) {
|
||||
await _localDataSource.removeCartItems(itemIds);
|
||||
|
||||
// TODO: Queue for sync when online
|
||||
|
||||
return true;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
} on StorageException {
|
||||
rethrow;
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} on NetworkException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
throw UnknownException('Failed to remove items from cart', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<CartItem>> getCartItems() async {
|
||||
try {
|
||||
// Try API first
|
||||
try {
|
||||
final cartItemModels = await _remoteDataSource.getUserCart();
|
||||
|
||||
// Sync to local storage
|
||||
await _localDataSource.saveCartItems(cartItemModels);
|
||||
|
||||
// Convert to domain entities
|
||||
return cartItemModels.map(_modelToEntity).toList();
|
||||
} on NetworkException catch (e) {
|
||||
// If no internet, fallback to local storage
|
||||
if (e is NoInternetException || e is TimeoutException) {
|
||||
final localItems = await _localDataSource.getCartItems();
|
||||
return localItems.map(_modelToEntity).toList();
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
} on StorageException {
|
||||
rethrow;
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} on NetworkException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
throw UnknownException('Failed to get cart items', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<CartItem>> updateQuantity({
|
||||
required String itemId,
|
||||
required double quantity,
|
||||
required double price,
|
||||
}) async {
|
||||
try {
|
||||
// API doesn't have update endpoint, use add with new quantity
|
||||
// This will replace the existing quantity
|
||||
return await addToCart(
|
||||
itemIds: [itemId],
|
||||
quantities: [quantity],
|
||||
prices: [price],
|
||||
);
|
||||
} catch (e) {
|
||||
throw UnknownException('Failed to update cart item quantity', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> clearCart() async {
|
||||
try {
|
||||
// Get all cart items
|
||||
final items = await getCartItems();
|
||||
if (items.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extract item IDs
|
||||
final itemIds = items.map((item) => item.productId).toList();
|
||||
|
||||
// Remove all items
|
||||
return await removeFromCart(itemIds: itemIds);
|
||||
} catch (e) {
|
||||
throw UnknownException('Failed to clear cart', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<double> getCartTotal() async {
|
||||
try {
|
||||
// Try to calculate from API data first
|
||||
try {
|
||||
final items = await getCartItems();
|
||||
return items.fold<double>(
|
||||
0.0,
|
||||
(sum, item) => sum + item.subtotal,
|
||||
);
|
||||
} on NetworkException catch (e) {
|
||||
// If no internet, use local calculation
|
||||
if (e is NoInternetException || e is TimeoutException) {
|
||||
return await _localDataSource.getCartTotal();
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
} catch (e) {
|
||||
throw UnknownException('Failed to get cart total', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getCartItemCount() async {
|
||||
try {
|
||||
// Try to calculate from API data first
|
||||
try {
|
||||
final items = await getCartItems();
|
||||
final totalQuantity = items.fold<double>(
|
||||
0.0,
|
||||
(sum, item) => sum + item.quantity,
|
||||
);
|
||||
return totalQuantity.toInt();
|
||||
} on NetworkException catch (e) {
|
||||
// If no internet, use local calculation
|
||||
if (e is NoInternetException || e is TimeoutException) {
|
||||
return await _localDataSource.getCartItemCount();
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
} catch (e) {
|
||||
throw UnknownException('Failed to get cart item count', e);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Methods
|
||||
// ============================================================================
|
||||
|
||||
/// Convert CartItemModel to CartItem entity
|
||||
CartItem _modelToEntity(CartItemModel model) {
|
||||
return CartItem(
|
||||
cartItemId: model.cartItemId,
|
||||
cartId: model.cartId,
|
||||
productId: model.productId,
|
||||
quantity: model.quantity,
|
||||
unitPrice: model.unitPrice,
|
||||
subtotal: model.subtotal,
|
||||
addedAt: model.addedAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create CartItemModel from parameters
|
||||
CartItemModel _createCartItemModel({
|
||||
required String productId,
|
||||
required double quantity,
|
||||
required double unitPrice,
|
||||
}) {
|
||||
return CartItemModel(
|
||||
cartItemId: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
cartId: 'user_cart',
|
||||
productId: productId,
|
||||
quantity: quantity,
|
||||
unitPrice: unitPrice,
|
||||
subtotal: quantity * unitPrice,
|
||||
addedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user