
Fetches detailed move line and move data, including product information, for stock picking records from the API. Centralizes ObjectBox persistence within `toEntity()` methods for product, move line, and move models, ensuring data is saved upon conversion. Correctly associates product entities with move line and move entities in the local database. Refactors API call processing to directly return mapped entities, leveraging the updated `toEntity()` behavior.
425 lines
16 KiB
Dart
425 lines
16 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:e_scan/backend/api/api_calls.dart';
|
|
import 'package:e_scan/backend/objectbox/entities/product/product_entity.dart';
|
|
import 'package:e_scan/backend/objectbox/objectbox_manager.dart';
|
|
import 'package:e_scan/backend/schema/product/product_model.dart';
|
|
import 'package:e_scan/components/loading_progress_component.dart';
|
|
import 'package:e_scan/components/product_scanned_component.dart';
|
|
import 'package:e_scan/router/go_secure_router_builder.dart';
|
|
import 'package:e_scan/themes/app_theme.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
|
|
|
class ScannerPage extends ConsumerStatefulWidget {
|
|
const ScannerPage({super.key});
|
|
|
|
@override
|
|
ConsumerState<ConsumerStatefulWidget> createState() => _ScannerPageState();
|
|
}
|
|
|
|
class _ScannerPageState extends ConsumerState<ScannerPage>
|
|
with WidgetsBindingObserver {
|
|
final MobileScannerController mobileScannerController =
|
|
MobileScannerController(
|
|
// required options for the scanner
|
|
);
|
|
|
|
StreamSubscription<Object?>? _subscription;
|
|
bool qrcodeFound = false;
|
|
bool loading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Start listening to lifecycle changes.
|
|
WidgetsBinding.instance.addObserver(this);
|
|
|
|
// 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}");
|
|
|
|
if (qrcodeValue == null) {
|
|
debugPrint("Qrcode non valide");
|
|
return;
|
|
}
|
|
setState(() {
|
|
loading = true;
|
|
});
|
|
final product = await ApiCalls.fetchProduct(code.displayValue!);
|
|
if (product != null) {
|
|
setState(() {
|
|
loading = false;
|
|
});
|
|
debugPrint('Nom du produit : ${product["product_name"]}');
|
|
debugPrint('Marque : ${product["brands"]}');
|
|
debugPrint('Image : ${product["image_url"]}');
|
|
final productStruct = ProductModel(
|
|
id: int.parse(product["id"]),
|
|
displayName: product["generic_name"],
|
|
);
|
|
Box<ProductEntity> productStore = objectboxManager.store
|
|
.box<ProductEntity>();
|
|
productStore.put(productStruct.toEntity());
|
|
//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: productStruct,
|
|
onRescan: () async {
|
|
Navigator.of(context).pop();
|
|
qrcodeFound = false;
|
|
mobileScannerController.start();
|
|
},
|
|
onDetails: () {
|
|
Navigator.of(context).pop();
|
|
unawaited(_subscription?.cancel());
|
|
_subscription = null;
|
|
unawaited(mobileScannerController.stop());
|
|
ProductFormRoute(id: productStruct.id ?? 0).push(context);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
} else {
|
|
setState(() {
|
|
loading = false;
|
|
});
|
|
debugPrint('Aucun produit trouvé.');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _fakeBarcode() async {
|
|
final qrcodeValue = "0737628064502";
|
|
qrcodeFound = true;
|
|
mobileScannerController.stop();
|
|
setState(() {
|
|
loading = true;
|
|
});
|
|
final product = await ApiCalls.fetchProduct(qrcodeValue);
|
|
if (product != null) {
|
|
setState(() {
|
|
loading = false;
|
|
});
|
|
debugPrint('Nom du produit : ${product["product_name"]}');
|
|
debugPrint('Marque : ${product["brands"]}');
|
|
debugPrint('Image : ${product["image_url"]}');
|
|
final productStruct = ProductModel(
|
|
id: int.parse(product["id"]),
|
|
displayName: product["generic_name"],
|
|
);
|
|
Box<ProductEntity> productStore = objectboxManager.store
|
|
.box<ProductEntity>();
|
|
productStore.put(productStruct.toEntity());
|
|
//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: productStruct,
|
|
onRescan: () async {
|
|
Navigator.of(context).pop();
|
|
qrcodeFound = false;
|
|
mobileScannerController.start();
|
|
},
|
|
onDetails: () {
|
|
Navigator.of(context).pop();
|
|
unawaited(_subscription?.cancel());
|
|
_subscription = null;
|
|
unawaited(mobileScannerController.stop());
|
|
ProductFormRoute(id: productStruct.id ?? 0).push(context);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
} else {
|
|
setState(() {
|
|
loading = false;
|
|
});
|
|
debugPrint('Aucun produit trouvé.');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
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('Scanner', 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: 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: [
|
|
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,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
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: () {
|
|
_fakeBarcode();
|
|
qrcodeFound = false;
|
|
mobileScannerController.start();
|
|
},
|
|
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,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(40),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.max,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
IconButton(
|
|
color: AppTheme.of(context).primaryText,
|
|
iconSize: 2,
|
|
icon: Icon(
|
|
mobileScannerController.torchEnabled
|
|
? Icons.flash_off
|
|
: Icons.flash_on,
|
|
color: AppTheme.of(context).primaryText,
|
|
size: 28.0,
|
|
),
|
|
onPressed: () {
|
|
mobileScannerController.toggleTorch();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|