add favorite
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/features/products/data/models/product_model.dart';
|
||||
|
||||
/// Favorite Products Local DataSource
|
||||
///
|
||||
/// Caches the actual product data from wishlist API.
|
||||
/// This is separate from ProductsLocalDataSource to avoid conflicts.
|
||||
class FavoriteProductsLocalDataSource {
|
||||
/// Get the Hive box for favorite products
|
||||
Box<dynamic> get _box {
|
||||
return Hive.box<dynamic>(HiveBoxNames.favoriteProductsBox);
|
||||
}
|
||||
|
||||
/// Get all favorite products from cache
|
||||
Future<List<ProductModel>> getAllProducts() async {
|
||||
try {
|
||||
final products = _box.values
|
||||
.whereType<ProductModel>()
|
||||
.toList();
|
||||
|
||||
_debugPrint('Loaded ${products.length} favorite products from cache');
|
||||
return products;
|
||||
} catch (e) {
|
||||
_debugPrint('Error getting favorite products: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Save products from wishlist API to cache
|
||||
Future<void> saveProducts(List<ProductModel> products) async {
|
||||
try {
|
||||
// Clear existing cache
|
||||
await _box.clear();
|
||||
|
||||
// Save new products
|
||||
for (final product in products) {
|
||||
await _box.put(product.productId, product);
|
||||
}
|
||||
|
||||
_debugPrint('Cached ${products.length} favorite products');
|
||||
} catch (e) {
|
||||
_debugPrint('Error saving favorite products: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all cached favorite products
|
||||
Future<void> clearAll() async {
|
||||
try {
|
||||
await _box.clear();
|
||||
_debugPrint('Cleared all favorite products cache');
|
||||
} catch (e) {
|
||||
_debugPrint('Error clearing favorite products: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the box is open
|
||||
bool isBoxOpen() {
|
||||
return Hive.isBoxOpen(HiveBoxNames.favoriteProductsBox);
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug print helper
|
||||
void _debugPrint(String message) {
|
||||
// ignore: avoid_print
|
||||
print('[FavoriteProductsLocalDataSource] $message');
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/features/favorites/data/models/favorite_model.dart';
|
||||
|
||||
/// Favorites Local DataSource
|
||||
///
|
||||
/// Handles all local database operations for favorites using Hive.
|
||||
/// Supports multi-user functionality by filtering favorites by userId.
|
||||
class FavoritesLocalDataSource {
|
||||
/// Get the Hive box for favorites
|
||||
Box<dynamic> get _box {
|
||||
return Hive.box<dynamic>(HiveBoxNames.favoriteBox);
|
||||
}
|
||||
|
||||
/// Get all favorites for a specific user
|
||||
///
|
||||
/// Returns a list of [FavoriteModel] filtered by [userId].
|
||||
/// If the box is not open or an error occurs, returns an empty list.
|
||||
Future<List<FavoriteModel>> getAllFavorites(String userId) async {
|
||||
try {
|
||||
final favorites = _box.values
|
||||
.whereType<FavoriteModel>()
|
||||
.where((fav) => fav.userId == userId)
|
||||
.toList();
|
||||
|
||||
// Sort by creation date (newest first)
|
||||
favorites.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
|
||||
return favorites;
|
||||
} catch (e) {
|
||||
debugPrint('[FavoritesLocalDataSource] Error getting favorites: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a favorite to the database
|
||||
///
|
||||
/// Adds a new [FavoriteModel] to the Hive box.
|
||||
/// Uses the favoriteId as the key for efficient lookup.
|
||||
Future<void> addFavorite(FavoriteModel favorite) async {
|
||||
try {
|
||||
await _box.put(favorite.favoriteId, favorite);
|
||||
debugPrint(
|
||||
'[FavoritesLocalDataSource] Added favorite: ${favorite.favoriteId} for user: ${favorite.userId}',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[FavoritesLocalDataSource] Error adding favorite: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a favorite from the database
|
||||
///
|
||||
/// Removes a favorite by finding it with the combination of [productId] and [userId].
|
||||
/// Returns true if the favorite was found and removed, false otherwise.
|
||||
Future<bool> removeFavorite(String productId, String userId) async {
|
||||
try {
|
||||
// Find the favorite by productId and userId
|
||||
final favorites = _box.values
|
||||
.whereType<FavoriteModel>()
|
||||
.where((fav) => fav.productId == productId && fav.userId == userId)
|
||||
.toList();
|
||||
|
||||
if (favorites.isEmpty) {
|
||||
debugPrint(
|
||||
'[FavoritesLocalDataSource] Favorite not found: productId=$productId, userId=$userId',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final favorite = favorites.first;
|
||||
await _box.delete(favorite.favoriteId);
|
||||
debugPrint(
|
||||
'[FavoritesLocalDataSource] Removed favorite: ${favorite.favoriteId} for user: $userId',
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('[FavoritesLocalDataSource] Error removing favorite: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a product is favorited by a user
|
||||
///
|
||||
/// Returns true if the product is in the user's favorites, false otherwise.
|
||||
bool isFavorite(String productId, String userId) {
|
||||
try {
|
||||
return _box.values.whereType<FavoriteModel>().any(
|
||||
(fav) => fav.productId == productId && fav.userId == userId,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[FavoritesLocalDataSource] Error checking favorite: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all favorites for a specific user
|
||||
///
|
||||
/// Removes all favorites for the given [userId].
|
||||
/// Useful for logout or data cleanup scenarios.
|
||||
Future<void> clearFavorites(String userId) async {
|
||||
try {
|
||||
final favoriteIds = _box.values
|
||||
.whereType<FavoriteModel>()
|
||||
.where((fav) => fav.userId == userId)
|
||||
.map((fav) => fav.favoriteId)
|
||||
.toList();
|
||||
|
||||
await _box.deleteAll(favoriteIds);
|
||||
debugPrint(
|
||||
'[FavoritesLocalDataSource] Cleared ${favoriteIds.length} favorites for user: $userId',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[FavoritesLocalDataSource] Error clearing favorites: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the count of favorites for a user
|
||||
///
|
||||
/// Returns the total number of favorites for the given [userId].
|
||||
int getFavoriteCount(String userId) {
|
||||
try {
|
||||
return _box.values
|
||||
.whereType<FavoriteModel>()
|
||||
.where((fav) => fav.userId == userId)
|
||||
.length;
|
||||
} catch (e) {
|
||||
debugPrint('[FavoritesLocalDataSource] Error getting favorite count: $e');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the favorites box is open
|
||||
///
|
||||
/// Returns true if the box is open and ready to use.
|
||||
bool isBoxOpen() {
|
||||
return Hive.isBoxOpen(HiveBoxNames.favoriteBox);
|
||||
}
|
||||
|
||||
/// Compact the favorites box to reduce storage space
|
||||
///
|
||||
/// Should be called periodically to optimize database size.
|
||||
Future<void> compact() async {
|
||||
try {
|
||||
if (isBoxOpen()) {
|
||||
await _box.compact();
|
||||
debugPrint('[FavoritesLocalDataSource] Favorites box compacted');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'[FavoritesLocalDataSource] Error compacting favorites box: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug print helper that works in both Flutter and Dart
|
||||
void debugPrint(String message) {
|
||||
print('[FavoritesLocalDataSource] $message');
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:worker/core/constants/api_constants.dart';
|
||||
import 'package:worker/core/errors/exceptions.dart';
|
||||
import 'package:worker/features/products/data/models/product_model.dart';
|
||||
|
||||
/// Favorites Remote DataSource
|
||||
///
|
||||
/// Handles all API operations for favorites/wishlist using Frappe ERPNext backend.
|
||||
/// Follows the API spec from docs/favorite.sh
|
||||
///
|
||||
/// Note: The API returns Product objects directly, not separate favorite entities.
|
||||
class FavoritesRemoteDataSource {
|
||||
FavoritesRemoteDataSource(this._dio);
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
/// Get all favorites/wishlist items for the current user
|
||||
///
|
||||
/// API: POST /api/method/building_material.building_material.api.item_wishlist.get_list
|
||||
/// Body: { "limit_start": 0, "limit_page_length": 0 }
|
||||
///
|
||||
/// Response format (from docs/favorite.sh):
|
||||
/// ```json
|
||||
/// {
|
||||
/// "message": [
|
||||
/// {
|
||||
/// "name": "GIB20 G04",
|
||||
/// "item_code": "GIB20 G04",
|
||||
/// "item_name": "Gibellina GIB20 G04",
|
||||
/// "item_group_name": "OUTDOOR [20mm]",
|
||||
/// "custom_link_360": "https://...",
|
||||
/// "thumbnail": "https://...",
|
||||
/// "price": 0,
|
||||
/// "currency": "",
|
||||
/// "conversion_of_sm": 5.5556
|
||||
/// }
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Returns list of Product objects that are favorited
|
||||
Future<List<ProductModel>> getFavorites({
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 0, // 0 means get all
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.getFavorites}',
|
||||
data: {
|
||||
'limit_start': limitStart,
|
||||
'limit_page_length': limitPageLength,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.data == null) {
|
||||
throw const ServerException('Response data is null');
|
||||
}
|
||||
|
||||
// Parse response according to Frappe format
|
||||
final data = response.data!;
|
||||
final messageList = data['message'] as List<dynamic>?;
|
||||
|
||||
if (messageList == null || messageList.isEmpty) {
|
||||
_debugPrint('No favorites found');
|
||||
return [];
|
||||
}
|
||||
|
||||
final products = <ProductModel>[];
|
||||
|
||||
// Convert API response - each item is a product object from wishlist
|
||||
for (final item in messageList) {
|
||||
try {
|
||||
final itemMap = item as Map<String, dynamic>;
|
||||
final productModel = ProductModel.fromWishlistApi(itemMap);
|
||||
products.add(productModel);
|
||||
} catch (e) {
|
||||
_debugPrint('Error parsing product: $e');
|
||||
// Continue with other products even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
_debugPrint('Fetched ${products.length} favorite products');
|
||||
return products;
|
||||
} on DioException catch (e) {
|
||||
_handleDioException(e);
|
||||
} catch (e) {
|
||||
_debugPrint('Error getting favorites: $e');
|
||||
throw ServerException('Failed to get favorites: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Add item to wishlist
|
||||
///
|
||||
/// API: POST /api/method/building_material.building_material.api.item_wishlist.add_to_wishlist
|
||||
/// Body: { "item_id": "GIB20 G04" }
|
||||
///
|
||||
/// Returns true if successful
|
||||
Future<bool> addToFavorites(String itemId) async {
|
||||
try {
|
||||
final response = await _dio.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.addToFavorites}',
|
||||
data: {
|
||||
'item_id': itemId,
|
||||
},
|
||||
);
|
||||
|
||||
_debugPrint('Added to favorites: $itemId');
|
||||
return response.statusCode == 200;
|
||||
} on DioException catch (e) {
|
||||
_handleDioException(e);
|
||||
} catch (e) {
|
||||
_debugPrint('Error adding to favorites: $e');
|
||||
throw ServerException('Failed to add to favorites: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove item from wishlist
|
||||
///
|
||||
/// API: POST /api/method/building_material.building_material.api.item_wishlist.remove_from_wishlist
|
||||
/// Body: { "item_id": "GIB20 G04" }
|
||||
///
|
||||
/// Returns true if successful
|
||||
Future<bool> removeFromFavorites(String itemId) async {
|
||||
try {
|
||||
final response = await _dio.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.removeFromFavorites}',
|
||||
data: {
|
||||
'item_id': itemId,
|
||||
},
|
||||
);
|
||||
|
||||
_debugPrint('Removed from favorites: $itemId');
|
||||
return response.statusCode == 200;
|
||||
} on DioException catch (e) {
|
||||
_handleDioException(e);
|
||||
} catch (e) {
|
||||
_debugPrint('Error removing from favorites: $e');
|
||||
throw ServerException('Failed to remove from favorites: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ERROR HANDLING
|
||||
// =========================================================================
|
||||
|
||||
/// Handle Dio exceptions and convert to custom exceptions
|
||||
Never _handleDioException(DioException e) {
|
||||
switch (e.type) {
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.receiveTimeout:
|
||||
throw const NetworkException('Request timeout. Please check your connection.');
|
||||
|
||||
case DioExceptionType.connectionError:
|
||||
throw const NetworkException('No internet connection.');
|
||||
|
||||
case DioExceptionType.badResponse:
|
||||
final statusCode = e.response?.statusCode;
|
||||
final message = e.response?.data?['message'] as String? ??
|
||||
e.response?.data?['error'] as String? ??
|
||||
'Server error';
|
||||
|
||||
if (statusCode == 401) {
|
||||
throw const UnauthorizedException('Unauthorized. Please login again.');
|
||||
} else if (statusCode == 403) {
|
||||
throw const UnauthorizedException('Access forbidden.');
|
||||
} else if (statusCode == 404) {
|
||||
throw const ServerException('Resource not found.');
|
||||
} else if (statusCode != null && statusCode >= 500) {
|
||||
throw ServerException('Server error: $message');
|
||||
} else {
|
||||
throw ServerException(message);
|
||||
}
|
||||
|
||||
case DioExceptionType.cancel:
|
||||
throw const NetworkException('Request was cancelled');
|
||||
|
||||
case DioExceptionType.unknown:
|
||||
case DioExceptionType.badCertificate:
|
||||
throw NetworkException('Network error: ${e.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DEBUG UTILITIES
|
||||
// ============================================================================
|
||||
|
||||
/// Debug print helper
|
||||
void _debugPrint(String message) {
|
||||
// ignore: avoid_print
|
||||
print('[FavoritesRemoteDataSource] $message');
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/features/favorites/domain/entities/favorite.dart';
|
||||
|
||||
part 'favorite_model.g.dart';
|
||||
|
||||
/// Favorite Model
|
||||
///
|
||||
/// Hive CE model for storing user's favorite products locally.
|
||||
/// Maps to the 'favorites' table in the database.
|
||||
///
|
||||
/// Type ID: 28
|
||||
@HiveType(typeId: HiveTypeIds.favoriteModel)
|
||||
class FavoriteModel extends HiveObject {
|
||||
FavoriteModel({
|
||||
required this.favoriteId,
|
||||
required this.productId,
|
||||
required this.userId,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
/// Favorite ID (Primary Key)
|
||||
@HiveField(0)
|
||||
final String favoriteId;
|
||||
|
||||
/// Product ID (Foreign Key)
|
||||
@HiveField(1)
|
||||
final String productId;
|
||||
|
||||
/// User ID (Foreign Key)
|
||||
@HiveField(2)
|
||||
final String userId;
|
||||
|
||||
/// Created timestamp
|
||||
@HiveField(3)
|
||||
final DateTime createdAt;
|
||||
|
||||
// =========================================================================
|
||||
// JSON SERIALIZATION
|
||||
// =========================================================================
|
||||
|
||||
/// Create FavoriteModel from JSON
|
||||
factory FavoriteModel.fromJson(Map<String, dynamic> json) {
|
||||
return FavoriteModel(
|
||||
favoriteId: json['favorite_id'] as String,
|
||||
productId: json['product_id'] as String,
|
||||
userId: json['user_id'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert FavoriteModel to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'favorite_id': favoriteId,
|
||||
'product_id': productId,
|
||||
'user_id': userId,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// COPY WITH
|
||||
// =========================================================================
|
||||
|
||||
/// Create a copy with updated fields
|
||||
FavoriteModel copyWith({
|
||||
String? favoriteId,
|
||||
String? productId,
|
||||
String? userId,
|
||||
DateTime? createdAt,
|
||||
}) {
|
||||
return FavoriteModel(
|
||||
favoriteId: favoriteId ?? this.favoriteId,
|
||||
productId: productId ?? this.productId,
|
||||
userId: userId ?? this.userId,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FavoriteModel(favoriteId: $favoriteId, productId: $productId, userId: $userId)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is FavoriteModel && other.favoriteId == favoriteId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => favoriteId.hashCode;
|
||||
|
||||
// =========================================================================
|
||||
// ENTITY CONVERSION
|
||||
// =========================================================================
|
||||
|
||||
/// Convert FavoriteModel to Favorite entity
|
||||
Favorite toEntity() {
|
||||
return Favorite(
|
||||
favoriteId: favoriteId,
|
||||
productId: productId,
|
||||
userId: userId,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create FavoriteModel from Favorite entity
|
||||
factory FavoriteModel.fromEntity(Favorite favorite) {
|
||||
return FavoriteModel(
|
||||
favoriteId: favorite.favoriteId,
|
||||
productId: favorite.productId,
|
||||
userId: favorite.userId,
|
||||
createdAt: favorite.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'favorite_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class FavoriteModelAdapter extends TypeAdapter<FavoriteModel> {
|
||||
@override
|
||||
final typeId = 28;
|
||||
|
||||
@override
|
||||
FavoriteModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return FavoriteModel(
|
||||
favoriteId: fields[0] as String,
|
||||
productId: fields[1] as String,
|
||||
userId: fields[2] as String,
|
||||
createdAt: fields[3] as DateTime,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, FavoriteModel obj) {
|
||||
writer
|
||||
..writeByte(4)
|
||||
..writeByte(0)
|
||||
..write(obj.favoriteId)
|
||||
..writeByte(1)
|
||||
..write(obj.productId)
|
||||
..writeByte(2)
|
||||
..write(obj.userId)
|
||||
..writeByte(3)
|
||||
..write(obj.createdAt);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FavoriteModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import 'package:worker/core/errors/exceptions.dart';
|
||||
import 'package:worker/core/network/network_info.dart';
|
||||
import 'package:worker/features/favorites/data/datasources/favorite_products_local_datasource.dart';
|
||||
import 'package:worker/features/favorites/data/datasources/favorites_remote_datasource.dart';
|
||||
import 'package:worker/features/favorites/domain/repositories/favorites_repository.dart';
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
|
||||
/// Favorites Repository Implementation
|
||||
///
|
||||
/// Implements the FavoritesRepository interface with online-first approach:
|
||||
/// 1. Always try API first when online
|
||||
/// 2. Cache API responses locally
|
||||
/// 3. Fall back to local cache on network errors
|
||||
class FavoritesRepositoryImpl implements FavoritesRepository {
|
||||
FavoritesRepositoryImpl({
|
||||
required this.remoteDataSource,
|
||||
required this.productsLocalDataSource,
|
||||
required this.networkInfo,
|
||||
});
|
||||
|
||||
final FavoritesRemoteDataSource remoteDataSource;
|
||||
final FavoriteProductsLocalDataSource productsLocalDataSource;
|
||||
final NetworkInfo networkInfo;
|
||||
|
||||
// =========================================================================
|
||||
// GET FAVORITE PRODUCTS (Returns actual Product entities)
|
||||
// =========================================================================
|
||||
|
||||
/// Get favorite products with full product data
|
||||
///
|
||||
/// Online-first: Fetches from API, caches locally
|
||||
/// Offline: Returns cached products
|
||||
@override
|
||||
Future<List<Product>> getFavoriteProducts() async {
|
||||
try {
|
||||
// Online-first: Try to fetch from API
|
||||
if (await networkInfo.isConnected) {
|
||||
_debugPrint('Fetching favorite products from API');
|
||||
|
||||
try {
|
||||
// Get products from wishlist API
|
||||
final remoteProducts = await remoteDataSource.getFavorites();
|
||||
|
||||
// Cache products locally
|
||||
await productsLocalDataSource.saveProducts(remoteProducts);
|
||||
|
||||
_debugPrint('Fetched ${remoteProducts.length} favorite products from API');
|
||||
return remoteProducts.map((model) => model.toEntity()).toList();
|
||||
} on ServerException catch (e) {
|
||||
_debugPrint('API error, falling back to cache: $e');
|
||||
return _getProductsFromCache();
|
||||
} on NetworkException catch (e) {
|
||||
_debugPrint('Network error, falling back to cache: $e');
|
||||
return _getProductsFromCache();
|
||||
}
|
||||
} else {
|
||||
// Offline: Use local cache
|
||||
_debugPrint('Offline - using cached favorite products');
|
||||
return _getProductsFromCache();
|
||||
}
|
||||
} catch (e) {
|
||||
_debugPrint('Error getting favorite products: $e');
|
||||
return _getProductsFromCache();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get favorite products from local cache
|
||||
Future<List<Product>> _getProductsFromCache() async {
|
||||
try {
|
||||
final cachedProducts = await productsLocalDataSource.getAllProducts();
|
||||
_debugPrint('Loaded ${cachedProducts.length} favorite products from cache');
|
||||
return cachedProducts.map((model) => model.toEntity()).toList();
|
||||
} catch (e) {
|
||||
_debugPrint('Error loading from cache: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ADD FAVORITE
|
||||
// =========================================================================
|
||||
|
||||
@override
|
||||
Future<void> addFavorite(String productId) async {
|
||||
try {
|
||||
// Online-first: Try to add via API
|
||||
if (await networkInfo.isConnected) {
|
||||
_debugPrint('Adding favorite via API: $productId');
|
||||
|
||||
try {
|
||||
final success = await remoteDataSource.addToFavorites(productId);
|
||||
|
||||
if (success) {
|
||||
_debugPrint('Added favorite successfully: $productId');
|
||||
} else {
|
||||
throw const ServerException('Failed to add to favorites');
|
||||
}
|
||||
} on ServerException catch (e) {
|
||||
_debugPrint('API error adding favorite: $e');
|
||||
rethrow;
|
||||
} on NetworkException catch (e) {
|
||||
_debugPrint('Network error adding favorite: $e');
|
||||
rethrow;
|
||||
}
|
||||
} else {
|
||||
// Offline: Queue for later sync
|
||||
_debugPrint('Offline - cannot add favorite: $productId');
|
||||
throw const NetworkException('No internet connection');
|
||||
}
|
||||
} catch (e) {
|
||||
_debugPrint('Error adding favorite: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// REMOVE FAVORITE
|
||||
// =========================================================================
|
||||
|
||||
@override
|
||||
Future<bool> removeFavorite(String productId) async {
|
||||
try {
|
||||
// Online-first: Try to remove via API
|
||||
if (await networkInfo.isConnected) {
|
||||
_debugPrint('Removing favorite via API: $productId');
|
||||
|
||||
try {
|
||||
final success = await remoteDataSource.removeFromFavorites(productId);
|
||||
|
||||
if (success) {
|
||||
_debugPrint('Removed favorite successfully: $productId');
|
||||
}
|
||||
|
||||
return success;
|
||||
} on ServerException catch (e) {
|
||||
_debugPrint('API error removing favorite: $e');
|
||||
return false;
|
||||
} on NetworkException catch (e) {
|
||||
_debugPrint('Network error removing favorite: $e');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Offline: Cannot remove
|
||||
_debugPrint('Offline - cannot remove favorite: $productId');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
_debugPrint('Error removing favorite: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DEBUG UTILITIES
|
||||
// ============================================================================
|
||||
|
||||
/// Debug print helper
|
||||
void _debugPrint(String message) {
|
||||
// ignore: avoid_print
|
||||
print('[FavoritesRepository] $message');
|
||||
}
|
||||
Reference in New Issue
Block a user