barcode_scanner/lib/pages/operation/reception/reception_scan_page.dart
your-name 5f158fb24e enhance: Enhances reception product scanning
Refreshes the list of receptions on the main page after returning from the details view, ensuring the displayed data is up-to-date.

Improves the product scanning logic by ensuring product lookups are specific to the current reception. This prevents misidentification of products across different receptions.

Adds an error toast message when a scanned product is not found within the active reception's product list, providing immediate user feedback. Refactors product retrieval and existence checks for clarity and robustness.
2025-07-31 04:26:54 +03:00

408 lines
13 KiB
Dart

import 'dart:async';
import 'package:e_scan/components/components.dart';
import 'package:e_scan/pages/operation/reception/reception_scan_page_model.dart';
import 'package:e_scan/themes/app_theme.dart';
import 'package:e_scan/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ReceptionScanPage extends ConsumerStatefulWidget {
const ReceptionScanPage({super.key, required this.receptionId});
final int receptionId;
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
_ReceptionScanPageState();
}
class _ReceptionScanPageState extends ConsumerState<ReceptionScanPage>
with WidgetsBindingObserver {
final MobileScannerController mobileScannerController =
MobileScannerController(
// required options for the scanner
);
StreamSubscription<Object?>? _subscription;
bool qrcodeFound = false;
@override
void initState() {
super.initState();
// Start listening to lifecycle changes.
WidgetsBinding.instance.addObserver(this);
SchedulerBinding.instance.addPostFrameCallback((_) {
ref
.read(receptionScanPageModelProvider.notifier)
.getReceptionById(id: widget.receptionId);
});
// Start listening to the barcode events.
_subscription = mobileScannerController.barcodes.listen(_handleBarcode);
// Finally, start the scanner itself.
unawaited(mobileScannerController.start());
}
@override
void dispose() {
// Stop listening to lifecycle changes.
WidgetsBinding.instance.removeObserver(this);
// Stop listening to the barcode events.
unawaited(_subscription?.cancel());
_subscription = null;
// Dispose the widget itself.
// Finally, dispose of the controller.
mobileScannerController.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// If the controller is not ready, do not try to start or stop it.
// Permission dialogs can trigger lifecycle changes before the controller is ready.
if (!mobileScannerController.value.hasCameraPermission) {
return;
}
switch (state) {
case AppLifecycleState.detached:
case AppLifecycleState.hidden:
case AppLifecycleState.paused:
return;
case AppLifecycleState.resumed:
// Restart the scanner when the app is resumed.
// Don't forget to resume listening to the barcode events.
_subscription = mobileScannerController.barcodes.listen(_handleBarcode);
unawaited(mobileScannerController.start());
case AppLifecycleState.inactive:
// Stop the scanner when the app is paused.
// Also stop the barcode events subscription.
unawaited(_subscription?.cancel());
_subscription = null;
unawaited(mobileScannerController.stop());
}
}
Future<void> _handleBarcode(BarcodeCapture barcodeCapture) async {
if (qrcodeFound) return;
if (barcodeCapture.barcodes.isNotEmpty) {
qrcodeFound = true;
mobileScannerController.stop();
final code = barcodeCapture.barcodes.first;
final qrcodeValue = code.displayValue;
debugPrint("Code détecté : ${code.displayValue}");
final model = ref.read(receptionScanPageModelProvider.notifier);
if (qrcodeValue == null) {
debugPrint("Produit non attendu dans cette réception");
Toast.showError('Produit non attendu dans cette réception');
return;
}
// find product in local database
final isProductExist = model.getProduct(
receptionId: widget.receptionId,
barcode: qrcodeValue,
);
if (isProductExist != null) {
final product = model.getProduct(
receptionId: widget.receptionId,
barcode: qrcodeValue,
);
model.incrementMoveLineQuantity(
barcode: qrcodeValue,
receptionId: widget.receptionId,
);
//show dialog
await showDialog(
barrierDismissible: false,
context: context,
builder: (dialogContext) {
return Dialog(
elevation: 0,
insetPadding: EdgeInsets.zero,
backgroundColor: Colors.transparent,
alignment: AlignmentDirectional(
0.0,
0.0,
).resolve(Directionality.of(context)),
child: GestureDetector(
onTap: () {
FocusScope.of(dialogContext).unfocus();
FocusManager.instance.primaryFocus?.unfocus();
},
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 0.9,
child: ProductScannedComponent(
productStruct: product!,
onRescan: () async {
Navigator.of(context).pop();
qrcodeFound = false;
mobileScannerController.start();
},
onDetails: () {
unawaited(_subscription?.cancel());
_subscription = null;
unawaited(mobileScannerController.stop());
Navigator.of(context).pop();
Navigator.of(context).pop();
},
),
),
),
);
},
);
} else {
Toast.showError('Produit non attendu dans cette réception');
debugPrint('Produit non attendu dans cette réception');
}
}
}
Future<void> _fakeBarcode() async {
final qrcodeValue = "AIRF0001";
qrcodeFound = true;
final model = ref.read(receptionScanPageModelProvider.notifier);
mobileScannerController.stop();
// find product in local database
final isProductExist = model.getProduct(
receptionId: widget.receptionId,
barcode: qrcodeValue,
);
if (isProductExist != null) {
final product = model.getProduct(
receptionId: widget.receptionId,
barcode: qrcodeValue,
);
model.incrementMoveLineQuantity(
barcode: qrcodeValue,
receptionId: widget.receptionId,
onError: () {
Toast.showError('Aucun produit trouvé.');
},
);
//show dialog
await showDialog(
barrierDismissible: false,
context: context,
builder: (dialogContext) {
return Dialog(
elevation: 0,
insetPadding: EdgeInsets.zero,
backgroundColor: Colors.transparent,
alignment: AlignmentDirectional(
0.0,
0.0,
).resolve(Directionality.of(context)),
child: GestureDetector(
onTap: () {
FocusScope.of(dialogContext).unfocus();
FocusManager.instance.primaryFocus?.unfocus();
},
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 0.9,
child: ProductScannedComponent(
productStruct: product!,
onRescan: () async {
qrcodeFound = false;
mobileScannerController.start();
Navigator.of(context).pop();
},
onDetails: () {
unawaited(_subscription?.cancel());
_subscription = null;
unawaited(mobileScannerController.stop());
Navigator.of(context).pop();
Navigator.of(context).pop();
},
),
),
),
);
},
);
} else {
Toast.showError('Aucun produit trouvé.');
debugPrint('Aucun produit trouvé.');
}
}
@override
Widget build(BuildContext context) {
final state = ref.watch(receptionScanPageModelProvider);
final reception = state.reception;
return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
appBar: AppBar(
backgroundColor: AppTheme.of(context).primaryBackground,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: AppTheme.of(context).primaryText,
size: 24.0,
),
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
'Réception ${reception?.name}',
style: AppTheme.of(context).titleLarge,
),
actions: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(8.0, 0.0, 8.0, 0.0),
child: IconButton(
icon: Icon(Icons.help_outline, color: Colors.white, size: 24.0),
onPressed: () {
debugPrint('IconButton pressed ...');
},
),
),
],
centerTitle: true,
elevation: 0.0,
),
body: state.loading
? Center(child: LoadingProgressComponent())
: SizedBox(
width: double.infinity,
height: double.infinity,
child: Stack(
children: [
SizedBox(
width: double.infinity,
height: double.infinity,
child: MobileScanner(
controller: mobileScannerController,
onDetectError: (error, stackTrace) {
debugPrint("===========> $error");
},
errorBuilder: (c, p2) {
return Center(child: Icon(Icons.error));
},
),
),
Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppTheme.of(context).primaryBackground,
Colors.transparent,
AppTheme.of(context).primaryBackground,
],
stops: [0.0, 0.5, 1.0],
begin: AlignmentDirectional(0.0, -1.0),
end: AlignmentDirectional(0, 1.0),
),
),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_InfoTextScan(),
_ZoneScanBox(
onTap: () {
_fakeBarcode();
qrcodeFound = false;
mobileScannerController.start();
},
),
FlashButtonComponent(
mobileScannerController: mobileScannerController,
),
],
),
),
),
],
),
),
);
}
}
class _ZoneScanBox extends StatelessWidget {
const _ZoneScanBox({this.onTap});
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return Align(
alignment: AlignmentDirectional(0.0, 0.0),
child: Container(
width: 280.0,
height: 280.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
border: Border.all(color: Colors.white, width: 3.0),
),
child: GestureDetector(
onTap: onTap,
child: Container(
width: 260.0,
height: 260.0,
decoration: BoxDecoration(
color: Color(0x33FFFFFF),
borderRadius: BorderRadius.circular(12.0),
),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.qr_code_scanner,
color: Colors.white.withValues(alpha: .5),
size: 64.0,
),
],
),
),
),
),
),
);
}
}
class _InfoTextScan extends StatelessWidget {
const _InfoTextScan();
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsetsDirectional.fromSTEB(24.0, 40.0, 24.0, 0.0),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Scanner le code',
textAlign: TextAlign.center,
style: TextStyle(color: AppTheme.of(context).primaryText),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(0.0, 8.0, 0.0, 0.0),
child: Text(
'Positionnez le code-barres ou QR code dans le cadre',
textAlign: TextAlign.center,
style: TextStyle(color: AppTheme.of(context).primaryText),
),
),
],
),
);
}
}