This commit is contained in:
Phuoc Nguyen
2025-10-24 16:20:48 +07:00
parent eaaa9921f5
commit b27c5d7742
17 changed files with 3245 additions and 5 deletions

View File

@@ -0,0 +1,151 @@
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');
}

View File

@@ -0,0 +1,120 @@
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,
);
}
}

View File

@@ -0,0 +1,50 @@
// 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;
}