diff --git a/android/app/build.gradle b/android/app/build.gradle index 3010ab1..c91f042 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,7 +34,7 @@ if (keystorePropertiesFile.exists()) { android { - compileSdkVersion 28 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -46,8 +46,8 @@ android { defaultConfig { applicationId "kz.com.aman.kassa" - minSdkVersion 18 - targetSdkVersion 28 + 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/logger.dart b/lib/core/logger.dart index fc9ffc6..01ec262 100644 --- a/lib/core/logger.dart +++ b/lib/core/logger.dart @@ -18,7 +18,7 @@ class SimpleLogPrinter extends LogPrinter { if (event.stackTrace == null) { stack = formatStackTrace(StackTrace.current, 2); } else { - stack = formatStackTrace(event.stackTrace, 2); + stack = formatStackTrace(event.stackTrace, 1); } print(color(' $emoji $message $error -> $stack ')); return []; @@ -34,6 +34,7 @@ class SimpleLogPrinter extends LogPrinter { } String formatStackTrace(StackTrace stackTrace, int methodPosition) { + var lines = stackTrace.toString()?.split('\n'); var formatted = []; var count = 0; diff --git a/lib/core/models/check_image_modal.dart b/lib/core/models/check_image_modal.dart new file mode 100644 index 0000000..9ff93be --- /dev/null +++ b/lib/core/models/check_image_modal.dart @@ -0,0 +1,17 @@ +class CheckImageModal { + final String base64Data; + final String textData; + CheckImageModal({this.base64Data, this.textData}); + + static CheckImageModal fromJson(Map json) { + return CheckImageModal( + base64Data: json['base64Data'], + textData: json['textData'] + ); + } + Map toJson() => + { + 'base64Data': base64Data, + 'textData': textData + }; +} \ No newline at end of file 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 21f9469..85453bc 100644 --- a/lib/core/route_names.dart +++ b/lib/core/route_names.dart @@ -6,4 +6,10 @@ const String HistoryViewRoute = "HistoryView"; const String InfoKkmViewRoute = "InfoKkmViewRoute"; const String SettingsViewRoute = "SettingsViewRoute"; const String QrViewRoute = "QrViewRoute"; + + +const String SettingsPrinterRoute = "SettingsPrinterRoute"; +const String SettingsPrinterBTRoute = "SettingsPrinterBTRoute"; +const String SettingsPrinterEncodingRoute = "SettingsPrinterEncodingRoute"; +const String SettingsPrinterPaperRoute = "SettingsPrinterPaperRoute"; // Generate the views here diff --git a/lib/core/router.dart b/lib/core/router.dart index 1af744f..69d3d62 100644 --- a/lib/core/router.dart +++ b/lib/core/router.dart @@ -3,12 +3,16 @@ 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/views/PrinterSelect.dart'; import 'package:aman_kassa_flutter/views/settings/settings_view.dart'; - -import './route_names.dart'; -import 'package:aman_kassa_flutter/views/home/home_view.dart'; +import 'package:aman_kassa_flutter/views/settings/printer/views/PrinterEncoding.dart'; +import 'package:aman_kassa_flutter/views/settings/printer/views/PrinterPaperSize.dart'; +import 'package:aman_kassa_flutter/views/settings/setting_printer_view.dart'; import 'package:aman_kassa_flutter/views/login/login_view.dart'; +import 'package:aman_kassa_flutter/views/home/home_view.dart'; import 'package:flutter/material.dart'; +import './route_names.dart'; + Route generateRoute(RouteSettings settings) { switch (settings.name) { @@ -57,6 +61,26 @@ 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(), + ); + case SettingsPrinterEncodingRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: PrinterEncodingView(), + ); + case SettingsPrinterPaperRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: PrinterPaperView(), + ); default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/core/services/DataService.dart b/lib/core/services/DataService.dart index 5f84729..33b2f41 100644 --- a/lib/core/services/DataService.dart +++ b/lib/core/services/DataService.dart @@ -8,6 +8,7 @@ import 'package:aman_kassa_flutter/core/entity/Voucher.dart'; import 'package:aman_kassa_flutter/core/locator.dart'; import 'package:aman_kassa_flutter/core/models/calc_model.dart'; import 'package:aman_kassa_flutter/core/models/check_data.dart'; +import 'package:aman_kassa_flutter/core/models/check_image_modal.dart'; import 'package:aman_kassa_flutter/core/models/check_item.dart'; import 'package:aman_kassa_flutter/core/models/product_dao.dart'; import 'package:aman_kassa_flutter/core/models/response.dart'; @@ -166,17 +167,26 @@ class DataService extends BaseService { // log.i('response operation: ${response.operation}'); if (response.status == 200 && response.operation == true) { User user = Redux.store.state.userState.user; + //check compare + String check = response?.body['check']; + var checkText = response?.body['check_text']; + CheckImageModal imageModal = new CheckImageModal( base64Data: check, textData: checkText !=null ? jsonEncode(checkText) : null ); + // journal analyze dynamic journal = response?.body['journal']; - String url = response?.body['link']; int checkNum = journal['check_num']; var summ = journal['summ']; + // short url + String url = response?.body['link']; + // total double total = summ != null ? double.parse(summ.toString()) : 0.0; + + //insert data to db this.insertVoucher( user: user, name: 'Чек №$checkNum', data: data, - base64Data: check, + base64Data: jsonEncode(imageModal.toJson()), total: total, url: url, type: operationType == OperationTypeReturn 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 7ba6071..2a003fe 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 @@ -41,4 +41,22 @@ ThunkAction changePinSkipFromSetting(bool skip) { return (Store store) async { store.dispatch(SetSettingStateAction(SettingState(pinSkip: skip))); }; +} + +ThunkAction selectPrinterFromSetting(BluetoothDevice device) { + return (Store store) async { + store.dispatch(SetSettingStateAction(SettingState(printerBT: device ))); + }; +} + +ThunkAction selectPrinterEncodingFromSetting(String encoding) { + return (Store store) async { + store.dispatch(SetSettingStateAction(SettingState(printerEncoding: encoding ))); + }; +} + +ThunkAction selectPrinterPaperSizeFromSetting(String paperSize) { + return (Store store) async { + store.dispatch(SetSettingStateAction(SettingState(printerPaperSize: paperSize ))); + }; } \ No newline at end of file diff --git a/lib/redux/constants/setting_const.dart b/lib/redux/constants/setting_const.dart index 787527a..ac922ea 100644 --- a/lib/redux/constants/setting_const.dart +++ b/lib/redux/constants/setting_const.dart @@ -3,4 +3,17 @@ const String SettingModeCalc = 'calcMode'; const String SettingTradeTypeGood = 'g'; -const String SettingTradeTypeService = 's'; \ No newline at end of file +const String SettingTradeTypeService = 's'; + + +const String SettingPrinterEncodingCp866 = 'cp866'; +const String SettingPrinterEncodingWin1251 = 'windows-1251'; +const String SettingPrinterEncodingImage = 'image'; + +const String SettingPrinterPaperM58 = '58mm'; +const String SettingPrinterPaperM80 = '80mm'; + + + + + diff --git a/lib/redux/reducers/setting_reducer.dart b/lib/redux/reducers/setting_reducer.dart index 961ea2c..b9f81ad 100644 --- a/lib/redux/reducers/setting_reducer.dart +++ b/lib/redux/reducers/setting_reducer.dart @@ -9,5 +9,8 @@ settingReducer(SettingState prevState, SetSettingStateAction action) { pinCode: payload.pinCode, pinLocked: payload.pinLocked, pinSkip: payload.pinSkip, + printerBT: payload.printerBT, + printerEncoding: payload.printerEncoding, + printerPaperSize: payload.printerPaperSize, ); } diff --git a/lib/redux/state/setting_state.dart b/lib/redux/state/setting_state.dart index 48bf38b..91a9edb 100644 --- a/lib/redux/state/setting_state.dart +++ b/lib/redux/state/setting_state.dart @@ -1,5 +1,6 @@ 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 { @@ -8,9 +9,14 @@ class SettingState { final String pinCode; final bool pinLocked; final bool pinSkip; + final BluetoothDevice printerBT; + final String printerEncoding; + final String printerPaperSize; - SettingState({this.mode, this.tradeType, this.pinCode, this.pinLocked, this.pinSkip}); + SettingState({this.mode, this.tradeType, this.pinCode, this.pinLocked, this.pinSkip, this.printerBT, + this.printerEncoding, + this.printerPaperSize}); //read hive factory SettingState.initial(SettingState payload) { @@ -20,6 +26,10 @@ class SettingState { pinCode: payload?.pinCode ?? null, pinLocked: true, pinSkip: false, + printerBT: payload?.printerBT ?? null, + printerEncoding: + payload?.printerEncoding ?? SettingPrinterEncodingCp866, + printerPaperSize: payload?.printerPaperSize ?? SettingPrinterPaperM58 ); } @@ -30,13 +40,19 @@ class SettingState { @required pinCode, @required pinLocked, @required pinSkip, + @required printerBT, + @required printerEncoding, + @required printerPaperSize, }) { return SettingState( mode: mode ?? this.mode, tradeType: tradeType ?? this.tradeType, pinCode: pinCode ?? this.pinCode, pinLocked: pinLocked ?? this.pinLocked, - pinSkip: pinSkip ?? this.pinSkip + pinSkip: pinSkip ?? this.pinSkip, + printerBT: printerBT ?? this.printerBT, + printerEncoding: printerEncoding ?? this.printerEncoding, + printerPaperSize: printerPaperSize ?? this.printerPaperSize ); } @@ -48,6 +64,11 @@ class SettingState { pinCode: json['pinCode'], pinLocked: json['pinLocked'], pinSkip: json['pinSkip'], + printerEncoding: json['printerEncoding'], + printerPaperSize: json['printerPaperSize'], + printerBT: json['printerBT'] != null + ? BluetoothDevice.fromJson(json['printerBT']) + : null, ) : null; } @@ -59,6 +80,9 @@ class SettingState { "pinCode": pinCode, "pinLocked" : pinLocked, "pinSkip" : pinSkip, + "printerBT": printerBT != null ? printerBT.toJson() : null, + "printerEncoding": printerEncoding, + "printerPaperSize": printerPaperSize, }; } } diff --git a/lib/views/check/image_show_container.dart b/lib/views/check/image_show_container.dart index 93be746..71d365d 100644 --- a/lib/views/check/image_show_container.dart +++ b/lib/views/check/image_show_container.dart @@ -1,34 +1,113 @@ import 'dart:convert'; import 'dart:io'; import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/check_image_modal.dart'; 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/constants/setting_const.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/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/cupertino.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'; -class ImageShowContainer extends StatelessWidget { - final ImageShowModel data; +class ImageShowContainer extends StatefulWidget { + final ImageShowModel showModel; - ImageShowContainer(this.data); + ImageShowContainer(this.showModel); + + @override + _ImageShowContainerState createState() => _ImageShowContainerState(); +} + +class _ImageShowContainerState extends State { + final PrinterBluetoothManager printerManager = PrinterBluetoothManager(); + final DialogService _dialogService = locator(); + final BluetoothDevice printerBtDevice = Redux.store.state.settingState.printerBT; + + bool _printing = false; + + void _print() async { + final SettingState state = Redux.store.state.settingState; + if(state.printerBT == null) { + _dialogService.showDialog(description: 'Укажите в настройках принтер для печати чеков'); + return; + } + + setState(() { + _printing = true; + }); + try { + printerManager.selectPrinter(PrinterBluetooth(state.printerBT)); + PaperSize paper = state.printerPaperSize == SettingPrinterPaperM80 + ? PaperSize.mm80 + : PaperSize.mm58; + if (SettingPrinterEncodingImage == state.printerEncoding) { + final PosPrintResult res = await printerManager.printTicket( + await printImageCheck(paper, widget.showModel.data.base64Data), + chunkSizeBytes: 3096, + queueSleepTimeMs: 50); + if (res.value != 1) { + _dialogService.showDialog(description: res.msg); + } + } else { + final PosPrintResult res = await printerManager.printTicket( + await printTextCheck( + paper, state.printerEncoding, + jsonDecode(widget.showModel.data.textData)), + chunkSizeBytes: 3096, + queueSleepTimeMs: 50); + if (res.value != 1) { + _dialogService.showDialog(description: res.msg); + } + } + } finally { + setState(() { + _printing = false; + }); + } + } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( //backgroundColor: fillColor, - title: Text(data.title), + title: Text(widget.showModel.title), + actions: [ + if(_printing) + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: SizedBox( + width: 36.0, + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: new AlwaysStoppedAnimation( + whiteColor), + ), + ), + ), + ) + else + IconButton(icon: Icon(Icons.print), onPressed: _print) + ], ), body: ListView( - children: [imageFromBase64String(data.data)], + children: [imageFromBase64String(widget.showModel.data.base64Data)], ), - floatingActionButton: MyFloatingActionButton(data), + floatingActionButton: MyFloatingActionButton(widget.showModel), ); } } @@ -41,7 +120,7 @@ Padding imageFromBase64String(String base64String) { } class ImageShowModel { - final String data; + final CheckImageModal data; final String title; final String url; @@ -60,8 +139,13 @@ class _MyFloatingActionButtonState extends State { DialogService _dialog = locator(); NavigatorService _navigatorService = locator(); + + double sheetHeight = 260; + @override Widget build(BuildContext context) { + + return showFab ? FloatingActionButton( child: Icon(Icons.share), @@ -79,7 +163,7 @@ class _MyFloatingActionButtonState extends State { color: Colors.grey[300], spreadRadius: 5) ]), - height: 260, + height: sheetHeight, child: Column( children: [ verticalSpaceSmall, @@ -102,7 +186,7 @@ class _MyFloatingActionButtonState extends State { BusyButtonIcon( title: 'Поделиться', onPressed: shareFile, - mainColor: yellowColor, + mainColor: redColor, icon: Icons.share, ), ], @@ -119,7 +203,7 @@ class _MyFloatingActionButtonState extends State { void shareFile() async { try { await Share.file('Aman Kassa', 'aman_kassa_check.png', - base64Decode(widget.data.data), 'image/png'); + base64Decode(widget.data.data.base64Data), 'image/png'); } catch (e) { print('error: $e'); } @@ -128,7 +212,7 @@ class _MyFloatingActionButtonState extends State { void qrGenerate() async { _navigatorService.push(QrViewRoute, arguments: - ImageShowModel(data: widget.data.url, title: 'Спасибо за покупку')); + ImageShowModel(url: widget.data.url, title: 'Спасибо за покупку')); } void callWhatsApp() async { diff --git a/lib/views/history/history_view.dart b/lib/views/history/history_view.dart index a1635a7..1e85ac8 100644 --- a/lib/views/history/history_view.dart +++ b/lib/views/history/history_view.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:aman_kassa_flutter/core/entity/Voucher.dart'; import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/check_image_modal.dart'; import 'package:aman_kassa_flutter/core/route_names.dart'; import 'package:aman_kassa_flutter/core/services/DbService.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; @@ -62,11 +65,18 @@ class _HistoryViewState extends State { }, itemBuilder: (BuildContext context, int index) { Voucher voucher = data[index]; + String base64Data = voucher.base64Data; + CheckImageModal checkImageData; + if(base64Data !=null && base64Data.startsWith('{')){ + checkImageData = CheckImageModal.fromJson(jsonDecode(base64Data)); + } else { + checkImageData = new CheckImageModal(base64Data: base64Data); + } return ListTile( onTap: () { _navigatorService.push(ImageShowRoute, arguments: ImageShowModel( - data: voucher.base64Data, + data: checkImageData, title: voucher.name, url: voucher.url)); }, diff --git a/lib/views/home/components/popup_menu.dart b/lib/views/home/components/popup_menu.dart index f500178..4f64903 100644 --- a/lib/views/home/components/popup_menu.dart +++ b/lib/views/home/components/popup_menu.dart @@ -8,6 +8,7 @@ const List choices = const [ const Choice( title: 'Информация о ККМ', icon: Icons.info_outline, command: 'infokkm'), const Choice(title: 'Настройки', icon: Icons.settings, command: 'settings'), + 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 727c9fc..b90b577 100644 --- a/lib/views/home/home_view.dart +++ b/lib/views/home/home_view.dart @@ -15,7 +15,6 @@ import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/views/home/components/header_title.dart'; import 'package:aman_kassa_flutter/views/lockscreen/passcodescreen.dart'; import 'package:aman_kassa_flutter/widgets/loader/Dialogs.dart'; - import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:logger/logger.dart'; @@ -87,7 +86,7 @@ class _HomeViewState extends State with WidgetsBindingObserver { onWillPop: () async { return false; }, - child: PassCodeScreen( title: 'Безопасность',) + child: PassCodeScreen( title: '',) ) )); } @@ -153,6 +152,8 @@ class _HomeViewState extends State with WidgetsBindingObserver { _navigatorService.push(InfoKkmViewRoute); } else if (choice.command == 'settings') { _navigatorService.push(SettingsViewRoute); + } else if (choice.command == 'print') { + _navigatorService.push(SettingsPrinterRoute); } } diff --git a/lib/views/payment/payment_view.dart b/lib/views/payment/payment_view.dart index 5e1d613..91ed945 100644 --- a/lib/views/payment/payment_view.dart +++ b/lib/views/payment/payment_view.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:aman_kassa_flutter/core/locator.dart'; import 'package:aman_kassa_flutter/core/models/calc_model.dart'; +import 'package:aman_kassa_flutter/core/models/check_image_modal.dart'; import 'package:aman_kassa_flutter/core/models/product_dao.dart'; import 'package:aman_kassa_flutter/core/models/response.dart'; import 'package:aman_kassa_flutter/core/route_names.dart'; @@ -195,6 +198,7 @@ class _PaymentViewState extends State { if (response.operation) { String message = response.body['message']; String check = response.body['check']; + var checkText = response.body['check_text']; String url = response?.body['link']; print('url : $url'); if (_mode == SettingModeCalc) { @@ -207,7 +211,7 @@ class _PaymentViewState extends State { Navigator.of(_keyLoader.currentContext, rootNavigator: true).pop(); _navigatorService.pop(); _navigatorService.push(ImageShowRoute, - arguments: ImageShowModel(data:check, title: message, url: url )); + arguments: ImageShowModel(data:new CheckImageModal(base64Data: check, textData: checkText !=null ? jsonEncode(checkText) : null ), title: message, url: url )); } else if (!response.operation && ![401,402,403,412,500].contains(response.status)) { Navigator.of(_keyLoader.currentContext, rootNavigator: true).pop(); _dialogService.showDialog(description: response.body['message']); diff --git a/lib/views/qr_view/qr_view.dart b/lib/views/qr_view/qr_view.dart index de6157e..ae73426 100644 --- a/lib/views/qr_view/qr_view.dart +++ b/lib/views/qr_view/qr_view.dart @@ -27,7 +27,7 @@ class _QrViewState extends State { body: Container( child: Center( child: QrImage( - data: widget.data.data, + data: widget.data.url, version: QrVersions.auto, size: 220.0, ), 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/PrinterTest.dart b/lib/views/settings/printer/PrinterTest.dart new file mode 100644 index 0000000..b3bfdff --- /dev/null +++ b/lib/views/settings/printer/PrinterTest.dart @@ -0,0 +1,229 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; +import 'package:charset_converter/charset_converter.dart'; +import 'package:esc_pos_utils/esc_pos_utils.dart'; +import 'package:flutter/services.dart'; +import 'package:image/image.dart' as Im; +import 'package:path_provider/path_provider.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +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.image(image); + ticket.feed(2); + ticket.cut(); + return ticket; +} + + +Future printTextCheck(PaperSize paper, String encoding, var data ) async { + final Ticket ticket = Ticket(paper); + + + + PosCodeTable codeTable; + if(encoding == SettingPrinterEncodingCp866) { + codeTable = PosCodeTable.pc866_2; + } else if(encoding == SettingPrinterEncodingWin1251) { + codeTable = PosCodeTable.wpc1251; + } + + ticket.setGlobalCodeTable(codeTable); + ticket.setGlobalFont(PosFontType.fontB); + + + String qr = data['qr']; + + + List rows = data['rows'] as List; + for(dynamic element in rows) { + var text = element['text']; + int size = element['size'] as int; + bool center = element['center'] !=null ? element['center'] as bool : false; + if(text is List) { + Uint8List firstCol = await CharsetConverter.encode(encoding, (text).first as String); + Uint8List lastCol = await CharsetConverter.encode(encoding, (text).last as String); + ticket.row([ + PosColumn( + textEncoded: firstCol, + width: 6, + styles: PosStyles(align: PosAlign.left), + ), + PosColumn( + textEncoded: lastCol, + width: 6, + styles: PosStyles(align: PosAlign.right), + ), + ]); + } else { + String line = text as String; + if(line == 'breakline') { + ticket.hr(); + } else if(line == 'br') { + ticket.emptyLines(1); + } else { + Uint8List encTxt11 = await CharsetConverter.encode(encoding, line); + ticket.textEncoded( encTxt11, styles: PosStyles( align: center ? PosAlign.center : PosAlign.left )); + } + } + } + + // Print barcode + //final List barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4]; + //ticket.barcode(Barcode.upcA(barData)); + //ticket.qrcode(qr, align: PosAlign.center); + ticket.emptyLines(1); + const double qrSize = 200; + try { + final uiImg = await QrPainter( + data: qr, + 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 = Im.decodeImage(imgFile.readAsBytesSync()); + + ticket.image(img); + } catch (e) { + print(e); + } + + + + ticket.feed(2); + + ticket.cut(); + return ticket; +} \ No newline at end of file diff --git a/lib/views/settings/printer/data/settings_envi.dart b/lib/views/settings/printer/data/settings_envi.dart new file mode 100644 index 0000000..dc55d35 --- /dev/null +++ b/lib/views/settings/printer/data/settings_envi.dart @@ -0,0 +1,12 @@ +import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; + +var encoding = { + SettingPrinterEncodingCp866: "CP-866", + SettingPrinterEncodingWin1251: "Windows-1251", + SettingPrinterEncodingImage: "Big-Encoding", +}; + +var paperSize = { + SettingPrinterPaperM58: "58 мм", + SettingPrinterPaperM80: "80 мм" +}; diff --git a/lib/views/settings/printer/example/check_test.dart b/lib/views/settings/printer/example/check_test.dart new file mode 100644 index 0000000..6d0c74f --- /dev/null +++ b/lib/views/settings/printer/example/check_test.dart @@ -0,0 +1,53 @@ +var exampleJson = { + "check_text": { + "rows": [ + {"size": 14, "text": "", "center": true}, + {"size": 15, "text": "breakline", "center": true}, + {"size": 14, "text": "ТОО «Aman Systems»"}, + {"size": 14, "text": "ИИН/БИН: 180640018960"}, + {"size": 14, "text": "Сер. номер ККМ: TEST00000005"}, + {"size": 14, "text": "Регистрационный номер: 123132132323"}, + {"size": 14, "text": "Коргальджинское шоссе, д.19 оф.308"}, + {"size": 15, "text": "breakline", "center": true}, + {"size": 14, "text": "Продажа,Наличные, ", "center": true}, + {"size": 15, "text": "ФИСКАЛЬНЫЙ ЧЕК №16580", "center": true}, + {"size": 15, "text": "ФП 471369529060", "center": true}, + {"size": 15, "text": "Дата: 03.03.2021 23:16", "center": true}, + {"size": 15, "text": "breakline", "center": true}, + {"size": 14, "text": "Кассир: Амантай ИХ"}, + {"size": 14, "text": "Касса: Касса 1"}, + {"size": 15, "text": "breakline", "center": true}, + {"size": 15, "text": "br"}, + {"size": 14, "text": "1. test"}, + {"size": 14, "text": "200,00 x 1 = 200,00"}, + {"size": 10, "text": "br"}, + {"size": 15, "text": "breakline"}, + { + "size": 17, + "text": ["ИТОГО:", "200,00"] + }, + { + "size": 14, + "text": ["Наличные", "200,00"] + }, + {"size": 15, "text": "breakline"}, + {"size": 17, "text": "В том числе НДС:"}, + { + "size": 14, + "text": ["12 %", "21,43"] + }, + {"size": 14, "text": ""}, + {"size": 15, "text": "breakline"}, + {"size": 15, "text": "ОФД АО \"Транстелеком\"", "center": true}, + {"size": 15, "text": "ofd1.kz", "center": true}, + {"size": 14, "text": ""}, + {"size": 15, "text": "breakline"}, + {"size": 14, "text": "", "center": true}, + {"size": 13, "text": "порядковый номер чека:19800", "center": true}, + {"size": 14, "text": "Онлайн касса Aman", "center": true}, + {"size": 14, "text": "kassa.aman.com.kz", "center": true} + ], + "qr": + "87.255.215.94:4000/t/?i=471369529060&f=123132132323&s=200.0&t=20210303T231651" + } +}; diff --git a/lib/views/settings/printer/views/PrinterEncoding.dart b/lib/views/settings/printer/views/PrinterEncoding.dart new file mode 100644 index 0000000..8f45bac --- /dev/null +++ b/lib/views/settings/printer/views/PrinterEncoding.dart @@ -0,0 +1,94 @@ +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 '../data/settings_envi.dart'; + + +class PrinterEncodingView extends StatefulWidget { + PrinterEncodingView({Key key, this.title}) : super(key: key); + final String title; + + @override + _PrinterEncodingViewState createState() => _PrinterEncodingViewState(); + +} + +class _PrinterEncodingViewState extends State { + + Logger _logger = getLogger('PrinterEncodingView'); + + @override + void initState() { + super.initState(); + } + + + + + + void _selectPrinter(String encoding, BuildContext context, ) async { + _logger.i(encoding); + await Redux.store.dispatch(selectPrinterEncodingFromSetting(encoding)); + Navigator.of(context).pop(false); + } + + + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Выберите кодировку'), + ), + body: ListView.builder( + itemCount: encoding.keys.length, + itemBuilder: (BuildContext _, int index) { + return InkWell( + onTap: () => _selectPrinter(encoding.keys.elementAt(index), context), + child: Column( + children: [ + Container( + height: 60, + padding: EdgeInsets.only(left: 10), + alignment: Alignment.centerLeft, + child: Row( + children: [ + Icon(Icons.sort_by_alpha_outlined), + SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(encoding.values.elementAt(index) ?? ''), + ], + ), + ) + ], + ), + ), + Divider(), + ], + ), + ); + }), + ); + } + +} \ No newline at end of file diff --git a/lib/views/settings/printer/views/PrinterPaperSize.dart b/lib/views/settings/printer/views/PrinterPaperSize.dart new file mode 100644 index 0000000..b028c84 --- /dev/null +++ b/lib/views/settings/printer/views/PrinterPaperSize.dart @@ -0,0 +1,94 @@ +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 '../data/settings_envi.dart'; + + +class PrinterPaperView extends StatefulWidget { + PrinterPaperView({Key key, this.title}) : super(key: key); + final String title; + + @override + _PrinterEncodingViewState createState() => _PrinterEncodingViewState(); + +} + +class _PrinterEncodingViewState extends State { + + Logger _logger = getLogger('PrinterEncodingView'); + + @override + void initState() { + super.initState(); + } + + + + + + void _selectPaper(String paperSize, BuildContext context, ) async { + _logger.i(encoding); + await Redux.store.dispatch(selectPrinterPaperSizeFromSetting(paperSize)); + Navigator.of(context).pop(false); + } + + + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Выберите ширину ленты'), + ), + body: ListView.builder( + itemCount: paperSize.keys.length, + itemBuilder: (BuildContext _, int index) { + return InkWell( + onTap: () => _selectPaper(paperSize.keys.elementAt(index), context), + child: Column( + children: [ + Container( + height: 60, + padding: EdgeInsets.only(left: 10), + alignment: Alignment.centerLeft, + child: Row( + children: [ + Icon(Icons.sort_by_alpha_outlined), + SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(paperSize.values.elementAt(index) ?? ''), + ], + ), + ) + ], + ), + ), + Divider(), + ], + ), + ); + }), + ); + } + +} \ No newline at end of file diff --git a/lib/views/settings/printer/views/PrinterSelect.dart b/lib/views/settings/printer/views/PrinterSelect.dart new file mode 100644 index 0000000..f28b03c --- /dev/null +++ b/lib/views/settings/printer/views/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/setting_printer_view.dart b/lib/views/settings/setting_printer_view.dart new file mode 100644 index 0000000..f255309 --- /dev/null +++ b/lib/views/settings/setting_printer_view.dart @@ -0,0 +1,121 @@ +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/constants/setting_const.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'; +import './printer/data/settings_envi.dart'; +import './printer/example/check_test.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 { + final SettingState state = Redux.store.state.settingState; + printerManager.selectPrinter(PrinterBluetooth(state.printerBT)); + // TODO Don't forget to choose printer's paper + PaperSize paper = state.printerPaperSize == SettingPrinterPaperM80 ? PaperSize.mm80 : PaperSize.mm58; + if(SettingPrinterEncodingImage == state.printerEncoding) { + final PosPrintResult res = await printerManager.printTicket( + await testTicketImage(paper), + chunkSizeBytes: 3096, + queueSleepTimeMs: 50 + ); + _dialogService.showDialog(description: res.msg); + } else { + final PosPrintResult res = await printerManager.printTicket( + await printTextCheck(paper, state.printerEncoding, exampleJson['check_text']), + 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); + }), + SettingItem( + title: 'Кодировка', + name: vm.printerEncoding != null ? encoding[vm.printerEncoding] : null , + onTap: () { + _navigatorService.push(SettingsPrinterEncodingRoute); + }), + SettingItem( + title: 'Ширина ленты', + name: vm.printerPaperSize != null ? paperSize[vm.printerPaperSize] : null , + onTap: () { + _navigatorService.push(SettingsPrinterPaperRoute); + }), + 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 69ca472..38f9189 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,13 +1,27 @@ # 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: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0" auto_size_text: dependency: "direct main" description: @@ -28,35 +42,42 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" + charset_converter: + dependency: "direct main" + description: + name: charset_converter + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" convert: 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: @@ -112,7 +154,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" ffi: dependency: transitive 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_lock_screen: dependency: "direct main" description: @@ -177,6 +226,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: @@ -191,6 +247,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: @@ -205,6 +275,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: @@ -212,6 +289,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.16.1" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" local_auth: dependency: "direct main" description: @@ -239,7 +330,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" material_design_icons_flutter: dependency: "direct main" description: @@ -253,7 +344,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" nested: dependency: transitive description: @@ -267,7 +358,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" path_provider: dependency: "direct main" description: @@ -310,6 +401,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: @@ -394,6 +492,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: "direct main" description: @@ -447,7 +552,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0" sqflite: dependency: "direct main" description: @@ -468,21 +573,21 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" synchronized: dependency: transitive description: @@ -496,21 +601,21 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" url_launcher: dependency: "direct main" description: @@ -559,7 +664,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" win32: dependency: transitive description: @@ -574,6 +679,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" + dart: ">=2.12.0-0.0 <3.0.0" + flutter: ">=1.22.2" diff --git a/pubspec.yaml b/pubspec.yaml index f924d38..6962819 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,10 @@ dependencies: shared_preferences: ^0.5.12+4 flutter_lock_screen: ^1.0.8 local_auth: ^0.6.3+4 + 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