fingerprint/lib/presentation/providers/scanner_provider.dart
Aastha Shrivastava 3132b7e8cd first commit
2026-01-17 12:54:01 +05:30

273 lines
7.7 KiB
Dart

import 'dart:isolate';
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/services/image_processor_isolate.dart';
import '../../core/services/image_analysis_service.dart';
import '../../data/datasources/camera_datasource.dart';
import '../../data/repositories/scanner_repository_impl.dart';
import '../../domain/entities/scan_result.dart';
import '../../domain/entities/quality_status.dart';
import '../../domain/entities/processing_request.dart';
import '../../domain/usecases/scan_fingerprint.dart';
// --- Dependency Injection ---
final cameraDataSourceProvider = Provider<CameraDataSource>((ref) {
return CameraDataSourceImpl();
});
final scannerRepositoryProvider = Provider<ScannerRepositoryImpl>((ref) {
return ScannerRepositoryImpl(ref.read(cameraDataSourceProvider));
});
final scanFingerprintUseCaseProvider = Provider<ScanFingerprint>((ref) {
return ScanFingerprint(ref.read(scannerRepositoryProvider));
});
// --- State ---
abstract class ScannerState {}
class ScannerInitial extends ScannerState {}
class ScannerInitializing extends ScannerState {}
class ScannerReady extends ScannerState {
final CameraController? controller;
final QualityStatus? qualityStatus;
final int consecutivePasses;
final bool isSimulation;
bool get canCapture => consecutivePasses >= 5;
ScannerReady(this.controller, {
this.qualityStatus,
this.consecutivePasses = 0,
this.isSimulation = false
});
}
class ScannerScanning extends ScannerState {
final CameraController? controller;
ScannerScanning(this.controller);
}
class ScannerSuccess extends ScannerState {
final ScanResult result;
final CameraController? controller;
ScannerSuccess(this.result, {this.controller});
}
class ScannerError extends ScannerState {
final String message;
final CameraController? controller;
ScannerError(this.message, {this.controller});
}
// --- Notifier ---
class ScannerNotifier extends Notifier<ScannerState> {
late final ScannerRepositoryImpl repository;
late final ScanFingerprint scanUseCase;
Isolate? _isolate;
SendPort? _isolateSendPort;
ReceivePort? _receivePort;
CameraController? _activeController; // Track controller for safe disposal
DateTime _lastFrameTime = DateTime.fromMillisecondsSinceEpoch(0);
static const _throttleDuration = Duration(milliseconds: 150);
bool _isMounted = true;
@override
ScannerState build() {
repository = ref.read(scannerRepositoryProvider);
scanUseCase = ref.read(scanFingerprintUseCaseProvider);
_isMounted = true;
ref.onDispose(() {
_isMounted = false;
_stopStreamAndDispose();
});
return ScannerInitial();
}
void _stopStreamAndDispose() {
if (_activeController != null) {
repository.stopImageStream(_activeController!).catchError((_) {});
_activeController!.dispose();
_activeController = null;
}
_receivePort?.close();
_isolate?.kill();
_isolate = null;
_receivePort = null;
}
Future<void> initialize() async {
// Default to initializing camera
await tryCamera();
}
Future<void> tryCamera() async {
state = ScannerInitializing();
// Dispose previous controller if exists
if (_activeController != null) {
await repository.stopImageStream(_activeController!).catchError((_) {});
await _activeController!.dispose();
_activeController = null;
}
try {
final controller = await repository.initializeCamera();
_activeController = controller;
if (!kIsWeb) {
_receivePort = ReceivePort();
_isolate = await Isolate.spawn(ImageProcessorIsolate.spawn, _receivePort!.sendPort);
_receivePort!.listen(_handleIsolateMessage);
}
await repository.startImageStream(controller, (image) => _onFrame(image, controller));
state = ScannerReady(controller, isSimulation: false);
} catch (e) {
debugPrint("Camera failed: $e");
state = ScannerError("Camera Access Denied or Unavailable. If you are on a mobile browser, ensure you are using HTTPS or have enabled the necessary flags.\n\nError: $e");
}
}
void startSimulation() {
state = ScannerReady(null, isSimulation: true);
if (kIsWeb) _startWebSimulation();
}
void _startWebSimulation() async {
while (_isMounted) {
final currentState = state;
if (currentState is! ScannerReady || !currentState.isSimulation) break;
await Future.delayed(const Duration(milliseconds: 200));
if (!_isMounted) return;
final result = ImageAnalysisService.analyze(
bytes: Uint8List(0),
width: 100,
height: 100,
);
_updateQuality(result);
}
}
void _handleIsolateMessage(dynamic message) {
if (message is SendPort) {
_isolateSendPort = message;
} else if (message is QualityStatus) {
_updateQuality(message);
}
}
void _updateQuality(QualityStatus status) {
if (!_isMounted) return;
final currentState = state;
if (currentState is ScannerReady) {
int passes = currentState.consecutivePasses;
if (status.canCapture) {
passes++;
} else {
passes = 0;
}
state = ScannerReady(
currentState.controller,
qualityStatus: status,
consecutivePasses: passes,
isSimulation: currentState.isSimulation
);
}
}
void _onFrame(CameraImage image, CameraController controller) {
if (!_isMounted) return;
final now = DateTime.now();
if (now.difference(_lastFrameTime) < _throttleDuration) {
return;
}
_lastFrameTime = now;
if (!controller.value.isInitialized) return;
final bytes = image.planes[0].bytes;
if (kIsWeb) {
final result = ImageAnalysisService.analyze(
bytes: bytes,
width: image.width,
height: image.height,
);
_updateQuality(result);
} else {
if (_isolateSendPort == null) return;
_isolateSendPort!.send(ProcessingRequest(
bytes: bytes,
width: image.width,
height: image.height,
sendPort: _receivePort!.sendPort,
));
}
}
Future<void> scan() async {
final currentState = state;
if (currentState is ScannerReady) {
if (!currentState.canCapture) return;
state = ScannerScanning(currentState.controller);
try {
String imagePath;
if (currentState.controller != null) {
await repository.stopImageStream(currentState.controller!);
final result = await scanUseCase(currentState.controller!);
imagePath = result.imagePath;
} else {
await Future.delayed(const Duration(seconds: 1));
imagePath = "simulated_capture.jpg";
}
if (_isMounted) {
state = ScannerSuccess(
ScanResult(imagePath: imagePath, timestamp: DateTime.now()),
controller: currentState.controller
);
}
} catch (e) {
if (_isMounted) {
state = ScannerError(e.toString(), controller: currentState.controller);
}
}
}
}
Future<void> reset() async {
final currentState = state;
if (currentState is ScannerSuccess) {
final controller = currentState.controller;
if (controller != null) {
try {
await repository.startImageStream(controller, (image) => _onFrame(image, controller));
state = ScannerReady(controller, isSimulation: false);
} catch(e) {
tryCamera();
}
} else {
startSimulation();
}
} else {
tryCamera();
}
}
}
final scannerProvider = NotifierProvider<ScannerNotifier, ScannerState>(ScannerNotifier.new);