diff --git a/android/app/build.gradle b/android/app/build.gradle index 196e0fe..c91f042 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,7 +34,7 @@ if (keystorePropertiesFile.exists()) { android { - compileSdkVersion 29 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -46,8 +46,8 @@ android { defaultConfig { applicationId "kz.com.aman.kassa" - minSdkVersion 18 - targetSdkVersion 29 + minSdkVersion 21 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/assets/images/check.png b/assets/images/check.png new file mode 100644 index 0000000..49b46d4 Binary files /dev/null and b/assets/images/check.png differ diff --git a/lib/core/models/setting_model.dart b/lib/core/models/setting_model.dart new file mode 100644 index 0000000..6c4dd1e --- /dev/null +++ b/lib/core/models/setting_model.dart @@ -0,0 +1,6 @@ +class SettingModel { + const SettingModel({this.name, this.type, this.address}); + final String type; + final String name; + final String address; +} \ No newline at end of file diff --git a/lib/core/route_names.dart b/lib/core/route_names.dart index f880aa9..cf471d2 100644 --- a/lib/core/route_names.dart +++ b/lib/core/route_names.dart @@ -5,4 +5,8 @@ const String PaymentViewRoute = "PaymentView"; const String HistoryViewRoute = "HistoryView"; const String InfoKkmViewRoute = "InfoKkmViewRoute"; const String QrViewRoute = "QrViewRoute"; + + +const String SettingsPrinterRoute = "SettingsPrinterRoute"; +const String SettingsPrinterBTRoute = "SettingsPrinterBTRoute"; // Generate the views here diff --git a/lib/core/router.dart b/lib/core/router.dart index 574f5de..b646483 100644 --- a/lib/core/router.dart +++ b/lib/core/router.dart @@ -3,6 +3,8 @@ import 'package:aman_kassa_flutter/views/history/history_view.dart'; import 'package:aman_kassa_flutter/views/info_kkm/info_kkm_view.dart'; import 'package:aman_kassa_flutter/views/payment/payment_view.dart'; import 'package:aman_kassa_flutter/views/qr_view/qr_view.dart'; +import 'package:aman_kassa_flutter/views/settings/printer/PrinterSelect.dart'; +import 'package:aman_kassa_flutter/views/settings/setting_printer_view.dart'; import './route_names.dart'; import 'package:aman_kassa_flutter/views/home/home_view.dart'; @@ -51,6 +53,16 @@ Route generateRoute(RouteSettings settings) { routeName: settings.name, viewToShow: ImageShowContainer(data), ); + case SettingsPrinterRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: SettingPrinterView(), + ); + case SettingsPrinterBTRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: PrinterSelectView(), + ); default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/main.dart b/lib/main.dart index 385b2c3..1f0d5e6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,9 +44,9 @@ class MainApplication extends StatelessWidget { primaryColor: primaryColor, accentColor: yellowColor, scaffoldBackgroundColor: Colors.white, - textTheme: GoogleFonts.latoTextTheme( - Theme.of(context).textTheme, - ) + // textTheme: GoogleFonts.latoTextTheme( + // Theme.of(context).textTheme, + // ) ), debugShowCheckedModeBanner: false, builder: (context, child) => Navigator( diff --git a/lib/redux/actions/setting_actions.dart b/lib/redux/actions/setting_actions.dart index da4953b..3937b17 100644 --- a/lib/redux/actions/setting_actions.dart +++ b/lib/redux/actions/setting_actions.dart @@ -3,7 +3,7 @@ import 'package:aman_kassa_flutter/redux/state/setting_state.dart'; import 'package:meta/meta.dart'; import 'package:redux/redux.dart'; import 'package:redux_thunk/redux_thunk.dart'; - +import 'package:flutter_bluetooth_basic/src/bluetooth_device.dart'; import '../store.dart'; @immutable @@ -23,4 +23,10 @@ ThunkAction changeTradeTypeFromSetting(String tradeType) { return (Store store) async { store.dispatch(SetSettingStateAction(SettingState(tradeType: tradeType ))); }; +} + +ThunkAction selectPrinterFromSetting(BluetoothDevice device) { + return (Store store) async { + store.dispatch(SetSettingStateAction(SettingState(printerBT: device ))); + }; } \ No newline at end of file diff --git a/lib/redux/constants/setting_const.dart b/lib/redux/constants/setting_const.dart index 787527a..6bae39b 100644 --- a/lib/redux/constants/setting_const.dart +++ b/lib/redux/constants/setting_const.dart @@ -3,4 +3,5 @@ const String SettingModeCalc = 'calcMode'; const String SettingTradeTypeGood = 'g'; -const String SettingTradeTypeService = 's'; \ No newline at end of file +const String SettingTradeTypeService = 's'; + diff --git a/lib/redux/reducers/setting_reducer.dart b/lib/redux/reducers/setting_reducer.dart index 5e76b10..9153491 100644 --- a/lib/redux/reducers/setting_reducer.dart +++ b/lib/redux/reducers/setting_reducer.dart @@ -5,6 +5,7 @@ settingReducer(SettingState prevState, SetSettingStateAction action) { final payload = action.settingState; return prevState.copyWith( mode: payload.mode, - tradeType: payload.tradeType + tradeType: payload.tradeType, + printerBT: payload.printerBT ); } diff --git a/lib/redux/state/setting_state.dart b/lib/redux/state/setting_state.dart index dd5bb1a..76f463b 100644 --- a/lib/redux/state/setting_state.dart +++ b/lib/redux/state/setting_state.dart @@ -1,28 +1,35 @@ import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; import 'package:meta/meta.dart'; +import 'package:flutter_bluetooth_basic/src/bluetooth_device.dart'; + @immutable class SettingState { final String mode; final String tradeType; + final BluetoothDevice printerBT; - SettingState({this.mode, this.tradeType}); + SettingState({this.mode, this.tradeType, this.printerBT}); //read hive factory SettingState.initial(SettingState payload) { return SettingState( mode: payload?.mode ?? SettingModeKassa, - tradeType: payload?.tradeType ?? SettingTradeTypeGood); + tradeType: payload?.tradeType ?? SettingTradeTypeGood, + printerBT: payload?.printerBT ?? null + ); } //write hive SettingState copyWith({ @required mode, @required tradeType, + @required printerBT, }) { return SettingState( mode: mode ?? this.mode, tradeType: tradeType ?? this.tradeType, + printerBT: printerBT ?? this.printerBT ); } @@ -31,11 +38,16 @@ class SettingState { ? SettingState( tradeType: json['tradeType'], mode: json['mode'], + printerBT: json['printerBT']!=null ? BluetoothDevice.fromJson(json['printerBT']) : null ) : null; } dynamic toJson() { - return {"tradeType": tradeType, "mode": mode}; + return { + "tradeType": tradeType, + "mode": mode, + "printerBT": printerBT !=null ? printerBT.toJson() : null + }; } } diff --git a/lib/views/check/image_show_container.dart b/lib/views/check/image_show_container.dart index 93be746..b47ecd5 100644 --- a/lib/views/check/image_show_container.dart +++ b/lib/views/check/image_show_container.dart @@ -5,10 +5,15 @@ import 'package:aman_kassa_flutter/core/models/dialog_models.dart'; import 'package:aman_kassa_flutter/core/route_names.dart'; import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/shared/ui_helpers.dart'; +import 'package:aman_kassa_flutter/views/settings/printer/PrinterTest.dart'; import 'package:aman_kassa_flutter/widgets/fields/busy_button_icon.dart'; +import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart'; +import 'package:esc_pos_utils/esc_pos_utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bluetooth_basic/flutter_bluetooth_basic.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -59,9 +64,18 @@ class _MyFloatingActionButtonState extends State { bool showFab = true; DialogService _dialog = locator(); NavigatorService _navigatorService = locator(); + PrinterBluetoothManager printerManager = PrinterBluetoothManager(); + final DialogService _dialogService = locator(); + BluetoothDevice printerBtDevice = Redux.store.state.settingState.printerBT; + + double sheetHeight = 260; @override Widget build(BuildContext context) { + if ( printerBtDevice != null){ + sheetHeight = 340; + } + return showFab ? FloatingActionButton( child: Icon(Icons.share), @@ -79,7 +93,7 @@ class _MyFloatingActionButtonState extends State { color: Colors.grey[300], spreadRadius: 5) ]), - height: 260, + height: sheetHeight, child: Column( children: [ verticalSpaceSmall, @@ -102,9 +116,17 @@ class _MyFloatingActionButtonState extends State { BusyButtonIcon( title: 'Поделиться', onPressed: shareFile, - mainColor: yellowColor, + mainColor: redColor, icon: Icons.share, ), + verticalSpaceSmall, + BusyButtonIcon( + title: 'Печать', + onPressed: printFile, + mainColor: primaryColor, + icon: Icons.print, + ), + ], ))); showFoatingActionButton(false); @@ -125,6 +147,23 @@ class _MyFloatingActionButtonState extends State { } } + void printFile() async { + Navigator.of(context).pop(false); + + printerManager.selectPrinter(PrinterBluetooth(Redux.store.state.settingState.printerBT)); + // TODO Don't forget to choose printer's paper + const PaperSize paper = PaperSize.mm58; + final PosPrintResult res = + await printerManager.printTicket( + await printImageCheck(paper,widget.data.data ), + chunkSizeBytes: 3096, + queueSleepTimeMs: 50 + ); + if(res.value != 1) { + _dialogService.showDialog(description: res.msg); + } + } + void qrGenerate() async { _navigatorService.push(QrViewRoute, arguments: diff --git a/lib/views/home/components/popup_menu.dart b/lib/views/home/components/popup_menu.dart index e588169..fad97bf 100644 --- a/lib/views/home/components/popup_menu.dart +++ b/lib/views/home/components/popup_menu.dart @@ -7,7 +7,7 @@ const List choices = const [ //const Choice(title: 'Помощь', icon: Icons.help, command: 'help'), const Choice( title: 'Информация о ККМ', icon: Icons.info_outline, command: 'infokkm'), - //const Choice(title: 'Язык', icon: Icons.language, command: 'language'), + const Choice(title: 'Принтер', icon: Icons.print, command: 'print'), const Choice(title: 'Выйти', icon: Icons.exit_to_app, command: 'exit') ]; diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart index 72aab32..f99dc09 100644 --- a/lib/views/home/home_view.dart +++ b/lib/views/home/home_view.dart @@ -64,6 +64,8 @@ class _HomeViewState extends State { Navigator.of(_keyLoader.currentContext, rootNavigator: true).pop(); } else if (choice.command == 'infokkm') { _navigatorService.push(InfoKkmViewRoute); + } else if (choice.command == 'print') { + _navigatorService.push(SettingsPrinterRoute); } } diff --git a/lib/views/settings/component/setting_item.dart b/lib/views/settings/component/setting_item.dart new file mode 100644 index 0000000..7f81896 --- /dev/null +++ b/lib/views/settings/component/setting_item.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class SettingItem extends StatefulWidget { + + final String name; + final String value; + final String title; + final Function onTap; + + SettingItem({Key key, this.name, this.value, this.onTap, this.title }) : super(key: key); + + @override + _SettingItemState createState() => _SettingItemState(); +} + +class _SettingItemState extends State { + @override + Widget build(BuildContext context) { + return Card( + child: ListTile( + title: Text(widget.title), + subtitle: Text.rich( + TextSpan( + text: widget.name, + style: TextStyle(fontWeight: FontWeight.w500), + children: [ + if(widget.value !=null) + TextSpan(text: ' ${widget.value}', style: TextStyle(fontStyle: FontStyle.italic)), + ], + ) + ), + trailing: Icon(Icons.chevron_right), + onTap: widget.onTap, + ), + ); + } +} \ No newline at end of file diff --git a/lib/views/settings/printer/PrinterSelect.dart b/lib/views/settings/printer/PrinterSelect.dart new file mode 100644 index 0000000..d790aa2 --- /dev/null +++ b/lib/views/settings/printer/PrinterSelect.dart @@ -0,0 +1,324 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:aman_kassa_flutter/core/logger.dart'; +import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; + +import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart'; +import 'package:esc_pos_utils/esc_pos_utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; + + +import 'package:flutter_bluetooth_basic/flutter_bluetooth_basic.dart'; +import 'package:intl/intl.dart'; +import 'package:logger/logger.dart'; + +import 'PrinterTest.dart'; + + +class PrinterSelectView extends StatefulWidget { + PrinterSelectView({Key key, this.title}) : super(key: key); + final String title; + + @override + _PrinterSelectViewState createState() => _PrinterSelectViewState(); + +} + +class _PrinterSelectViewState extends State { + PrinterBluetoothManager printerManager = PrinterBluetoothManager(); + List _devices = []; + Logger _logger = getLogger('PrinterSelectView'); + + @override + void initState() { + super.initState(); + + printerManager.scanResults.listen((devices) async { + // print('UI: Devices found ${devices.length}'); + setState(() { + _devices = devices; + }); + }); + _startScanDevices(); + } + + void _startScanDevices() { + setState(() { + _devices = []; + }); + printerManager.startScan(Duration(seconds: 4)); + } + + void _stopScanDevices() { + printerManager.stopScan(); + } + + Future demoReceipt(PaperSize paper) async { + final Ticket ticket = Ticket(paper, ); + + // Print image + // final ByteData data = await rootBundle.load('assets/images/aman_kassa_check.png'); + // final Uint8List bytes = data.buffer.asUint8List(); + // final Im.Image image = Im.decodeImage(bytes); + // Im.Image thumbnail = Im.copyResize(image, width: 270); + // ticket.image(thumbnail, align: PosAlign.center); + + //ticket.imageRaster(image, align: PosAlign.center); + + ticket.text('AMAN-SATU', + styles: PosStyles( + align: PosAlign.center, + height: PosTextSize.size2, + width: PosTextSize.size2, + ), + linesAfter: 1); + + ticket.text('889 Watson Lane', styles: PosStyles(align: PosAlign.center)); + ticket.text('Русский язык', styles: PosStyles(align: PosAlign.center, codeTable: PosCodeTable.westEur), containsChinese: true); + ticket.text('Русский язык', styles: PosStyles(align: PosAlign.center, fontType: PosFontType.fontA), containsChinese: true); + ticket.text('Русский язык', styles: PosStyles(align: PosAlign.center, fontType: PosFontType.fontB), containsChinese: true); + ticket.text('Русский язык', styles: PosStyles(align: PosAlign.center, height: PosTextSize.size1), containsChinese: true); + ticket.text('Русский язык', styles: PosStyles(align: PosAlign.center, width: PosTextSize.size2), containsChinese: true); + ticket.text('Русский язык', styles: PosStyles(align: PosAlign.center, width: PosTextSize.size3), containsChinese: true); + ticket.text('Русский язык', styles: PosStyles(align: PosAlign.center, width: PosTextSize.size4), containsChinese: true); + ticket.text('Tel: 830-221-1234', styles: PosStyles(align: PosAlign.center)); + ticket.text('Web: www.example.com', + styles: PosStyles(align: PosAlign.center), linesAfter: 1); + + ticket.hr(); + ticket.row([ + PosColumn(text: 'Qty', width: 1), + PosColumn(text: 'Item', width: 7), + PosColumn( + text: 'Price', width: 2, styles: PosStyles(align: PosAlign.right)), + PosColumn( + text: 'Total', width: 2, styles: PosStyles(align: PosAlign.right)), + ]); + + ticket.row([ + PosColumn(text: '2', width: 1), + PosColumn(text: 'ONION RINGS', width: 7), + PosColumn( + text: '0.99', width: 2, styles: PosStyles(align: PosAlign.right)), + PosColumn( + text: '1.98', width: 2, styles: PosStyles(align: PosAlign.right)), + ]); + ticket.row([ + PosColumn(text: '1', width: 1), + PosColumn(text: 'PIZZA', width: 7), + PosColumn( + text: '3.45', width: 2, styles: PosStyles(align: PosAlign.right)), + PosColumn( + text: '3.45', width: 2, styles: PosStyles(align: PosAlign.right)), + ]); + ticket.row([ + PosColumn(text: '1', width: 1), + PosColumn(text: 'SPRING ROLLS', width: 7), + PosColumn( + text: '2.99', width: 2, styles: PosStyles(align: PosAlign.right)), + PosColumn( + text: '2.99', width: 2, styles: PosStyles(align: PosAlign.right)), + ]); + ticket.row([ + PosColumn(text: '3', width: 1), + PosColumn(text: 'CRUNCHY STICKS', width: 7), + PosColumn( + text: '0.85', width: 2, styles: PosStyles(align: PosAlign.right)), + PosColumn( + text: '2.55', width: 2, styles: PosStyles(align: PosAlign.right)), + ]); + ticket.hr(); + + ticket.row([ + PosColumn( + text: 'TOTAL', + width: 6, + styles: PosStyles( + height: PosTextSize.size2, + width: PosTextSize.size2, + )), + PosColumn( + text: '\$10.97', + width: 6, + styles: PosStyles( + align: PosAlign.right, + height: PosTextSize.size2, + width: PosTextSize.size2, + )), + ]); + + ticket.hr(ch: '=', linesAfter: 1); + + ticket.row([ + PosColumn( + text: 'Cash', + width: 7, + styles: PosStyles(align: PosAlign.right, width: PosTextSize.size2)), + PosColumn( + text: '\$15.00', + width: 5, + styles: PosStyles(align: PosAlign.right, width: PosTextSize.size2)), + ]); + ticket.row([ + PosColumn( + text: 'Change', + width: 7, + styles: PosStyles(align: PosAlign.right, width: PosTextSize.size2)), + PosColumn( + text: '\$4.03', + width: 5, + styles: PosStyles(align: PosAlign.right, width: PosTextSize.size2)), + ]); + + ticket.feed(2); + ticket.text('Thank you!', + styles: PosStyles(align: PosAlign.center, bold: true)); + + final now = DateTime.now(); + final formatter = DateFormat('MM/dd/yyyy H:m'); + final String timestamp = formatter.format(now); + ticket.text(timestamp, + styles: PosStyles(align: PosAlign.center), linesAfter: 2); + + // Print QR Code from image + // try { + // const String qrData = 'example.com'; + // const double qrSize = 200; + // final uiImg = await QrPainter( + // data: qrData, + // version: QrVersions.auto, + // gapless: false, + // ).toImageData(qrSize); + // final dir = await getTemporaryDirectory(); + // final pathName = '${dir.path}/qr_tmp.png'; + // final qrFile = File(pathName); + // final imgFile = await qrFile.writeAsBytes(uiImg.buffer.asUint8List()); + // final img = decodeImage(imgFile.readAsBytesSync()); + + // ticket.image(img); + // } catch (e) { + // print(e); + // } + + // Print QR Code using native function + // ticket.qrcode('example.com'); + + ticket.feed(2); + ticket.cut(); + return ticket; + } + + + + void _selectPrinter(PrinterBluetooth printer, BuildContext context, ) async { + printerManager.selectPrinter(printer); + _logger.i(printer.name); + _logger.i(printer.address); + + BluetoothDevice device = new BluetoothDevice() + ..address = printer.address + ..name=printer.name + ..type=printer.type; + + await Redux.store.dispatch(selectPrinterFromSetting(device)); + Navigator.of(context).pop(false); + } + + void _testPrint(PrinterBluetooth printer) async { + printerManager.selectPrinter(printer); + + // TODO Don't forget to choose printer's paper + const PaperSize paper = PaperSize.mm58; + + // TEST PRINT + // final PosPrintResult res = + // await printerManager.printTicket(await testTicket(paper), queueSleepTimeMs: 50); + + final PosPrintResult res = + await printerManager.printTicket( + await testTicketImage(paper), + chunkSizeBytes: 1024, + queueSleepTimeMs: 50 + ); + + // DEMO RECEIPT + // final PosPrintResult res = + // await printerManager.printTicket(await demoReceipt(paper) , queueSleepTimeMs: 50); + + } + + final key = GlobalKey(); + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + key: key, + child: Scaffold( + appBar: AppBar( + title: Text('Выберите принтер'), + ), + body: ListView.builder( + itemCount: _devices.length, + itemBuilder: (BuildContext _, int index) { + return InkWell( + onTap: () => _selectPrinter(_devices[index], context), + child: Column( + children: [ + Container( + height: 60, + padding: EdgeInsets.only(left: 10), + alignment: Alignment.centerLeft, + child: Row( + children: [ + Icon(Icons.print), + SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(_devices[index].name ?? ''), + Text(_devices[index].address), + Text( + 'Click to print a test receipt', + style: TextStyle(color: Colors.grey[700]), + ), + ], + ), + ) + ], + ), + ), + Divider(), + ], + ), + ); + }), + floatingActionButton: StreamBuilder( + stream: printerManager.isScanningStream, + initialData: false, + builder: (c, snapshot) { + if (snapshot.data) { + return FloatingActionButton( + child: Icon(Icons.stop), + onPressed: _stopScanDevices, + backgroundColor: Colors.red, + ); + } else { + return FloatingActionButton( + child: Icon(Icons.search), + onPressed: _startScanDevices, + ); + } + }, + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/views/settings/printer/PrinterTest.dart b/lib/views/settings/printer/PrinterTest.dart new file mode 100644 index 0000000..f42e0f8 --- /dev/null +++ b/lib/views/settings/printer/PrinterTest.dart @@ -0,0 +1,139 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:esc_pos_utils/esc_pos_utils.dart'; +import 'package:flutter/services.dart'; +import 'package:image/image.dart' as Im; + +Future testTicket(PaperSize paper) async { + final Ticket ticket = Ticket(paper); + + //Uint8List encTxt11 = await CharsetConverter.encode("cp866", "Russian: Привет Мир!"); + //ticket.textEncoded(encTxt11, styles: PosStyles(codeTable: PosCodeTable.pc866_2)); + //ticket.textEncoded(encTxt11); + + // ticket.text('Special 1: àÀ', styles: PosStyles(codeTable: PosCodeTable.westEur)); //А + // ticket.text('Special 1: á'.toUpperCase(), styles: PosStyles(codeTable: PosCodeTable.westEur));// Б + // ticket.text('Special 1: â', styles: PosStyles(codeTable: PosCodeTable.westEur)); //В + // ticket.text('Special 1: ã', styles: PosStyles(codeTable: PosCodeTable.westEur));// Г + // ticket.text('Special 1: äÄ', styles: PosStyles(codeTable: PosCodeTable.westEur)); //Д + // ticket.text('Special 1: å', styles: PosStyles(codeTable: PosCodeTable.westEur));// Е + // ticket.text('Special 1: æÆ', styles: PosStyles(codeTable: PosCodeTable.westEur));// Ж + // ticket.text('Special 1: ç', styles: PosStyles(codeTable: PosCodeTable.westEur));//З + // ticket.text('Special 1: èÈ', styles: PosStyles(codeTable: PosCodeTable.westEur)); // И + // ticket.text('Special 1: éÉ', styles: PosStyles(codeTable: PosCodeTable.westEur)); // Й + // ticket.text('Special 1: ê', styles: PosStyles(codeTable: PosCodeTable.westEur));//К + // ticket.text('Special 1: ëË', styles: PosStyles(codeTable: PosCodeTable.westEur)); // Л + // ticket.text('Special 1: ìÌ', styles: PosStyles(codeTable: PosCodeTable.westEur));// M + // ticket.text('Special 1: íÍ', styles: PosStyles(codeTable: PosCodeTable.westEur)); // Н + // ticket.text('Special 1: î', styles: PosStyles(codeTable: PosCodeTable.westEur));// О + // ticket.text('Special 1: ï', styles: PosStyles(codeTable: PosCodeTable.westEur)); // П + // ticket.text('Special 1: ð', styles: PosStyles(codeTable: PosCodeTable.westEur));// Р + // ticket.text('Special 1: ñ', styles: PosStyles(codeTable: PosCodeTable.westEur));// С + // ticket.text('Special 1: ò', styles: PosStyles(codeTable: PosCodeTable.westEur)); // Т + // ticket.text('Special 1: óÓ', styles: PosStyles(codeTable: PosCodeTable.westEur)); //У + // ticket.text('Special 1: ô', styles: PosStyles(codeTable: PosCodeTable.westEur));// Ф + // ticket.text('Special 1: õÕ', styles: PosStyles(codeTable: PosCodeTable.westEur));// Х + // ticket.text('Special 1: ö', styles: PosStyles(codeTable: PosCodeTable.westEur)); //Ц + // ticket.text('Special 1: ÷', styles: PosStyles(codeTable: PosCodeTable.westEur)); //Ч + // ticket.text('Special 1: ø', styles: PosStyles(codeTable: PosCodeTable.westEur));//Ш + // ticket.text('Special 1: ù', styles: PosStyles(codeTable: PosCodeTable.westEur)); //Щ + // ticket.text('Special 1: ú', styles: PosStyles(codeTable: PosCodeTable.westEur));//Ъ + // ticket.text('Special 1: û', styles: PosStyles(codeTable: PosCodeTable.westEur));//Ы + // ticket.text('Special 1: üÜ', styles: PosStyles(codeTable: PosCodeTable.westEur)); //Ь + // ticket.text('Special 1: ý', styles: PosStyles(codeTable: PosCodeTable.westEur)); //Э + // ticket.text('Special 1: þ', styles: PosStyles(codeTable: PosCodeTable.westEur)); // ю + // ticket.text('Special 1: ÿß', styles: PosStyles(codeTable: PosCodeTable.westEur)); //Я + + // Uint8List encTxt11 = await CharsetConverter.encode("cp866", "Russian: Привет Мир!"); + // //ticket.textEncoded(encTxt11, styles: PosStyles(codeTable: PosCodeTable.pc866_2)); + // ticket.textEncoded(encTxt11); + + ticket.text( + 'Regular: aA bB cC dD eE fF gG hH iI jJ kK lL mM nN oO pP qQ rR sS tT uU vV wW xX yY zZ'); + //ticket.text('Special 1: àÀ èÈ éÉ ûÛ üÜ çÇ ôÔ', styles: PosStyles(codeTable: PosCodeTable.westEur)); + //ticket.text('Special 2: blåbærgrød', styles: PosStyles(codeTable: PosCodeTable.westEur)); + + ticket.text('Bold text', styles: PosStyles(bold: true)); + ticket.text('Reverse text', styles: PosStyles(reverse: true)); + ticket.text('Underlined text', + styles: PosStyles(underline: true), linesAfter: 1); + ticket.text('Align left', styles: PosStyles(align: PosAlign.left)); + ticket.text('Align center', styles: PosStyles(align: PosAlign.center)); + ticket.text('Align right', + styles: PosStyles(align: PosAlign.right), linesAfter: 1); + + ticket.row([ + PosColumn( + text: 'col3', + width: 3, + styles: PosStyles(align: PosAlign.center, underline: true), + ), + PosColumn( + text: 'col6', + width: 6, + styles: PosStyles(align: PosAlign.center, underline: true), + ), + PosColumn( + text: 'col3', + width: 3, + styles: PosStyles(align: PosAlign.center, underline: true), + ), + ]); + + ticket.text('Text size 200%', + styles: PosStyles( + height: PosTextSize.size2, + width: PosTextSize.size2, + )); + + // Print image + //final ByteData data = await rootBundle.load('assets/images/logo.png'); + //final Uint8List bytes = data.buffer.asUint8List(); + // Print image using alternative commands + // ticket.imageRaster(image); + // ticket.imageRaster(image, imageFn: PosImageFn.graphics); + + // Print barcode + final List barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4]; + ticket.barcode(Barcode.upcA(barData)); + + + + ticket.feed(2); + + ticket.cut(); + return ticket; +} + +Future testTicketImage(PaperSize paper) async { + final Ticket ticket = Ticket(paper); + + // Print image + final ByteData byteData = await rootBundle.load('assets/images/check.png'); + final Uint8List bytes = byteData.buffer.asUint8List(); + final Im.Image imagea = Im.decodeImage(bytes); + // Using `ESC *` + //ticket.image(imagea); + // Using `GS v 0` (obsolete) + //ticket.imageRaster(imagea); + // Using `GS ( L` + ticket.imageRaster(imagea, imageFn: PosImageFn.bitImageRaster); + + + + ticket.feed(2); + + ticket.cut(); + return ticket; +} + +Future printImageCheck(PaperSize paper, String base64Src) async { + final Ticket ticket = Ticket(paper); + final Uint8List bytes = base64Decode(base64Src); + final Im.Image image = Im.decodeImage(bytes); + ticket.imageRaster(image, imageFn: PosImageFn.bitImageRaster); + ticket.feed(2); + ticket.cut(); + return ticket; +} \ No newline at end of file diff --git a/lib/views/settings/setting_printer_view.dart b/lib/views/settings/setting_printer_view.dart new file mode 100644 index 0000000..76401ed --- /dev/null +++ b/lib/views/settings/setting_printer_view.dart @@ -0,0 +1,97 @@ +import 'dart:typed_data'; + +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/route_names.dart'; +import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; +import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; +import 'package:aman_kassa_flutter/redux/state/setting_state.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; +import 'package:aman_kassa_flutter/shared/app_colors.dart'; +import 'package:aman_kassa_flutter/widgets/fields/aman_icon_button_horizontal.dart'; +import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart'; +import 'package:esc_pos_utils/esc_pos_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:aman_kassa_flutter/views/settings/printer/PrinterTest.dart'; +import 'package:flutter_redux/flutter_redux.dart'; + +import 'component/setting_item.dart'; + +class SettingPrinterView extends StatefulWidget { + @override + _SettingPrinterViewState createState() => _SettingPrinterViewState(); +} + +class _SettingPrinterViewState extends State { + NavigatorService _navigatorService = locator(); + final DialogService _dialogService = locator(); + PrinterBluetoothManager printerManager = PrinterBluetoothManager(); + + + @override + void initState() { + super.initState(); + } + + + void _testPrint() async { + printerManager.selectPrinter(PrinterBluetooth(Redux.store.state.settingState.printerBT)); + // TODO Don't forget to choose printer's paper + const PaperSize paper = PaperSize.mm58; + final PosPrintResult res = + await printerManager.printTicket( + await testTicketImage(paper), + chunkSizeBytes: 3096, + queueSleepTimeMs: 50 + ); + _dialogService.showDialog(description: res.msg); + + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Настройка принтера'), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: StoreConnector( + converter: (store) => store.state.settingState, + builder: (context, vm) { + return Column( + children: [ + SettingItem( + title: 'Принтер', + name: vm.printerBT?.name, + value: vm.printerBT != null + ? 'BT: ${vm.printerBT.address} ' + : 'не выбран', + onTap: () { + _navigatorService.push(SettingsPrinterBTRoute); + }), + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + AmanIconButtonHorizontal( + icon: Icons.local_printshop_outlined, + title: 'Напечатать тестовую страницу', + activeColor: primaryColor, + selected: vm.printerBT != null, + onPressed: () { + _testPrint(); + }, + ), + ], + ), + ), + ) + ], + ); + }), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 861beca..06ba462 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" async: dependency: transitive description: @@ -43,6 +57,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0-nullsafety.1" + charset_converter: + dependency: "direct main" + description: + name: charset_converter + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" clock: dependency: transitive description: @@ -71,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.5" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.2" cupertino_icons: dependency: "direct main" description: @@ -99,6 +127,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.5" + esc_pos_bluetooth: + dependency: "direct main" + description: + name: esc_pos_bluetooth + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.8" + esc_pos_utils: + dependency: "direct main" + description: + name: esc_pos_utils + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.6" esys_flutter_share: dependency: "direct main" description: @@ -139,6 +181,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bluetooth_basic: + dependency: "direct main" + description: + name: flutter_bluetooth_basic + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" flutter_redux: dependency: "direct main" description: @@ -163,6 +212,13 @@ packages: description: flutter source: sdk version: "0.0.0" + gbk_codec: + dependency: transitive + description: + name: gbk_codec + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.2" get_it: dependency: "direct main" description: @@ -177,6 +233,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + hex: + dependency: transitive + description: + name: hex + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+4" http: dependency: "direct main" description: @@ -191,6 +261,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.19" intl: dependency: "direct main" description: @@ -198,6 +275,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.16.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" logger: dependency: "direct main" description: @@ -289,6 +373,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.2" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" platform: dependency: transitive description: @@ -373,6 +464,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.0" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.23.1" shared_preferences: dependency: transitive description: @@ -553,6 +651,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.1" sdks: dart: ">=2.10.2 <2.11.0" flutter: ">=1.22.2 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index fe85302..a719791 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,10 @@ dependencies: qr_flutter: ^3.2.0 mask_text_input_formatter: ^1.2.1 flutter_screenutil: ^2.3.1 + esc_pos_bluetooth: ^0.2.8 + flutter_bluetooth_basic: ^0.1.5 + esc_pos_utils: ^0.3.6 # no edit for esc_pos_bluetooth: ^0.2.8 + charset_converter: ^1.0.3 dev_dependencies: flutter_test: sdk: flutter @@ -40,8 +44,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/images/logo.png - - assets/images/icon_large.png + - assets/images/ - assets/lang/en.json - assets/lang/ru.json - assets/google_fonts/