runable
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import '../../../../core/network/api_client.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../models/save_request_model.dart';
|
||||
|
||||
/// Abstract remote data source for scanner operations
|
||||
abstract class ScannerRemoteDataSource {
|
||||
/// Save scan data to remote server
|
||||
Future<void> saveScan(SaveRequestModel request);
|
||||
|
||||
/// Get scan data from remote server (optional for future use)
|
||||
Future<Map<String, dynamic>?> getScanData(String barcode);
|
||||
}
|
||||
|
||||
/// Implementation of ScannerRemoteDataSource using HTTP API
|
||||
class ScannerRemoteDataSourceImpl implements ScannerRemoteDataSource {
|
||||
final ApiClient apiClient;
|
||||
|
||||
ScannerRemoteDataSourceImpl({required this.apiClient});
|
||||
|
||||
@override
|
||||
Future<void> saveScan(SaveRequestModel request) async {
|
||||
try {
|
||||
// Validate request before sending
|
||||
if (!request.isValid) {
|
||||
throw ValidationException('Invalid request data: ${request.validationErrors.join(', ')}');
|
||||
}
|
||||
|
||||
final response = await apiClient.post(
|
||||
'/api/scans',
|
||||
data: request.toJson(),
|
||||
);
|
||||
|
||||
// Check if the response indicates success
|
||||
if (response.statusCode == null ||
|
||||
(response.statusCode! < 200 || response.statusCode! >= 300)) {
|
||||
final errorMessage = response.data?['message'] ?? 'Unknown server error';
|
||||
throw ServerException('Failed to save scan: $errorMessage');
|
||||
}
|
||||
|
||||
// Log successful save (in production, use proper logging)
|
||||
// print('Scan saved successfully: ${request.barcode}');
|
||||
|
||||
} on ValidationException {
|
||||
rethrow;
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} on NetworkException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
// Handle any unexpected errors
|
||||
throw ServerException('Unexpected error occurred while saving scan: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>?> getScanData(String barcode) async {
|
||||
try {
|
||||
if (barcode.trim().isEmpty) {
|
||||
throw const ValidationException('Barcode cannot be empty');
|
||||
}
|
||||
|
||||
final response = await apiClient.get(
|
||||
'/api/scans/$barcode',
|
||||
);
|
||||
|
||||
if (response.statusCode == 404) {
|
||||
// Scan not found is not an error, just return null
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.statusCode == null ||
|
||||
(response.statusCode! < 200 || response.statusCode! >= 300)) {
|
||||
final errorMessage = response.data?['message'] ?? 'Unknown server error';
|
||||
throw ServerException('Failed to get scan data: $errorMessage');
|
||||
}
|
||||
|
||||
return response.data as Map<String, dynamic>?;
|
||||
|
||||
} on ValidationException {
|
||||
rethrow;
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} on NetworkException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
throw ServerException('Unexpected error occurred while getting scan data: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Update scan data on remote server (optional for future use)
|
||||
Future<void> updateScan(String barcode, SaveRequestModel request) async {
|
||||
try {
|
||||
if (barcode.trim().isEmpty) {
|
||||
throw const ValidationException('Barcode cannot be empty');
|
||||
}
|
||||
|
||||
if (!request.isValid) {
|
||||
throw ValidationException('Invalid request data: ${request.validationErrors.join(', ')}');
|
||||
}
|
||||
|
||||
final response = await apiClient.put(
|
||||
'/api/scans/$barcode',
|
||||
data: request.toJson(),
|
||||
);
|
||||
|
||||
if (response.statusCode == null ||
|
||||
(response.statusCode! < 200 || response.statusCode! >= 300)) {
|
||||
final errorMessage = response.data?['message'] ?? 'Unknown server error';
|
||||
throw ServerException('Failed to update scan: $errorMessage');
|
||||
}
|
||||
|
||||
} on ValidationException {
|
||||
rethrow;
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} on NetworkException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
throw ServerException('Unexpected error occurred while updating scan: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete scan data from remote server (optional for future use)
|
||||
Future<void> deleteScan(String barcode) async {
|
||||
try {
|
||||
if (barcode.trim().isEmpty) {
|
||||
throw const ValidationException('Barcode cannot be empty');
|
||||
}
|
||||
|
||||
final response = await apiClient.delete('/api/scans/$barcode');
|
||||
|
||||
if (response.statusCode == null ||
|
||||
(response.statusCode! < 200 || response.statusCode! >= 300)) {
|
||||
final errorMessage = response.data?['message'] ?? 'Unknown server error';
|
||||
throw ServerException('Failed to delete scan: $errorMessage');
|
||||
}
|
||||
|
||||
} on ValidationException {
|
||||
rethrow;
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} on NetworkException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
throw ServerException('Unexpected error occurred while deleting scan: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user