This commit is contained in:
2025-09-16 23:14:35 +07:00
parent be2ad0a8fd
commit 9ebe7c2919
55 changed files with 5953 additions and 893 deletions

View File

@@ -0,0 +1,229 @@
import 'package:hive_ce/hive.dart';
import '../../../../core/errors/exceptions.dart';
import '../models/scan_item.dart';
/// Abstract local data source for scanner operations
abstract class ScannerLocalDataSource {
/// Save scan to local storage
Future<void> saveScan(ScanItem scan);
/// Get all scans from local storage
Future<List<ScanItem>> getAllScans();
/// Get scan by barcode from local storage
Future<ScanItem?> getScanByBarcode(String barcode);
/// Update scan in local storage
Future<void> updateScan(ScanItem scan);
/// Delete scan from local storage
Future<void> deleteScan(String barcode);
/// Clear all scans from local storage
Future<void> clearAllScans();
}
/// Implementation of ScannerLocalDataSource using Hive
class ScannerLocalDataSourceImpl implements ScannerLocalDataSource {
static const String _boxName = 'scans';
Box<ScanItem>? _box;
/// Initialize Hive box
Future<Box<ScanItem>> _getBox() async {
if (_box == null || !_box!.isOpen) {
try {
_box = await Hive.openBox<ScanItem>(_boxName);
} catch (e) {
throw CacheException('Failed to open Hive box: ${e.toString()}');
}
}
return _box!;
}
@override
Future<void> saveScan(ScanItem scan) async {
try {
final box = await _getBox();
// Use barcode as key to avoid duplicates
await box.put(scan.barcode, scan);
// Optional: Log the save operation
// print('Scan saved locally: ${scan.barcode}');
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to save scan locally: ${e.toString()}');
}
}
@override
Future<List<ScanItem>> getAllScans() async {
try {
final box = await _getBox();
// Get all values from the box
final scans = box.values.toList();
// Sort by timestamp (most recent first)
scans.sort((a, b) => b.timestamp.compareTo(a.timestamp));
return scans;
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to get scans from local storage: ${e.toString()}');
}
}
@override
Future<ScanItem?> getScanByBarcode(String barcode) async {
try {
if (barcode.trim().isEmpty) {
throw const ValidationException('Barcode cannot be empty');
}
final box = await _getBox();
// Get scan by barcode key
return box.get(barcode);
} on ValidationException {
rethrow;
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to get scan by barcode: ${e.toString()}');
}
}
@override
Future<void> updateScan(ScanItem scan) async {
try {
final box = await _getBox();
// Check if scan exists
if (!box.containsKey(scan.barcode)) {
throw CacheException('Scan with barcode ${scan.barcode} not found');
}
// Update the scan
await box.put(scan.barcode, scan);
// Optional: Log the update operation
// print('Scan updated locally: ${scan.barcode}');
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to update scan locally: ${e.toString()}');
}
}
@override
Future<void> deleteScan(String barcode) async {
try {
if (barcode.trim().isEmpty) {
throw const ValidationException('Barcode cannot be empty');
}
final box = await _getBox();
// Check if scan exists
if (!box.containsKey(barcode)) {
throw CacheException('Scan with barcode $barcode not found');
}
// Delete the scan
await box.delete(barcode);
// Optional: Log the delete operation
// print('Scan deleted locally: $barcode');
} on ValidationException {
rethrow;
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to delete scan locally: ${e.toString()}');
}
}
@override
Future<void> clearAllScans() async {
try {
final box = await _getBox();
// Clear all scans
await box.clear();
// Optional: Log the clear operation
// print('All scans cleared from local storage');
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to clear all scans: ${e.toString()}');
}
}
/// Get scans count (utility method)
Future<int> getScansCount() async {
try {
final box = await _getBox();
return box.length;
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to get scans count: ${e.toString()}');
}
}
/// Check if scan exists (utility method)
Future<bool> scanExists(String barcode) async {
try {
if (barcode.trim().isEmpty) {
return false;
}
final box = await _getBox();
return box.containsKey(barcode);
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to check if scan exists: ${e.toString()}');
}
}
/// Get scans within date range (utility method)
Future<List<ScanItem>> getScansByDateRange({
required DateTime startDate,
required DateTime endDate,
}) async {
try {
final allScans = await getAllScans();
// Filter by date range
final filteredScans = allScans.where((scan) {
return scan.timestamp.isAfter(startDate) &&
scan.timestamp.isBefore(endDate);
}).toList();
return filteredScans;
} on CacheException {
rethrow;
} catch (e) {
throw CacheException('Failed to get scans by date range: ${e.toString()}');
}
}
/// Close the Hive box (call this when app is closing)
Future<void> dispose() async {
if (_box != null && _box!.isOpen) {
await _box!.close();
_box = null;
}
}
}