feat(scanner): add auto-scan cadence with pause/resume controls
- Add periodic auto-capture loop for uninterrupted scanner workflow - Start auto-scan after camera init and stop cleanly on dispose - Gate scans by busy/camera-ready state to avoid overlapping captures - Add AUTO ON/OFF toggle in scanner status bar - Keep manual capture path available as fallback
This commit is contained in:
parent
4ba1fe19cc
commit
13114bd63f
1 changed files with 46 additions and 8 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -37,6 +38,8 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||||
bool _scanAccepted = false;
|
bool _scanAccepted = false;
|
||||||
String _statusText = 'Ready to scan';
|
String _statusText = 'Ready to scan';
|
||||||
String? _activeCollectionId;
|
String? _activeCollectionId;
|
||||||
|
bool _autoScanEnabled = false;
|
||||||
|
Timer? _autoScanTimer;
|
||||||
|
|
||||||
// Matches typical Hot Wheels model IDs: 2–5 uppercase letters followed by
|
// Matches typical Hot Wheels model IDs: 2–5 uppercase letters followed by
|
||||||
// 2–4 digits, e.g. JKF21, HCV73, GRX33, FYD83.
|
// 2–4 digits, e.g. JKF21, HCV73, GRX33, FYD83.
|
||||||
|
|
@ -46,6 +49,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_activeCollectionId = widget.activeCollectionId;
|
_activeCollectionId = widget.activeCollectionId;
|
||||||
|
_autoScanEnabled = widget.onDetected != null;
|
||||||
_textRecognizer = TextRecognizer();
|
_textRecognizer = TextRecognizer();
|
||||||
_initCamera();
|
_initCamera();
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +85,22 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||||
await _cameraController!.initialize();
|
await _cameraController!.initialize();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _cameraReady = true);
|
setState(() => _cameraReady = true);
|
||||||
|
_startAutoScanLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startAutoScanLoop() {
|
||||||
|
_autoScanTimer?.cancel();
|
||||||
|
_autoScanTimer = Timer.periodic(const Duration(milliseconds: 1400), (_) {
|
||||||
|
if (!_autoScanEnabled || _isBusy || !_cameraReady || !mounted) return;
|
||||||
|
_captureAndScan();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toggleAutoScan() {
|
||||||
|
setState(() {
|
||||||
|
_autoScanEnabled = !_autoScanEnabled;
|
||||||
|
_statusText = _autoScanEnabled ? 'Auto scan enabled' : 'Auto scan paused';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture a photo, run OCR, and look for a Hot Wheels ID.
|
/// Capture a photo, run OCR, and look for a Hot Wheels ID.
|
||||||
|
|
@ -187,6 +207,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_autoScanTimer?.cancel();
|
||||||
_cameraController?.dispose();
|
_cameraController?.dispose();
|
||||||
_textRecognizer.close();
|
_textRecognizer.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
@ -243,14 +264,31 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||||
color: _scanAccepted
|
color: _scanAccepted
|
||||||
? AppColors.success.withValues(alpha: 0.12)
|
? AppColors.success.withValues(alpha: 0.12)
|
||||||
: Colors.black.withValues(alpha: 0.55),
|
: Colors.black.withValues(alpha: 0.55),
|
||||||
child: Text(
|
child: Row(
|
||||||
_statusText,
|
children: [
|
||||||
textAlign: TextAlign.center,
|
Expanded(
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
color: _scanAccepted ? AppColors.success : Colors.white,
|
_statusText,
|
||||||
fontSize: 12,
|
textAlign: TextAlign.center,
|
||||||
fontWeight: FontWeight.w600,
|
style: TextStyle(
|
||||||
),
|
color: _scanAccepted ? AppColors.success : Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.onDetected != null)
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : _toggleAutoScan,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor:
|
||||||
|
_autoScanEnabled ? AppColors.success : Colors.white,
|
||||||
|
minimumSize: const Size(0, 26),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
),
|
||||||
|
child: Text(_autoScanEnabled ? 'AUTO ON' : 'AUTO OFF'),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// ── Camera preview ──
|
// ── Camera preview ──
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue