diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f5484e9..4dea81c 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -41,5 +41,9 @@ UIViewControllerBasedStatusBarAppearance + NSCameraUsageDescription + Требуется доступ к камере для сканирования QR-кодов и штрих-кодов + io.flutter.embedded_views_preview + diff --git a/lib/core/redux/actions/user_actions.dart b/lib/core/redux/actions/user_actions.dart index 440c280..d785136 100644 --- a/lib/core/redux/actions/user_actions.dart +++ b/lib/core/redux/actions/user_actions.dart @@ -45,6 +45,27 @@ ThunkAction authenticate(String email, String password) { }; } +ThunkAction authenticateByToken(String token) { + return (Store store) async { + store.dispatch(SetUserStateAction(UserState(isLoading: true))); + try { + AuthResponse result = await _api.authorization(token); + if ( result.operation!) { + _api.token = result.token!; + store.dispatch(SetUserStateAction(UserState(isLoading: false, auth: result))); + _navigation.replace(MainViewRoute); + _afterAuth(store); + } else { + _dialogService.showDialog(title: 'Внимание', buttonTitle: 'Ok', description: result.message!); + } + } catch (e) { + print(e); + } finally { + store.dispatch(SetUserStateAction(UserState(isLoading: false))); + } + }; +} + Future auth(Store store) async { store.dispatch(SetUserStateAction(UserState(isLoading: true))); try { diff --git a/lib/core/redux/store.dart b/lib/core/redux/store.dart index c668bcb..d344ab9 100644 --- a/lib/core/redux/store.dart +++ b/lib/core/redux/store.dart @@ -95,8 +95,8 @@ class Redux { serializer: JsonSerializer(AppState.fromJson), // Or use other serializers ); - final initialState = await persist.load(); - final userStateInitial = UserState.initial(initialState!.userState!); + final AppState? initialState = await persist.load(); + final userStateInitial = UserState.initial(initialState?.userState); final navStateInitial = NavState.initial(); final sellStateInitial = SellState.initial(); diff --git a/lib/core/services/dictionary_service.dart b/lib/core/services/dictionary_service.dart index cb69690..35c4b08 100644 --- a/lib/core/services/dictionary_service.dart +++ b/lib/core/services/dictionary_service.dart @@ -91,6 +91,22 @@ class DictionaryService extends BaseService { return list; } + Future> getGoodsByEan( String code ) async { + List list = []; + try { + int? appCompanyId = Redux.store!.state.userState!.auth!.companyId; + String where = '( $GoodColumnAppCompanyId = ? and $GoodColumnEan like ? ) '; + List args = [appCompanyId, '%$code%']; + List> elements = await _db.queryRowsWithWhere(GoodTableName, where, args); + elements.forEach((element) { + list.add(Good.fromMap(element)); + }); + } catch (e, stack) { + log.e("getGoodsByEan", e, stack); + } + return list; + } + bool _isNumericInt(String str) { if(str == null) { return false; diff --git a/lib/routes/router.dart b/lib/routes/router.dart index b818563..47f020e 100644 --- a/lib/routes/router.dart +++ b/lib/routes/router.dart @@ -35,7 +35,7 @@ Route generateRoute(RouteSettings settings) { case AddByBarcodeViewRoute: return _getPageRoute( routeName: settings.name!, - viewToShow: AddByBarcodeView(title: 'Scanner',), + viewToShow: AddByBarcodeView(), ); case SettingPrinterBluetoothViewRoute: return _getPageRoute( diff --git a/lib/views/add_by_barcode/add_by_barcode_view.dart b/lib/views/add_by_barcode/add_by_barcode_view.dart index f0db8dd..5034cbc 100644 --- a/lib/views/add_by_barcode/add_by_barcode_view.dart +++ b/lib/views/add_by_barcode/add_by_barcode_view.dart @@ -2,15 +2,13 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:satu/views/work/tabs/component/products_app_bar.dart'; +import 'package:satu/widgets/tools/app_barcode_scanner_widget.dart'; class AddByBarcodeView extends StatefulWidget { - final String? title; - final int? transactionId; const AddByBarcodeView({ - Key? key, - this.title, - this.transactionId, + Key? key }) : super(key: key); @override @@ -18,19 +16,27 @@ class AddByBarcodeView extends StatefulWidget { } class _AddByBarcodeViewState extends State { - @override - void initState() { - super.initState(); - } - + String _code = ''; @override Widget build(BuildContext context) { - print('barcode = image-${widget.transactionId}'); - return Container( - child: Center( - child: Hero( - tag: 'text', - child: Text(widget.title ?? '')), + return Scaffold( + appBar: ProductsAppBar( + title: 'Сканер', + ), + body: Column( + children: [ + Expanded( + child: AppBarcodeScannerWidget.defaultStyle( + resultCallback: (String code) { + Navigator.pop(context, code); + // print(code); + // setState(() { + // _code = code; + // }); + }, + ), + ), + ], ), ); } diff --git a/lib/views/login/login_view.dart b/lib/views/login/login_view.dart index ebbea81..5fe19eb 100644 --- a/lib/views/login/login_view.dart +++ b/lib/views/login/login_view.dart @@ -9,7 +9,9 @@ import 'package:satu/core/redux/actions/user_actions.dart'; import 'package:satu/core/redux/state/user_state.dart'; import 'package:satu/core/redux/store.dart'; import 'package:satu/core/services/dialog_service.dart'; +import 'package:satu/core/services/navigator_service.dart'; import 'package:satu/core/utils/locator.dart'; +import 'package:satu/routes/route_names.dart'; import 'package:satu/shared/app_colors.dart'; import 'package:satu/shared/ui_helpers.dart'; import 'package:satu/widgets/buttons/busy_button.dart'; @@ -102,6 +104,16 @@ class _LoginViewState extends State { } Future scan() async { + + String? result = await locator().push(AddByBarcodeViewRoute); + if(result != null) { + if( result.length == 60 ) { + Redux.store?.dispatch(authenticateByToken(result)); + } else { + _dialogService.showDialog(description: 'Не верный формат QR кода'); + } + } + // try { // var options = ScanOptions(strings: { // "cancel": 'Отмена', diff --git a/lib/views/work/tabs/component/product_list_item.dart b/lib/views/work/tabs/component/product_list_item.dart index 60362ce..33a6a55 100644 --- a/lib/views/work/tabs/component/product_list_item.dart +++ b/lib/views/work/tabs/component/product_list_item.dart @@ -33,10 +33,7 @@ class _ProductListItemState extends State { void _onItemTapped(BuildContext context) { Navigator.of(context).push(new MaterialPageRoute( - builder: (BuildContext context) => new AddByBarcodeView( - title: widget.name, - transactionId: widget.transactionId, - ))); + builder: (BuildContext context) => new AddByBarcodeView())); } @override diff --git a/lib/views/work/tabs/component/products_app_bar.dart b/lib/views/work/tabs/component/products_app_bar.dart index 98171db..c0b2cc9 100644 --- a/lib/views/work/tabs/component/products_app_bar.dart +++ b/lib/views/work/tabs/component/products_app_bar.dart @@ -11,6 +11,7 @@ class ProductsAppBar extends StatelessWidget implements PreferredSizeWidget { final int? childHeight; final double elevation; final Color? backgroundColor; + final bool drawerShow; const ProductsAppBar( {Key? key, @@ -19,6 +20,7 @@ class ProductsAppBar extends StatelessWidget implements PreferredSizeWidget { this.child, this.childHeight = 0, this.elevation = 0.0, + this.drawerShow = false, this.backgroundColor = Colors.transparent}) : super(key: key); @@ -44,7 +46,7 @@ class ProductsAppBar extends StatelessWidget implements PreferredSizeWidget { centerTitle: true, backgroundColor: Colors.transparent, elevation: 0.0, - leading: IconButton( + leading: drawerShow ? IconButton( icon: Icon( Icons.menu, color: blackColor, @@ -55,7 +57,7 @@ class ProductsAppBar extends StatelessWidget implements PreferredSizeWidget { .currentState! .openDrawer(); }, - ), + ) : null , actions: actions, ), if (child != null && childHeight! > 0) child!, diff --git a/lib/views/work/tabs/sell_view.dart b/lib/views/work/tabs/sell_view.dart index 46f4264..5badf4b 100644 --- a/lib/views/work/tabs/sell_view.dart +++ b/lib/views/work/tabs/sell_view.dart @@ -2,10 +2,12 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:satu/core/entity/Goods.dart'; import 'package:satu/core/models/flow/product_dao.dart'; import 'package:satu/core/redux/actions/sell_actions.dart'; import 'package:satu/core/redux/state/sell_state.dart'; import 'package:satu/core/redux/store.dart'; +import 'package:satu/core/services/dictionary_service.dart'; import 'package:satu/core/services/navigator_service.dart'; import 'package:satu/core/utils/locator.dart'; import 'package:satu/routes/route_names.dart'; @@ -27,6 +29,7 @@ class SellView extends StatelessWidget { builder: (_, state) { return Scaffold( appBar: ProductsAppBar( + drawerShow: true, title: 'Продажа', child: ProductHeaderBar( count: state.items!.length, @@ -103,7 +106,15 @@ class SellView extends StatelessWidget { verticalSpaceMedium, FloatingActionButton( elevation: 2, - onPressed: () => locator().push(AddByBarcodeViewRoute), + onPressed: () async { + String? result = await locator().push(AddByBarcodeViewRoute); + if(result !=null) { + List goods = await locator().getGoodsByEan(result); + if(goods.isNotEmpty) { + Redux.store?.dispatch(addSellItem( good: goods.first)); + } + } + }, child: Icon(Icons.qr_code_rounded, size: 35.sp, color: whiteColor), ), ], diff --git a/lib/widgets/tools/app_barcode_scanner_widget.dart b/lib/widgets/tools/app_barcode_scanner_widget.dart new file mode 100644 index 0000000..5a43a60 --- /dev/null +++ b/lib/widgets/tools/app_barcode_scanner_widget.dart @@ -0,0 +1,179 @@ +import 'package:ai_barcode/ai_barcode.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; + +late String _label; +late Function(String result) _resultCallback; + +/// +/// AppBarcodeScannerWidget +class AppBarcodeScannerWidget extends StatefulWidget { + /// + /// + AppBarcodeScannerWidget.defaultStyle({ + Function(String result)? resultCallback, + String label = 'Заголовок', + }) { + _resultCallback = resultCallback ?? (String result) {}; + _label = label; + } + + @override + _AppBarcodeState createState() => _AppBarcodeState(); +} + +class _AppBarcodeState extends State { + @override + Widget build(BuildContext context) { + return _BarcodePermissionWidget(); + } +} + +class _BarcodePermissionWidget extends StatefulWidget { + @override + State createState() { + return _BarcodePermissionWidgetState(); + } +} + +class _BarcodePermissionWidgetState extends State<_BarcodePermissionWidget> { + bool _isGranted = false; + + bool _useCameraScan = true; + + String _inputValue = ""; + + @override + void initState() { + super.initState(); + } + + void _requestMobilePermission() async { + if (await Permission.camera.request().isGranted) { + setState(() { + _isGranted = true; + }); + } + } + + @override + Widget build(BuildContext context) { + TargetPlatform platform = Theme.of(context).platform; + if (!kIsWeb) { + if (platform == TargetPlatform.android || + platform == TargetPlatform.iOS) { + _requestMobilePermission(); + } else { + setState(() { + _isGranted = true; + }); + } + } else { + setState(() { + _isGranted = true; + }); + } + + return Column( + children: [ + Expanded( + child: _isGranted + ? _BarcodeScannerWidget() + : Center( + child: OutlinedButton( + onPressed: () { + _requestMobilePermission(); + }, + child: Text("Запроса на разрешения"), + ), + ), + ), + // _useCameraScan + // ? OutlinedButton( + // onPressed: () { + // setState(() { + // _useCameraScan = false; + // }); + // }, + // child: Text("Использовать камеру $_label"), + // ) + // : Row( + // children: [ + // OutlineButton( + // onPressed: () { + // setState(() { + // _useCameraScan = true; + // }); + // }, + // child: Text("_useCameraScan = true $_label"), + // ), + // OutlineButton( + // onPressed: () { + // _resultCallback(_inputValue); + // }, + // child: Text("_resultCallback"), + // ), + // ], + // ), + ], + ); + } +} + + +///ScannerWidget +class _BarcodeScannerWidget extends StatefulWidget { + @override + State createState() { + return _AppBarcodeScannerWidgetState(); + } +} + +class _AppBarcodeScannerWidgetState extends State<_BarcodeScannerWidget> { + late ScannerController _scannerController; + + @override + void initState() { + super.initState(); + + _scannerController = ScannerController(scannerResult: (result) { + _resultCallback(result); + }, scannerViewCreated: () { + TargetPlatform platform = Theme.of(context).platform; + if (TargetPlatform.iOS == platform) { + Future.delayed(Duration(seconds: 2), () { + _scannerController.startCamera(); + _scannerController.startCameraPreview(); + }); + } else { + _scannerController.startCamera(); + _scannerController.startCameraPreview(); + } + }); + } + + @override + void dispose() { + super.dispose(); + _scannerController.stopCameraPreview(); + _scannerController.stopCamera(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: _getScanWidgetByPlatform(), + ) + ], + ); + } + + Widget _getScanWidgetByPlatform() { + return PlatformAiBarcodeScannerWidget( + platformScannerController: _scannerController, + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 8683275..9fdbb60 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,27 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + ai_barcode: + dependency: "direct main" + description: + name: ai_barcode + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + ai_barcode_platform_interface: + dependency: transitive + description: + name: ai_barcode_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + ai_barcode_web: + dependency: transitive + description: + name: ai_barcode_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" async: dependency: transitive description: @@ -64,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" cupertino_icons: dependency: "direct main" description: @@ -149,6 +177,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.1.3" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.0" http: dependency: "direct main" description: @@ -282,6 +317,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.11.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.2" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.0" platform: dependency: transitive description: @@ -476,6 +525,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + universal_html: + dependency: transitive + description: + name: universal_html + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + universal_platform: + dependency: transitive + description: + name: universal_platform + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0+1" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bc99125..214b743 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,8 @@ dependencies: implicitly_animated_reorderable_list: ^0.4.0 uuid: ^3.0.4 charset_converter: ^2.0.0 + ai_barcode: ^3.0.1 + permission_handler: ^8.1.2 dev_dependencies: flutter_test: sdk: flutter