runable
This commit is contained in:
6
lib/features/scanner/data/data.dart
Normal file
6
lib/features/scanner/data/data.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
// Data layer exports
|
||||
export 'datasources/scanner_local_datasource.dart';
|
||||
export 'datasources/scanner_remote_datasource.dart';
|
||||
export 'models/save_request_model.dart';
|
||||
export 'models/scan_item.dart';
|
||||
export 'repositories/scanner_repository_impl.dart';
|
||||
@@ -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()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
134
lib/features/scanner/data/models/save_request_model.dart
Normal file
134
lib/features/scanner/data/models/save_request_model.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import '../../domain/entities/scan_entity.dart';
|
||||
|
||||
part 'save_request_model.g.dart';
|
||||
|
||||
/// API request model for saving scan data to the server
|
||||
@JsonSerializable()
|
||||
class SaveRequestModel {
|
||||
final String barcode;
|
||||
final String field1;
|
||||
final String field2;
|
||||
final String field3;
|
||||
final String field4;
|
||||
|
||||
SaveRequestModel({
|
||||
required this.barcode,
|
||||
required this.field1,
|
||||
required this.field2,
|
||||
required this.field3,
|
||||
required this.field4,
|
||||
});
|
||||
|
||||
/// Create from domain entity
|
||||
factory SaveRequestModel.fromEntity(ScanEntity entity) {
|
||||
return SaveRequestModel(
|
||||
barcode: entity.barcode,
|
||||
field1: entity.field1,
|
||||
field2: entity.field2,
|
||||
field3: entity.field3,
|
||||
field4: entity.field4,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create from parameters
|
||||
factory SaveRequestModel.fromParams({
|
||||
required String barcode,
|
||||
required String field1,
|
||||
required String field2,
|
||||
required String field3,
|
||||
required String field4,
|
||||
}) {
|
||||
return SaveRequestModel(
|
||||
barcode: barcode,
|
||||
field1: field1,
|
||||
field2: field2,
|
||||
field3: field3,
|
||||
field4: field4,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create from JSON
|
||||
factory SaveRequestModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$SaveRequestModelFromJson(json);
|
||||
|
||||
/// Convert to JSON for API requests
|
||||
Map<String, dynamic> toJson() => _$SaveRequestModelToJson(this);
|
||||
|
||||
/// Create a copy with updated fields
|
||||
SaveRequestModel copyWith({
|
||||
String? barcode,
|
||||
String? field1,
|
||||
String? field2,
|
||||
String? field3,
|
||||
String? field4,
|
||||
}) {
|
||||
return SaveRequestModel(
|
||||
barcode: barcode ?? this.barcode,
|
||||
field1: field1 ?? this.field1,
|
||||
field2: field2 ?? this.field2,
|
||||
field3: field3 ?? this.field3,
|
||||
field4: field4 ?? this.field4,
|
||||
);
|
||||
}
|
||||
|
||||
/// Validate the request data
|
||||
bool get isValid {
|
||||
return barcode.trim().isNotEmpty &&
|
||||
field1.trim().isNotEmpty &&
|
||||
field2.trim().isNotEmpty &&
|
||||
field3.trim().isNotEmpty &&
|
||||
field4.trim().isNotEmpty;
|
||||
}
|
||||
|
||||
/// Get validation errors
|
||||
List<String> get validationErrors {
|
||||
final errors = <String>[];
|
||||
|
||||
if (barcode.trim().isEmpty) {
|
||||
errors.add('Barcode is required');
|
||||
}
|
||||
|
||||
if (field1.trim().isEmpty) {
|
||||
errors.add('Field 1 is required');
|
||||
}
|
||||
|
||||
if (field2.trim().isEmpty) {
|
||||
errors.add('Field 2 is required');
|
||||
}
|
||||
|
||||
if (field3.trim().isEmpty) {
|
||||
errors.add('Field 3 is required');
|
||||
}
|
||||
|
||||
if (field4.trim().isEmpty) {
|
||||
errors.add('Field 4 is required');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SaveRequestModel{barcode: $barcode, field1: $field1, field2: $field2, field3: $field3, field4: $field4}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SaveRequestModel &&
|
||||
runtimeType == other.runtimeType &&
|
||||
barcode == other.barcode &&
|
||||
field1 == other.field1 &&
|
||||
field2 == other.field2 &&
|
||||
field3 == other.field3 &&
|
||||
field4 == other.field4;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
barcode.hashCode ^
|
||||
field1.hashCode ^
|
||||
field2.hashCode ^
|
||||
field3.hashCode ^
|
||||
field4.hashCode;
|
||||
}
|
||||
25
lib/features/scanner/data/models/save_request_model.g.dart
Normal file
25
lib/features/scanner/data/models/save_request_model.g.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'save_request_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SaveRequestModel _$SaveRequestModelFromJson(Map<String, dynamic> json) =>
|
||||
SaveRequestModel(
|
||||
barcode: json['barcode'] as String,
|
||||
field1: json['field1'] as String,
|
||||
field2: json['field2'] as String,
|
||||
field3: json['field3'] as String,
|
||||
field4: json['field4'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SaveRequestModelToJson(SaveRequestModel instance) =>
|
||||
<String, dynamic>{
|
||||
'barcode': instance.barcode,
|
||||
'field1': instance.field1,
|
||||
'field2': instance.field2,
|
||||
'field3': instance.field3,
|
||||
'field4': instance.field4,
|
||||
};
|
||||
131
lib/features/scanner/data/models/scan_item.dart
Normal file
131
lib/features/scanner/data/models/scan_item.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import '../../domain/entities/scan_entity.dart';
|
||||
|
||||
part 'scan_item.g.dart';
|
||||
|
||||
/// Data model for ScanEntity with Hive annotations for local storage
|
||||
/// This is the data layer representation that can be persisted
|
||||
@HiveType(typeId: 0)
|
||||
class ScanItem extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String barcode;
|
||||
|
||||
@HiveField(1)
|
||||
final DateTime timestamp;
|
||||
|
||||
@HiveField(2)
|
||||
final String field1;
|
||||
|
||||
@HiveField(3)
|
||||
final String field2;
|
||||
|
||||
@HiveField(4)
|
||||
final String field3;
|
||||
|
||||
@HiveField(5)
|
||||
final String field4;
|
||||
|
||||
ScanItem({
|
||||
required this.barcode,
|
||||
required this.timestamp,
|
||||
this.field1 = '',
|
||||
this.field2 = '',
|
||||
this.field3 = '',
|
||||
this.field4 = '',
|
||||
});
|
||||
|
||||
/// Convert from domain entity to data model
|
||||
factory ScanItem.fromEntity(ScanEntity entity) {
|
||||
return ScanItem(
|
||||
barcode: entity.barcode,
|
||||
timestamp: entity.timestamp,
|
||||
field1: entity.field1,
|
||||
field2: entity.field2,
|
||||
field3: entity.field3,
|
||||
field4: entity.field4,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to domain entity
|
||||
ScanEntity toEntity() {
|
||||
return ScanEntity(
|
||||
barcode: barcode,
|
||||
timestamp: timestamp,
|
||||
field1: field1,
|
||||
field2: field2,
|
||||
field3: field3,
|
||||
field4: field4,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create from JSON (useful for API responses)
|
||||
factory ScanItem.fromJson(Map<String, dynamic> json) {
|
||||
return ScanItem(
|
||||
barcode: json['barcode'] ?? '',
|
||||
timestamp: json['timestamp'] != null
|
||||
? DateTime.parse(json['timestamp'])
|
||||
: DateTime.now(),
|
||||
field1: json['field1'] ?? '',
|
||||
field2: json['field2'] ?? '',
|
||||
field3: json['field3'] ?? '',
|
||||
field4: json['field4'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON (useful for API requests)
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'barcode': barcode,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'field1': field1,
|
||||
'field2': field2,
|
||||
'field3': field3,
|
||||
'field4': field4,
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a copy with updated fields
|
||||
ScanItem copyWith({
|
||||
String? barcode,
|
||||
DateTime? timestamp,
|
||||
String? field1,
|
||||
String? field2,
|
||||
String? field3,
|
||||
String? field4,
|
||||
}) {
|
||||
return ScanItem(
|
||||
barcode: barcode ?? this.barcode,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
field1: field1 ?? this.field1,
|
||||
field2: field2 ?? this.field2,
|
||||
field3: field3 ?? this.field3,
|
||||
field4: field4 ?? this.field4,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScanItem{barcode: $barcode, timestamp: $timestamp, field1: $field1, field2: $field2, field3: $field3, field4: $field4}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ScanItem &&
|
||||
runtimeType == other.runtimeType &&
|
||||
barcode == other.barcode &&
|
||||
timestamp == other.timestamp &&
|
||||
field1 == other.field1 &&
|
||||
field2 == other.field2 &&
|
||||
field3 == other.field3 &&
|
||||
field4 == other.field4;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
barcode.hashCode ^
|
||||
timestamp.hashCode ^
|
||||
field1.hashCode ^
|
||||
field2.hashCode ^
|
||||
field3.hashCode ^
|
||||
field4.hashCode;
|
||||
}
|
||||
56
lib/features/scanner/data/models/scan_item.g.dart
Normal file
56
lib/features/scanner/data/models/scan_item.g.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'scan_item.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class ScanItemAdapter extends TypeAdapter<ScanItem> {
|
||||
@override
|
||||
final int typeId = 0;
|
||||
|
||||
@override
|
||||
ScanItem read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return ScanItem(
|
||||
barcode: fields[0] as String,
|
||||
timestamp: fields[1] as DateTime,
|
||||
field1: fields[2] as String,
|
||||
field2: fields[3] as String,
|
||||
field3: fields[4] as String,
|
||||
field4: fields[5] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ScanItem obj) {
|
||||
writer
|
||||
..writeByte(6)
|
||||
..writeByte(0)
|
||||
..write(obj.barcode)
|
||||
..writeByte(1)
|
||||
..write(obj.timestamp)
|
||||
..writeByte(2)
|
||||
..write(obj.field1)
|
||||
..writeByte(3)
|
||||
..write(obj.field2)
|
||||
..writeByte(4)
|
||||
..write(obj.field3)
|
||||
..writeByte(5)
|
||||
..write(obj.field4);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ScanItemAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/errors/failures.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../../domain/entities/scan_entity.dart';
|
||||
import '../../domain/repositories/scanner_repository.dart';
|
||||
import '../datasources/scanner_local_datasource.dart';
|
||||
import '../datasources/scanner_remote_datasource.dart';
|
||||
import '../models/save_request_model.dart';
|
||||
import '../models/scan_item.dart';
|
||||
|
||||
/// Implementation of ScannerRepository
|
||||
/// This class handles the coordination between remote and local data sources
|
||||
class ScannerRepositoryImpl implements ScannerRepository {
|
||||
final ScannerRemoteDataSource remoteDataSource;
|
||||
final ScannerLocalDataSource localDataSource;
|
||||
|
||||
ScannerRepositoryImpl({
|
||||
required this.remoteDataSource,
|
||||
required this.localDataSource,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> saveScan({
|
||||
required String barcode,
|
||||
required String field1,
|
||||
required String field2,
|
||||
required String field3,
|
||||
required String field4,
|
||||
}) async {
|
||||
try {
|
||||
// Create the request model
|
||||
final request = SaveRequestModel.fromParams(
|
||||
barcode: barcode,
|
||||
field1: field1,
|
||||
field2: field2,
|
||||
field3: field3,
|
||||
field4: field4,
|
||||
);
|
||||
|
||||
// Validate the request
|
||||
if (!request.isValid) {
|
||||
return Left(ValidationFailure(request.validationErrors.join(', ')));
|
||||
}
|
||||
|
||||
// Save to remote server
|
||||
await remoteDataSource.saveScan(request);
|
||||
|
||||
// If remote save succeeds, we return success
|
||||
// Local save will be handled separately by the use case if needed
|
||||
return const Right(null);
|
||||
|
||||
} on ValidationException catch (e) {
|
||||
return Left(ValidationFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to save scan: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<ScanEntity>>> getScanHistory() async {
|
||||
try {
|
||||
// Get scans from local storage
|
||||
final scanItems = await localDataSource.getAllScans();
|
||||
|
||||
// Convert to domain entities
|
||||
final entities = scanItems.map((item) => item.toEntity()).toList();
|
||||
|
||||
return Right(entities);
|
||||
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to get scan history: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> saveScanLocally(ScanEntity scan) async {
|
||||
try {
|
||||
// Convert entity to data model
|
||||
final scanItem = ScanItem.fromEntity(scan);
|
||||
|
||||
// Save to local storage
|
||||
await localDataSource.saveScan(scanItem);
|
||||
|
||||
return const Right(null);
|
||||
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to save scan locally: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> deleteScanLocally(String barcode) async {
|
||||
try {
|
||||
if (barcode.trim().isEmpty) {
|
||||
return const Left(ValidationFailure('Barcode cannot be empty'));
|
||||
}
|
||||
|
||||
// Delete from local storage
|
||||
await localDataSource.deleteScan(barcode);
|
||||
|
||||
return const Right(null);
|
||||
|
||||
} on ValidationException catch (e) {
|
||||
return Left(ValidationFailure(e.message));
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to delete scan: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> clearScanHistory() async {
|
||||
try {
|
||||
// Clear all scans from local storage
|
||||
await localDataSource.clearAllScans();
|
||||
|
||||
return const Right(null);
|
||||
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to clear scan history: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, ScanEntity?>> getScanByBarcode(String barcode) async {
|
||||
try {
|
||||
if (barcode.trim().isEmpty) {
|
||||
return const Left(ValidationFailure('Barcode cannot be empty'));
|
||||
}
|
||||
|
||||
// Get scan from local storage
|
||||
final scanItem = await localDataSource.getScanByBarcode(barcode);
|
||||
|
||||
if (scanItem == null) {
|
||||
return const Right(null);
|
||||
}
|
||||
|
||||
// Convert to domain entity
|
||||
final entity = scanItem.toEntity();
|
||||
|
||||
return Right(entity);
|
||||
|
||||
} on ValidationException catch (e) {
|
||||
return Left(ValidationFailure(e.message));
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to get scan by barcode: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> updateScanLocally(ScanEntity scan) async {
|
||||
try {
|
||||
// Convert entity to data model
|
||||
final scanItem = ScanItem.fromEntity(scan);
|
||||
|
||||
// Update in local storage
|
||||
await localDataSource.updateScan(scanItem);
|
||||
|
||||
return const Right(null);
|
||||
|
||||
} on CacheException catch (e) {
|
||||
return Left(CacheFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to update scan: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional utility methods for repository
|
||||
|
||||
/// Get scans count
|
||||
Future<Either<Failure, int>> getScansCount() async {
|
||||
try {
|
||||
if (localDataSource is ScannerLocalDataSourceImpl) {
|
||||
final impl = localDataSource as ScannerLocalDataSourceImpl;
|
||||
final count = await impl.getScansCount();
|
||||
return Right(count);
|
||||
}
|
||||
|
||||
// Fallback: get all scans and count them
|
||||
final result = await getScanHistory();
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(scans) => Right(scans.length),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to get scans count: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if scan exists locally
|
||||
Future<Either<Failure, bool>> scanExistsLocally(String barcode) async {
|
||||
try {
|
||||
if (barcode.trim().isEmpty) {
|
||||
return const Left(ValidationFailure('Barcode cannot be empty'));
|
||||
}
|
||||
|
||||
if (localDataSource is ScannerLocalDataSourceImpl) {
|
||||
final impl = localDataSource as ScannerLocalDataSourceImpl;
|
||||
final exists = await impl.scanExists(barcode);
|
||||
return Right(exists);
|
||||
}
|
||||
|
||||
// Fallback: get scan by barcode
|
||||
final result = await getScanByBarcode(barcode);
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(scan) => Right(scan != null),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to check if scan exists: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get scans by date range
|
||||
Future<Either<Failure, List<ScanEntity>>> getScansByDateRange({
|
||||
required DateTime startDate,
|
||||
required DateTime endDate,
|
||||
}) async {
|
||||
try {
|
||||
if (localDataSource is ScannerLocalDataSourceImpl) {
|
||||
final impl = localDataSource as ScannerLocalDataSourceImpl;
|
||||
final scanItems = await impl.getScansByDateRange(
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
);
|
||||
|
||||
// Convert to domain entities
|
||||
final entities = scanItems.map((item) => item.toEntity()).toList();
|
||||
return Right(entities);
|
||||
}
|
||||
|
||||
// Fallback: get all scans and filter
|
||||
final result = await getScanHistory();
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(scans) {
|
||||
final filteredScans = scans
|
||||
.where((scan) =>
|
||||
scan.timestamp.isAfter(startDate) &&
|
||||
scan.timestamp.isBefore(endDate))
|
||||
.toList();
|
||||
return Right(filteredScans);
|
||||
},
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to get scans by date range: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user