diff --git a/android/app/build.gradle b/android/app/build.gradle index 93cab99..5ed7547 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 30 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -53,7 +53,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "kz.com.aman.satu" minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName // multiDexEnabled true diff --git a/lib/core/entity/goods_entity.dart b/lib/core/entity/goods_entity.dart index c52e793..459e81e 100644 --- a/lib/core/entity/goods_entity.dart +++ b/lib/core/entity/goods_entity.dart @@ -1,3 +1,5 @@ +import 'package:satu/core/utils/utils_parse.dart'; + const String goodTableName = 'goods'; const String GoodColumnId = 'id'; const String GoodColumnCategoryId = 'category_id'; @@ -22,7 +24,7 @@ class Good { ean = map[GoodColumnEan] as String; appCompanyId = map[GoodColumnAppCompanyId] as int; optPrice = map[GoodColumnOptPrice] as num; - basePrice = map[GoodColumnBasePrice] as num; + basePrice = cast(map[GoodColumnBasePrice]); divisible = map[GoodColumnDivisible] as int; updatedAt = map[GoodColumnUpdatedAt] as String; } diff --git a/lib/core/services/data_service.dart b/lib/core/services/data_service.dart index cc46259..de5e8d8 100644 --- a/lib/core/services/data_service.dart +++ b/lib/core/services/data_service.dart @@ -25,7 +25,7 @@ class DataService extends BaseService { final DialogService _dialogService = locator(); - Future sellBtnHandler( + Future sellBtnHandler( {double card = 0, double nal = 0, double total = 0}) async { final SellRequest request = SellRequest(); final SellState sellState = Redux.store!.state.sellState!; @@ -46,9 +46,9 @@ class DataService extends BaseService { final SellResponse response = await _api.sell(request); if (response.operation == false) { _dialogService.showDialog(description: response.message); - return false; + return null; } - await _updateTransaction( + final TransactionData transactionData = await _updateTransaction( transactionState: transactionState, total: total, card: card, @@ -56,10 +56,10 @@ class DataService extends BaseService { sellResponse: response); await Redux.store!.dispatch(loadSellData); - return true; + return transactionData; } - Future _updateTransaction( + Future _updateTransaction( {required TransactionState transactionState, required double card, required double nal, @@ -83,6 +83,7 @@ class DataService extends BaseService { transaction.data = jsonEncode(data.toMap()); log.i(jsonEncode(data.toMap())); await _db.update(transactionTableName, transaction.toMap()); + return data; } ItemBean _productToItemBean(ProductDao product) { diff --git a/lib/core/utils/pos_printer.dart b/lib/core/utils/pos_printer.dart index 2ff2974..6e62b57 100644 --- a/lib/core/utils/pos_printer.dart +++ b/lib/core/utils/pos_printer.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:typed_data'; import 'package:charset_converter/charset_converter.dart'; @@ -5,81 +6,149 @@ import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart'; import 'package:esc_pos_utils/esc_pos_utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bluetooth_basic/flutter_bluetooth_basic.dart'; -import 'package:image/image.dart'; +import 'package:image/image.dart' as Im; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:satu/core/models/flow/check_bean.dart'; +import 'package:satu/core/models/flow/check_row_bean.dart'; import 'package:satu/core/models/settings/printer_setting.dart'; -Future> getReceipt(String encoding, String paperSize) async { +Future> getTestReceipt(String encoding, String paperSize) async { + final CheckBean checkBean = CheckBean(); + final CheckRowBean rowBean1 = CheckRowBean(); + rowBean1.text = 'Тестовый чек'; + rowBean1.center = true; + + final CheckRowBean rowBean2 = CheckRowBean(); + rowBean2.text = ['Левая сторона', 'Правая сторона']; + rowBean2.center = true; + + checkBean.rows.add(rowBean1); + checkBean.rows.add(CheckRowBean()..text='{br}'..center=true); + checkBean.rows.add(CheckRowBean()..text='{line}'..center=true); + checkBean.rows.add(CheckRowBean()..text='{br}'..center=true); + checkBean.rows.add(rowBean2); + checkBean.rows.add(CheckRowBean()..text='{br}'..center=true); + checkBean.rows.add(CheckRowBean()..text='{line}'..center=true); + checkBean.qr = 'test qr data'; + + + return await _generateReceipt(encoding, paperSize, checkBean); +} + +Future> getReceipt( + String encoding, String paperSize, CheckBean checkBean) async { + return await _generateReceipt(encoding, paperSize, checkBean); +} + +Future> _generateReceipt( + String encoding, String paperSize, CheckBean checkBean) async { + List bytes = []; final profile = await CapabilityProfile.load(); final generator = Generator( paperSize == PrinterConst.paperSize58mm ? PaperSize.mm58 : PaperSize.mm80, profile, ); - String codeTable = 'CP866'; - if(encoding == PrinterConst.encodingCP866) { + if (encoding == PrinterConst.encodingCP866) { codeTable = 'CP866'; - } else if(encoding == PrinterConst.encodingCP1251) { + } else if (encoding == PrinterConst.encodingCP1251) { codeTable = 'CP1251'; } - - - List bytes = []; generator.setGlobalCodeTable(codeTable); - final Uint8List firstCol = - await CharsetConverter.encode(encoding.toLowerCase(), 'Тестовый чек'); - bytes += generator.textEncoded(firstCol); - bytes += - generator.text('CENTER', styles: const PosStyles(align: PosAlign.center)); - bytes += generator.text('Right', - styles: const PosStyles(align: PosAlign.right), linesAfter: 1); + for (CheckRowBean row in checkBean.rows) { + String type = row.text.runtimeType.toString(); + final bool isList = type == 'List' || type == 'List'; + final bool isNull = type == 'Null'; + final bool isString = type == 'String'; + final bool center = row.center; - bytes += generator.row([ - PosColumn( - text: 'col3', - width: 3, - styles: const PosStyles(align: PosAlign.center, underline: true), - ), - PosColumn( - text: 'col6', - width: 6, - styles: const PosStyles(align: PosAlign.center, underline: true), - ), - PosColumn( - text: 'col3', - width: 3, - styles: const PosStyles(align: PosAlign.center, underline: true), - ), - ]); + if (isNull) { + continue; + } + if (isString) { + String text = row.text as String; + text = text.replaceAll('«', '«'); + text = text.replaceAll('»', '»'); + final bool isLine = '{line}' == text; + final bool isBr = '{br}' == text; + if (isBr) { + bytes += generator.feed(1); + } else if (isLine) { + bytes += generator.hr(); + } else { + final Uint8List firstCol = + await CharsetConverter.encode(encoding.toLowerCase(), text); + bytes += generator.textEncoded(firstCol, + styles: PosStyles(align: center ? PosAlign.center : PosAlign.left)); + } + } - bytes += generator.text( - 'Text size 200%', - styles: const PosStyles( - height: PosTextSize.size2, - width: PosTextSize.size2, - ), - ); + if (isList) { + final List list = row.text as List; + if (list.length == 2) { + final Uint8List firstCol = await CharsetConverter.encode( + encoding.toLowerCase(), list[0] as String); + final Uint8List secondCol = await CharsetConverter.encode( + encoding.toLowerCase(), list[1] as String); + bytes += generator.row([ + PosColumn( + textEncoded: firstCol, + width: 6, + styles: const PosStyles(align: PosAlign.left, underline: true), + ), + PosColumn( + textEncoded: secondCol, + width: 6, + styles: const PosStyles(align: PosAlign.right, underline: true), + ), + ]); + } + } - // Print barcode - final List barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4]; - bytes += generator.barcode(Barcode.upcA(barData)); + print('${row.text.runtimeType}, ${row.text}'); + } + if (checkBean.qr != null) { + bytes += generator.feed(2); + const double qrSize = 200; + try { + final ByteData? uiImg = await QrPainter( + data: checkBean.qr!, + version: QrVersions.auto, + gapless: true, + ).toImageData(qrSize); + final img = Im.decodePng(uiImg!.buffer.asUint8List()); + bytes += generator.image(img!); + } catch (e) { + print(e); + } + + } bytes += generator.feed(2); bytes += generator.cut(); return bytes; } -Future> getReceiptImg(String paperSize) async { +Future> getTestReceiptImg(String paperSize) async { + final ByteData data = + await rootBundle.load('assets/images/aman_kassa_check.png'); + final Uint8List imgBytes = data.buffer.asUint8List(); + return _getReceiptImg(paperSize, imgBytes); +} + +Future> getReceiptImg(String paperSize, Uint8List imgBytes) async { + return _getReceiptImg(paperSize, imgBytes); +} + +Future> _getReceiptImg(String paperSize, Uint8List imgBytes) async { final profile = await CapabilityProfile.load(); final generator = Generator( paperSize == PrinterConst.paperSize58mm ? PaperSize.mm58 : PaperSize.mm80, profile, ); List bytes = []; - final ByteData data = - await rootBundle.load('assets/images/aman_kassa_check.png'); - final Uint8List imgBytes = data.buffer.asUint8List(); - final Image? image = decodeImage(imgBytes); + final Im.Image? image = Im.decodeImage(imgBytes); // Using `ESC *` bytes += generator.image(image!); bytes += generator.feed(2); @@ -87,6 +156,22 @@ Future> getReceiptImg(String paperSize) async { return bytes; } +int getChunkSize() { + final bool isIos = Platform.isIOS; + if (isIos) { + return 75; + } + return 1024; +} + +int getQueueSleep() { + final bool isIos = Platform.isIOS; + if (isIos) { + return 10; + } + return 100; +} + BluetoothDevice printerDeviceToBluetoothDevice(PrinterDevice device) { return BluetoothDevice() ..name = device.name diff --git a/lib/views/settings/printer_bluetooth/printer_view.dart b/lib/views/settings/printer_bluetooth/printer_view.dart index 54581c0..a66046e 100644 --- a/lib/views/settings/printer_bluetooth/printer_view.dart +++ b/lib/views/settings/printer_bluetooth/printer_view.dart @@ -34,7 +34,7 @@ class _PrinterViewState extends State { super.initState(); } - void printTest() async { + void print() async { setState(() { printerLocked = true; }); @@ -45,39 +45,28 @@ class _PrinterViewState extends State { printerDeviceToBluetoothDevice(printerSetting.device!), ), ); + final int chunkSizeBytes = getChunkSize(); + final int queueSleepTimeMs = getQueueSleep(); - bool isIos = Platform.isIOS; - int chunkSizeBytes = 3096; - int queueSleepTimeMs = 100; - - if (isIos) { - chunkSizeBytes = 75; - queueSleepTimeMs = 10; + List data = List.empty(); + if (PrinterConst.encodingBigEncoding == printerSetting.encoding) { + data = await getTestReceiptImg( + printerSetting.paperSize!, + ); + } else { + data = await getTestReceipt( + printerSetting.encoding!, + printerSetting.paperSize!, + ); } - if(PrinterConst.encodingBigEncoding == printerSetting.encoding ) { - PosPrintResult printResult = await printerManager.writeBytes( - await getReceiptImg( - printerSetting.paperSize!, - ), - chunkSizeBytes: chunkSizeBytes, - queueSleepTimeMs: queueSleepTimeMs, - ); - if(printResult.value != 1) { - _dialogService.showDialog(description: printResult.msg); - } - } else { - PosPrintResult printResult = await printerManager.writeBytes( - await getReceipt( - printerSetting.encoding!, - printerSetting.paperSize!, - ), - chunkSizeBytes: chunkSizeBytes, - queueSleepTimeMs: queueSleepTimeMs, - ); - if(printResult.value != 1) { - _dialogService.showDialog(description: printResult.msg); - } + final PosPrintResult printResult = await printerManager.writeBytes( + data, + chunkSizeBytes: chunkSizeBytes, + queueSleepTimeMs: queueSleepTimeMs, + ); + if (printResult.value != 1) { + _dialogService.showDialog(description: printResult.msg); } } finally { await Future.delayed(const Duration(seconds: 7)); @@ -99,7 +88,7 @@ class _PrinterViewState extends State { final bool success = snapshot.device != null && printerLocked == false; return IconButton( - onPressed: success ? printTest : null, + onPressed: success ? print : null, icon: Icon( Icons.print, color: success ? textColor : placeholderColor, diff --git a/lib/views/work/views/payment/payment_view.dart b/lib/views/work/views/payment/payment_view.dart index 6fa169d..7ba5489 100644 --- a/lib/views/work/views/payment/payment_view.dart +++ b/lib/views/work/views/payment/payment_view.dart @@ -1,11 +1,13 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; +import 'package:satu/core/models/entity_data/transaction_data.dart'; import 'package:satu/core/redux/state/sell_state.dart'; import 'package:satu/core/redux/store.dart'; import 'package:satu/core/services/navigator_service.dart'; import 'package:satu/core/utils/locator.dart'; import 'package:satu/core/utils/utils_parse.dart'; +import 'package:satu/routes/route_names.dart'; import 'package:satu/shared/app_colors.dart'; import 'package:satu/core/services/data_service.dart'; import 'package:satu/views/work/views/payment/component/combine_dock.dart'; @@ -135,7 +137,10 @@ class _PaymentViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 45, vertical: 30), child: BusyButton( - title: 'ОПЛАТА', busy: loading, onPressed: _payment), + title: 'ОПЛАТА', + busy: loading, + onPressed: _payment, + ), ), ], ), @@ -161,10 +166,11 @@ class _PaymentViewState extends State { nal = _cashSum; } - final bool result = + final TransactionData? transactionData = await _dataService.sellBtnHandler(card: card, nal: nal, total: _sum); - if(result) { + if (transactionData !=null) { _navigatorService.pop(); + _navigatorService.push(receiptViewRoute, arguments: transactionData); } setState(() { loading = false; diff --git a/lib/views/work/views/receipt/receipt_view.dart b/lib/views/work/views/receipt/receipt_view.dart index 1adbeb3..c454c31 100644 --- a/lib/views/work/views/receipt/receipt_view.dart +++ b/lib/views/work/views/receipt/receipt_view.dart @@ -1,12 +1,20 @@ import 'dart:convert'; +import 'dart:io'; +import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; import 'package:satu/core/models/entity_data/transaction_data.dart'; import 'package:satu/core/models/flow/sell_response.dart'; +import 'package:satu/core/models/settings/printer_setting.dart'; +import 'package:satu/core/redux/store.dart'; +import 'package:satu/core/services/dialog_service.dart'; +import 'package:satu/core/utils/locator.dart'; +import 'package:satu/core/utils/pos_printer.dart'; import 'package:satu/shared/app_colors.dart'; import 'package:satu/widgets/bar/products_app_bar.dart'; -class ReceiptView extends StatelessWidget { +class ReceiptView extends StatefulWidget { const ReceiptView({ required this.transactionData, Key? key, @@ -14,23 +22,106 @@ class ReceiptView extends StatelessWidget { final TransactionData transactionData; + @override + State createState() => _ReceiptViewState(); +} + +class _ReceiptViewState extends State { + final DialogService _dialogService = locator(); + PrinterBluetoothManager printerManager = PrinterBluetoothManager(); + bool printerLocked = false; + + void print() async { + setState(() { + printerLocked = true; + }); + try { + PrinterSetting printerSetting = Redux.store!.state.settingState!.printer!; + printerManager.selectPrinter( + PrinterBluetooth( + printerDeviceToBluetoothDevice(printerSetting.device!), + ), + ); + final int chunkSizeBytes = getChunkSize(); + final int queueSleepTimeMs = getQueueSleep(); + + List data = List.empty(); + if (PrinterConst.encodingBigEncoding == printerSetting.encoding) { + data = await getReceiptImg( + printerSetting.paperSize!, + base64Decode(widget.transactionData.sellResponse!.checkPng!), + ); + } else { + data = await getReceipt( + printerSetting.encoding!, + printerSetting.paperSize!, + widget.transactionData.sellResponse!.check! + ); + } + + final PosPrintResult printResult = await printerManager.writeBytes( + data, + chunkSizeBytes: chunkSizeBytes, + queueSleepTimeMs: queueSleepTimeMs, + ); + if (printResult.value != 1) { + _dialogService.showDialog(description: printResult.msg); + } + } finally { + await Future.delayed(const Duration(seconds: 7)); + setState(() { + printerLocked = false; + }); + } + } + @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: whiteColor, - appBar: const ProductsAppBar( - title: 'Просмотр чека', - ), - body: Column( - children: [ - Expanded( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Center(child: imageFromBase64String(transactionData.sellResponse)), + backgroundColor: whiteColor, + appBar: ProductsAppBar( + title: 'Просмотр чека', + actions: [ + StoreConnector( + converter: (store) => store.state.settingState!.printer!, + builder: (context, snapshot) { + final bool success = + snapshot.device != null && printerLocked == false; + return IconButton( + onPressed: success ? print : null, + icon: Icon( + Icons.print, + color: success ? textColor : placeholderColor, + ), + ); + }, + ) + ], + ), + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Center( + child: + imageFromBase64String(widget.transactionData.sellResponse), ), - ) - ], - )); + ), + ) + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Add your onPressed code here! + }, + backgroundColor: successColor, + child: const Icon( + Icons.share_rounded, + size: 20, + ), + ), + ); } } diff --git a/lib/views/work/views/share/share_view.dart b/lib/views/work/views/share/share_view.dart new file mode 100644 index 0000000..04ead82 --- /dev/null +++ b/lib/views/work/views/share/share_view.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:satu/widgets/bar/products_app_bar.dart'; + +class ShareView extends StatelessWidget { + const ShareView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Scaffold( + appBar: ProductsAppBar( + title: 'Поделится', + ), + ); + } +}