
Performs a comprehensive project rename from 'barcode_scanner' to 'e_scan' (or 'eScan' for user-facing labels). This update spans all relevant files, including: - Application IDs and bundle identifiers for Android, iOS, macOS, and Linux. - VS Code launch configurations. - Dart package import paths. - Project names and titles in `pubspec.yaml`, `README.md`, and platform-specific configurations (e.g., CMakeLists, Info.plist, AndroidManifest).
431 lines
16 KiB
Dart
431 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"]),
|
|
image: product["image_thumb_url"],
|
|
name: product["generic_name"],
|
|
description: product["product_name"],
|
|
quantity: product["quantity"],
|
|
);
|
|
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).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"]),
|
|
image: product["image_thumb_url"],
|
|
name: product["generic_name"],
|
|
description: product["product_name"],
|
|
quantity: product["quantity"],
|
|
);
|
|
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).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();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|