feat(scanner): add in-scanner active collection switcher

- Add collection dropdown at top of scanner screen
- Wire scanner selection changes back to scan tab active collection state
- Reuse persisted active collection behavior while scanning
- Keep continuous scan loop intact with collection changes applied immediately
This commit is contained in:
Lukas Müllner 2026-03-04 11:05:38 +01:00
parent 360fd5a705
commit b9d1d750b6
2 changed files with 53 additions and 1 deletions

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart'; import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'services/collection_service.dart';
import 'theme/app_colors.dart'; import 'theme/app_colors.dart';
/// Screen that uses the camera to scan text (OCR) from a Hot Wheels package /// Screen that uses the camera to scan text (OCR) from a Hot Wheels package
@ -11,8 +12,17 @@ import 'theme/app_colors.dart';
/// The detected ID is returned via Navigator.pop(context, hwId). /// The detected ID is returned via Navigator.pop(context, hwId).
class ScannerScreen extends StatefulWidget { class ScannerScreen extends StatefulWidget {
final Future<bool> Function(String hwId)? onDetected; final Future<bool> Function(String hwId)? onDetected;
final List<Collection> collections;
final String? activeCollectionId;
final ValueChanged<String>? onCollectionChanged;
const ScannerScreen({super.key, this.onDetected}); const ScannerScreen({
super.key,
this.onDetected,
this.collections = const [],
this.activeCollectionId,
this.onCollectionChanged,
});
@override @override
State<ScannerScreen> createState() => _ScannerScreenState(); State<ScannerScreen> createState() => _ScannerScreenState();
@ -26,6 +36,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
String? _lastDetected; String? _lastDetected;
bool _scanAccepted = false; bool _scanAccepted = false;
String _statusText = 'Ready to scan'; String _statusText = 'Ready to scan';
String? _activeCollectionId;
// Matches typical Hot Wheels model IDs: 25 uppercase letters followed by // Matches typical Hot Wheels model IDs: 25 uppercase letters followed by
// 24 digits, e.g. JKF21, HCV73, GRX33, FYD83. // 24 digits, e.g. JKF21, HCV73, GRX33, FYD83.
@ -34,10 +45,17 @@ class _ScannerScreenState extends State<ScannerScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_activeCollectionId = widget.activeCollectionId;
_textRecognizer = TextRecognizer(); _textRecognizer = TextRecognizer();
_initCamera(); _initCamera();
} }
void _handleCollectionChanged(String? value) {
if (value == null) return;
setState(() => _activeCollectionId = value);
widget.onCollectionChanged?.call(value);
}
Future<void> _initCamera() async { Future<void> _initCamera() async {
final cameras = await availableCameras(); final cameras = await availableCameras();
if (cameras.isEmpty) { if (cameras.isEmpty) {
@ -188,6 +206,37 @@ class _ScannerScreenState extends State<ScannerScreen> {
), ),
body: Column( body: Column(
children: [ children: [
if (widget.collections.isNotEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(12, 10, 12, 8),
color: Colors.black.withValues(alpha: 0.55),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: _activeCollectionId,
dropdownColor: AppColors.navy,
iconEnabledColor: Colors.white,
style: const TextStyle(color: Colors.white),
hint: const Text(
'Select collection',
style: TextStyle(color: Colors.white70),
),
items: widget.collections
.map(
(c) => DropdownMenuItem<String>(
value: c.id,
child: Text(
c.name,
overflow: TextOverflow.ellipsis,
),
),
)
.toList(),
onChanged: _isBusy ? null : _handleCollectionChanged,
),
),
),
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),

View file

@ -239,6 +239,9 @@ class ScanTabState extends State<ScanTab> {
await navigatorKey.currentState!.push<void>( await navigatorKey.currentState!.push<void>(
MaterialPageRoute( MaterialPageRoute(
builder: (_) => ScannerScreen( builder: (_) => ScannerScreen(
collections: _collections,
activeCollectionId: _selectedCollection?.id,
onCollectionChanged: _setActiveCollection,
onDetected: (hwId) => _processHwId(hwId), onDetected: (hwId) => _processHwId(hwId),
), ),
), ),