Files
lis/mylis/lib/screens/dashboard/barcode_scanner_screen.dart
T
2026-08-02 10:46:34 +07:00

170 lines
4.9 KiB
Dart

part of '../../app/app.dart';
class BarcodeScannerScreen extends StatefulWidget {
const BarcodeScannerScreen({super.key});
@override
State<BarcodeScannerScreen> createState() => _BarcodeScannerScreenState();
}
class _BarcodeScannerScreenState extends State<BarcodeScannerScreen> {
final MobileScannerController _controller = MobileScannerController();
final _manualCode = TextEditingController();
bool _handled = false;
@override
void dispose() {
_manualCode.dispose();
_controller.dispose();
super.dispose();
}
void _handleDetect(BarcodeCapture capture) {
if (_handled) {
return;
}
final value = capture.barcodes
.map((barcode) => barcode.rawValue?.trim() ?? '')
.firstWhere((text) => text.isNotEmpty, orElse: () => '');
if (value.isEmpty) {
return;
}
_handled = true;
Navigator.of(context).pop(value);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scan Barcode Sampel'),
actions: [
IconButton(
onPressed: () => _controller.toggleTorch(),
icon: const Icon(Icons.flashlight_on_outlined),
tooltip: 'Lampu',
),
],
),
body: Stack(
children: [
MobileScanner(
controller: _controller,
onDetect: _handleDetect,
errorBuilder: (context, error) => _ScannerFallback(
controller: _manualCode,
message:
'Scanner belum tersedia. Tutup aplikasi lalu jalankan ulang full rebuild, atau masukkan barcode manual.',
onSubmit: _submitManualCode,
),
placeholderBuilder: (context) =>
const Center(child: CircularProgressIndicator()),
),
Center(
child: Container(
width: 260,
height: 180,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 3),
borderRadius: BorderRadius.circular(16),
),
),
),
Positioned(
left: 18,
right: 18,
bottom: 24,
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.62),
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'Arahkan kamera ke barcode nomor sampel.',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
);
}
void _submitManualCode() {
final code = _manualCode.text.trim();
if (code.isEmpty) {
return;
}
Navigator.of(context).pop(code);
}
}
class _ScannerFallback extends StatelessWidget {
const _ScannerFallback({
required this.controller,
required this.message,
required this.onSubmit,
});
final TextEditingController controller;
final String message;
final VoidCallback onSubmit;
@override
Widget build(BuildContext context) {
return Container(
color: const Color(0xFFF7FAF9),
padding: const EdgeInsets.all(18),
child: Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Icon(
Icons.qr_code_scanner_rounded,
size: 42,
color: Color(0xFF0F766E),
),
const SizedBox(height: 10),
Text(
message,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.black54),
),
const SizedBox(height: 14),
TextField(
controller: controller,
autofocus: true,
textInputAction: TextInputAction.search,
onSubmitted: (_) => onSubmit(),
decoration: const InputDecoration(
labelText: 'Barcode / No. Sampel',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: onSubmit,
icon: const Icon(Icons.manage_search_rounded),
label: const Text('Gunakan Kode'),
),
],
),
),
),
),
);
}
}