runable
This commit is contained in:
5
lib/features/scanner/domain/domain.dart
Normal file
5
lib/features/scanner/domain/domain.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
// Domain layer exports
|
||||
export 'entities/scan_entity.dart';
|
||||
export 'repositories/scanner_repository.dart';
|
||||
export 'usecases/get_scan_history_usecase.dart';
|
||||
export 'usecases/save_scan_usecase.dart';
|
||||
71
lib/features/scanner/domain/entities/scan_entity.dart
Normal file
71
lib/features/scanner/domain/entities/scan_entity.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Domain entity representing a scan item
|
||||
/// This is the business logic representation without any external dependencies
|
||||
class ScanEntity extends Equatable {
|
||||
final String barcode;
|
||||
final DateTime timestamp;
|
||||
final String field1;
|
||||
final String field2;
|
||||
final String field3;
|
||||
final String field4;
|
||||
|
||||
const ScanEntity({
|
||||
required this.barcode,
|
||||
required this.timestamp,
|
||||
this.field1 = '',
|
||||
this.field2 = '',
|
||||
this.field3 = '',
|
||||
this.field4 = '',
|
||||
});
|
||||
|
||||
/// Create a copy with updated fields
|
||||
ScanEntity copyWith({
|
||||
String? barcode,
|
||||
DateTime? timestamp,
|
||||
String? field1,
|
||||
String? field2,
|
||||
String? field3,
|
||||
String? field4,
|
||||
}) {
|
||||
return ScanEntity(
|
||||
barcode: barcode ?? this.barcode,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
field1: field1 ?? this.field1,
|
||||
field2: field2 ?? this.field2,
|
||||
field3: field3 ?? this.field3,
|
||||
field4: field4 ?? this.field4,
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if the entity has any form data
|
||||
bool get hasFormData {
|
||||
return field1.isNotEmpty ||
|
||||
field2.isNotEmpty ||
|
||||
field3.isNotEmpty ||
|
||||
field4.isNotEmpty;
|
||||
}
|
||||
|
||||
/// Check if all form fields are filled
|
||||
bool get isFormComplete {
|
||||
return field1.isNotEmpty &&
|
||||
field2.isNotEmpty &&
|
||||
field3.isNotEmpty &&
|
||||
field4.isNotEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
barcode,
|
||||
timestamp,
|
||||
field1,
|
||||
field2,
|
||||
field3,
|
||||
field4,
|
||||
];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScanEntity{barcode: $barcode, timestamp: $timestamp, field1: $field1, field2: $field2, field3: $field3, field4: $field4}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/errors/failures.dart';
|
||||
import '../entities/scan_entity.dart';
|
||||
|
||||
/// Abstract repository interface for scanner operations
|
||||
/// This defines the contract that the data layer must implement
|
||||
abstract class ScannerRepository {
|
||||
/// Save scan data to remote server
|
||||
Future<Either<Failure, void>> saveScan({
|
||||
required String barcode,
|
||||
required String field1,
|
||||
required String field2,
|
||||
required String field3,
|
||||
required String field4,
|
||||
});
|
||||
|
||||
/// Get scan history from local storage
|
||||
Future<Either<Failure, List<ScanEntity>>> getScanHistory();
|
||||
|
||||
/// Save scan to local storage
|
||||
Future<Either<Failure, void>> saveScanLocally(ScanEntity scan);
|
||||
|
||||
/// Delete a scan from local storage
|
||||
Future<Either<Failure, void>> deleteScanLocally(String barcode);
|
||||
|
||||
/// Clear all scan history from local storage
|
||||
Future<Either<Failure, void>> clearScanHistory();
|
||||
|
||||
/// Get a specific scan by barcode from local storage
|
||||
Future<Either<Failure, ScanEntity?>> getScanByBarcode(String barcode);
|
||||
|
||||
/// Update a scan in local storage
|
||||
Future<Either<Failure, void>> updateScanLocally(ScanEntity scan);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/errors/failures.dart';
|
||||
import '../entities/scan_entity.dart';
|
||||
import '../repositories/scanner_repository.dart';
|
||||
|
||||
/// Use case for retrieving scan history
|
||||
/// Handles the business logic for fetching scan history from local storage
|
||||
class GetScanHistoryUseCase {
|
||||
final ScannerRepository repository;
|
||||
|
||||
GetScanHistoryUseCase(this.repository);
|
||||
|
||||
/// Execute the get scan history operation
|
||||
///
|
||||
/// Returns a list of scan entities sorted by timestamp (most recent first)
|
||||
Future<Either<Failure, List<ScanEntity>>> call() async {
|
||||
try {
|
||||
final result = await repository.getScanHistory();
|
||||
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(scans) {
|
||||
// Sort scans by timestamp (most recent first)
|
||||
final sortedScans = List<ScanEntity>.from(scans);
|
||||
sortedScans.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
return Right(sortedScans);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to get scan history: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get scan history filtered by date range
|
||||
Future<Either<Failure, List<ScanEntity>>> getHistoryInDateRange({
|
||||
required DateTime startDate,
|
||||
required DateTime endDate,
|
||||
}) async {
|
||||
try {
|
||||
final result = await repository.getScanHistory();
|
||||
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(scans) {
|
||||
// Filter scans by date range
|
||||
final filteredScans = scans
|
||||
.where((scan) =>
|
||||
scan.timestamp.isAfter(startDate) &&
|
||||
scan.timestamp.isBefore(endDate))
|
||||
.toList();
|
||||
|
||||
// Sort by timestamp (most recent first)
|
||||
filteredScans.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
|
||||
return Right(filteredScans);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to get scan history: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get scans that have form data (non-empty fields)
|
||||
Future<Either<Failure, List<ScanEntity>>> getScansWithFormData() async {
|
||||
try {
|
||||
final result = await repository.getScanHistory();
|
||||
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(scans) {
|
||||
// Filter scans that have form data
|
||||
final filteredScans = scans.where((scan) => scan.hasFormData).toList();
|
||||
|
||||
// Sort by timestamp (most recent first)
|
||||
filteredScans.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
|
||||
return Right(filteredScans);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to get scan history: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Search scans by barcode pattern
|
||||
Future<Either<Failure, List<ScanEntity>>> searchByBarcode(String pattern) async {
|
||||
try {
|
||||
if (pattern.trim().isEmpty) {
|
||||
return const Right([]);
|
||||
}
|
||||
|
||||
final result = await repository.getScanHistory();
|
||||
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(scans) {
|
||||
// Filter scans by barcode pattern (case-insensitive)
|
||||
final filteredScans = scans
|
||||
.where((scan) =>
|
||||
scan.barcode.toLowerCase().contains(pattern.toLowerCase()))
|
||||
.toList();
|
||||
|
||||
// Sort by timestamp (most recent first)
|
||||
filteredScans.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
|
||||
return Right(filteredScans);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to search scans: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
109
lib/features/scanner/domain/usecases/save_scan_usecase.dart
Normal file
109
lib/features/scanner/domain/usecases/save_scan_usecase.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/errors/failures.dart';
|
||||
import '../entities/scan_entity.dart';
|
||||
import '../repositories/scanner_repository.dart';
|
||||
|
||||
/// Use case for saving scan data
|
||||
/// Handles the business logic for saving scan information to both remote and local storage
|
||||
class SaveScanUseCase {
|
||||
final ScannerRepository repository;
|
||||
|
||||
SaveScanUseCase(this.repository);
|
||||
|
||||
/// Execute the save scan operation
|
||||
///
|
||||
/// First saves to remote server, then saves locally only if remote save succeeds
|
||||
/// This ensures data consistency and allows for offline-first behavior
|
||||
Future<Either<Failure, void>> call(SaveScanParams params) async {
|
||||
// Validate input parameters
|
||||
final validationResult = _validateParams(params);
|
||||
if (validationResult != null) {
|
||||
return Left(ValidationFailure(validationResult));
|
||||
}
|
||||
|
||||
try {
|
||||
// Save to remote server first
|
||||
final remoteResult = await repository.saveScan(
|
||||
barcode: params.barcode,
|
||||
field1: params.field1,
|
||||
field2: params.field2,
|
||||
field3: params.field3,
|
||||
field4: params.field4,
|
||||
);
|
||||
|
||||
return remoteResult.fold(
|
||||
(failure) => Left(failure),
|
||||
(_) async {
|
||||
// If remote save succeeds, save to local storage
|
||||
final scanEntity = ScanEntity(
|
||||
barcode: params.barcode,
|
||||
timestamp: DateTime.now(),
|
||||
field1: params.field1,
|
||||
field2: params.field2,
|
||||
field3: params.field3,
|
||||
field4: params.field4,
|
||||
);
|
||||
|
||||
final localResult = await repository.saveScanLocally(scanEntity);
|
||||
return localResult.fold(
|
||||
(failure) {
|
||||
// Log the local save failure but don't fail the entire operation
|
||||
// since remote save succeeded
|
||||
return const Right(null);
|
||||
},
|
||||
(_) => const Right(null),
|
||||
);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure('Failed to save scan: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate the input parameters
|
||||
String? _validateParams(SaveScanParams params) {
|
||||
if (params.barcode.trim().isEmpty) {
|
||||
return 'Barcode cannot be empty';
|
||||
}
|
||||
|
||||
if (params.field1.trim().isEmpty) {
|
||||
return 'Field 1 cannot be empty';
|
||||
}
|
||||
|
||||
if (params.field2.trim().isEmpty) {
|
||||
return 'Field 2 cannot be empty';
|
||||
}
|
||||
|
||||
if (params.field3.trim().isEmpty) {
|
||||
return 'Field 3 cannot be empty';
|
||||
}
|
||||
|
||||
if (params.field4.trim().isEmpty) {
|
||||
return 'Field 4 cannot be empty';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for the SaveScanUseCase
|
||||
class SaveScanParams {
|
||||
final String barcode;
|
||||
final String field1;
|
||||
final String field2;
|
||||
final String field3;
|
||||
final String field4;
|
||||
|
||||
SaveScanParams({
|
||||
required this.barcode,
|
||||
required this.field1,
|
||||
required this.field2,
|
||||
required this.field3,
|
||||
required this.field4,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SaveScanParams{barcode: $barcode, field1: $field1, field2: $field2, field3: $field3, field4: $field4}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user