228 lines
8.4 KiB
Dart
228 lines
8.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
|
import '../l10n/app_localizations.dart';
|
|
import '../models/tasks/barcode_task.dart';
|
|
import '../widgets/offline_banner.dart';
|
|
|
|
class BarcodeCaptureScreen extends StatefulWidget {
|
|
final BarcodeTask task;
|
|
final Function(List<String>) onBarcodesCompleted;
|
|
|
|
const BarcodeCaptureScreen({super.key, required this.task, required this.onBarcodesCompleted});
|
|
|
|
@override
|
|
State<BarcodeCaptureScreen> createState() => _BarcodeCaptureScreenState();
|
|
}
|
|
|
|
class _BarcodeCaptureScreenState extends State<BarcodeCaptureScreen> {
|
|
final List<String> _scannedBarcodes = [];
|
|
final List<TextEditingController> _textControllers = [];
|
|
MobileScannerController? _scannerController;
|
|
bool _isMobilePlatform = false;
|
|
bool _isScannerInitialized = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_detectPlatformAndInit();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_scannerController?.dispose();
|
|
for (final controller in _textControllers) {
|
|
controller.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
void _detectPlatformAndInit() {
|
|
// Determine if we're on a mobile platform
|
|
if (kIsWeb) {
|
|
_isMobilePlatform = false;
|
|
_initializeDesktopMode();
|
|
} else {
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.iOS:
|
|
_isMobilePlatform = true;
|
|
_initializeMobileScanner();
|
|
break;
|
|
case TargetPlatform.macOS:
|
|
case TargetPlatform.windows:
|
|
case TargetPlatform.linux:
|
|
_isMobilePlatform = false;
|
|
_initializeDesktopMode();
|
|
break;
|
|
default:
|
|
_isMobilePlatform = false;
|
|
_initializeDesktopMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
void _initializeMobileScanner() {
|
|
try {
|
|
_scannerController = MobileScannerController();
|
|
setState(() {
|
|
_isScannerInitialized = true;
|
|
});
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${AppLocalizations.of(context).cameraError}: $e')));
|
|
}
|
|
}
|
|
}
|
|
|
|
void _initializeDesktopMode() {
|
|
// Create text controllers for desktop input fields
|
|
for (int i = 0; i < widget.task.maxBarcodeCount; i++) {
|
|
_textControllers.add(TextEditingController());
|
|
}
|
|
setState(() {
|
|
_isScannerInitialized = true;
|
|
});
|
|
}
|
|
|
|
void _onBarcodeDetected(BarcodeCapture capture) {
|
|
final List<Barcode> barcodes = capture.barcodes;
|
|
for (final barcode in barcodes) {
|
|
final String? code = barcode.rawValue;
|
|
if (code != null && code.isNotEmpty && !_scannedBarcodes.contains(code)) {
|
|
if (_scannedBarcodes.length < widget.task.maxBarcodeCount) {
|
|
setState(() {
|
|
_scannedBarcodes.add(code);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _removeBarcode(int index) {
|
|
setState(() {
|
|
_scannedBarcodes.removeAt(index);
|
|
});
|
|
}
|
|
|
|
void _finishTask() {
|
|
final List<String> barcodes;
|
|
if (_isMobilePlatform) {
|
|
barcodes = _scannedBarcodes;
|
|
} else {
|
|
// Collect barcodes from text fields
|
|
barcodes = [];
|
|
for (final controller in _textControllers) {
|
|
if (controller.text.trim().isNotEmpty) {
|
|
barcodes.add(controller.text.trim());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Navigate back to task view first
|
|
Navigator.of(context).pop();
|
|
|
|
// Then call the completion callback
|
|
widget.onBarcodesCompleted(barcodes);
|
|
}
|
|
|
|
bool _canFinish() {
|
|
if (_isMobilePlatform) {
|
|
return _scannedBarcodes.length >= widget.task.minBarcodeCount;
|
|
} else {
|
|
int filledFields = 0;
|
|
for (final controller in _textControllers) {
|
|
if (controller.text.trim().isNotEmpty) {
|
|
filledFields++;
|
|
}
|
|
}
|
|
return filledFields >= widget.task.minBarcodeCount;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(appBar: AppBar(title: Text(AppLocalizations.of(context).barcodeScan), backgroundColor: Colors.deepPurple[100], leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.of(context).pop())), body: Column(children: [OfflineBanner(), Expanded(child: _isScannerInitialized ? (_isMobilePlatform ? _buildMobileView() : _buildDesktopView()) : const Center(child: CircularProgressIndicator()))]));
|
|
}
|
|
|
|
Widget _buildMobileView() {
|
|
return Column(
|
|
children: [
|
|
// Scanner view
|
|
Expanded(
|
|
flex: 3,
|
|
child: Stack(
|
|
children: [
|
|
MobileScanner(controller: _scannerController, onDetect: _onBarcodeDetected),
|
|
// Overlay with scanning frame
|
|
Container(decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.5)), child: Center(child: Container(width: 250, height: 250, decoration: BoxDecoration(border: Border.all(color: Colors.white, width: 2), borderRadius: BorderRadius.circular(12)), child: Container(margin: const EdgeInsets.all(20), decoration: BoxDecoration(border: Border.all(color: Colors.green, width: 2), borderRadius: BorderRadius.circular(8)))))),
|
|
],
|
|
),
|
|
),
|
|
// Scanned barcodes list
|
|
Expanded(
|
|
flex: 2,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('${AppLocalizations.of(context).scannedBarcodes} (${_scannedBarcodes.length}/${widget.task.maxBarcodeCount})', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 8),
|
|
Text('${AppLocalizations.of(context).minBarcodes} ${widget.task.minBarcodeCount} ${AppLocalizations.of(context).barcodesRequired}', style: TextStyle(fontSize: 14, color: Colors.grey[600])),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child: ListView.builder(
|
|
itemCount: _scannedBarcodes.length,
|
|
itemBuilder: (context, index) {
|
|
return Card(child: ListTile(leading: const Icon(Icons.qr_code), title: Text(_scannedBarcodes[index]), trailing: IconButton(icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => _removeBarcode(index))));
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _canFinish() ? _finishTask : null, style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)), child: Text(AppLocalizations.of(context).finish))),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildDesktopView() {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(AppLocalizations.of(context).enterBarcode, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 8),
|
|
Text('${AppLocalizations.of(context).barcodeEnterDescription} (${widget.task.minBarcodeCount}-${widget.task.maxBarcodeCount})', style: TextStyle(fontSize: 16, color: Colors.grey[600])),
|
|
const SizedBox(height: 24),
|
|
Expanded(
|
|
child: ListView.builder(
|
|
itemCount: widget.task.maxBarcodeCount,
|
|
itemBuilder: (context, index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12),
|
|
child: TextField(
|
|
controller: _textControllers[index],
|
|
decoration: InputDecoration(labelText: index < widget.task.minBarcodeCount ? AppLocalizations.of(context).barcodeNumberRequired(index + 1) : AppLocalizations.of(context).barcodeNumberOptional(index + 1), border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.qr_code)),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
// Trigger rebuild to update button state
|
|
});
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _canFinish() ? _finishTask : null, style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)), child: Text(AppLocalizations.of(context).finish))),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|