receipt print release

null-safety-migration
error500 2021-10-15 11:50:08 +06:00
parent ae272604b6
commit 1a9958e84f
8 changed files with 293 additions and 104 deletions

View File

@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 30 compileSdkVersion 31
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
@ -53,7 +53,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "kz.com.aman.satu" applicationId "kz.com.aman.satu"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 31
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
// multiDexEnabled true // multiDexEnabled true

View File

@ -1,3 +1,5 @@
import 'package:satu/core/utils/utils_parse.dart';
const String goodTableName = 'goods'; const String goodTableName = 'goods';
const String GoodColumnId = 'id'; const String GoodColumnId = 'id';
const String GoodColumnCategoryId = 'category_id'; const String GoodColumnCategoryId = 'category_id';
@ -22,7 +24,7 @@ class Good {
ean = map[GoodColumnEan] as String; ean = map[GoodColumnEan] as String;
appCompanyId = map[GoodColumnAppCompanyId] as int; appCompanyId = map[GoodColumnAppCompanyId] as int;
optPrice = map[GoodColumnOptPrice] as num; optPrice = map[GoodColumnOptPrice] as num;
basePrice = map[GoodColumnBasePrice] as num; basePrice = cast<num>(map[GoodColumnBasePrice]);
divisible = map[GoodColumnDivisible] as int; divisible = map[GoodColumnDivisible] as int;
updatedAt = map[GoodColumnUpdatedAt] as String; updatedAt = map[GoodColumnUpdatedAt] as String;
} }

View File

@ -25,7 +25,7 @@ class DataService extends BaseService {
final DialogService _dialogService = locator<DialogService>(); final DialogService _dialogService = locator<DialogService>();
Future<bool> sellBtnHandler( Future<TransactionData?> sellBtnHandler(
{double card = 0, double nal = 0, double total = 0}) async { {double card = 0, double nal = 0, double total = 0}) async {
final SellRequest request = SellRequest(); final SellRequest request = SellRequest();
final SellState sellState = Redux.store!.state.sellState!; final SellState sellState = Redux.store!.state.sellState!;
@ -46,9 +46,9 @@ class DataService extends BaseService {
final SellResponse response = await _api.sell(request); final SellResponse response = await _api.sell(request);
if (response.operation == false) { if (response.operation == false) {
_dialogService.showDialog(description: response.message); _dialogService.showDialog(description: response.message);
return false; return null;
} }
await _updateTransaction( final TransactionData transactionData = await _updateTransaction(
transactionState: transactionState, transactionState: transactionState,
total: total, total: total,
card: card, card: card,
@ -56,10 +56,10 @@ class DataService extends BaseService {
sellResponse: response); sellResponse: response);
await Redux.store!.dispatch(loadSellData); await Redux.store!.dispatch(loadSellData);
return true; return transactionData;
} }
Future<void> _updateTransaction( Future<TransactionData> _updateTransaction(
{required TransactionState transactionState, {required TransactionState transactionState,
required double card, required double card,
required double nal, required double nal,
@ -83,6 +83,7 @@ class DataService extends BaseService {
transaction.data = jsonEncode(data.toMap()); transaction.data = jsonEncode(data.toMap());
log.i(jsonEncode(data.toMap())); log.i(jsonEncode(data.toMap()));
await _db.update(transactionTableName, transaction.toMap()); await _db.update(transactionTableName, transaction.toMap());
return data;
} }
ItemBean _productToItemBean(ProductDao product) { ItemBean _productToItemBean(ProductDao product) {

View File

@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:charset_converter/charset_converter.dart'; import 'package:charset_converter/charset_converter.dart';
@ -5,81 +6,149 @@ import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart';
import 'package:esc_pos_utils/esc_pos_utils.dart'; import 'package:esc_pos_utils/esc_pos_utils.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bluetooth_basic/flutter_bluetooth_basic.dart'; import 'package:flutter_bluetooth_basic/flutter_bluetooth_basic.dart';
import 'package:image/image.dart'; import 'package:image/image.dart' as Im;
import 'package:qr_flutter/qr_flutter.dart';
import 'package:satu/core/models/flow/check_bean.dart';
import 'package:satu/core/models/flow/check_row_bean.dart';
import 'package:satu/core/models/settings/printer_setting.dart'; import 'package:satu/core/models/settings/printer_setting.dart';
Future<List<int>> getReceipt(String encoding, String paperSize) async { Future<List<int>> getTestReceipt(String encoding, String paperSize) async {
final CheckBean checkBean = CheckBean();
final CheckRowBean rowBean1 = CheckRowBean();
rowBean1.text = 'Тестовый чек';
rowBean1.center = true;
final CheckRowBean rowBean2 = CheckRowBean();
rowBean2.text = ['Левая сторона', 'Правая сторона'];
rowBean2.center = true;
checkBean.rows.add(rowBean1);
checkBean.rows.add(CheckRowBean()..text='{br}'..center=true);
checkBean.rows.add(CheckRowBean()..text='{line}'..center=true);
checkBean.rows.add(CheckRowBean()..text='{br}'..center=true);
checkBean.rows.add(rowBean2);
checkBean.rows.add(CheckRowBean()..text='{br}'..center=true);
checkBean.rows.add(CheckRowBean()..text='{line}'..center=true);
checkBean.qr = 'test qr data';
return await _generateReceipt(encoding, paperSize, checkBean);
}
Future<List<int>> getReceipt(
String encoding, String paperSize, CheckBean checkBean) async {
return await _generateReceipt(encoding, paperSize, checkBean);
}
Future<List<int>> _generateReceipt(
String encoding, String paperSize, CheckBean checkBean) async {
List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
final generator = Generator( final generator = Generator(
paperSize == PrinterConst.paperSize58mm ? PaperSize.mm58 : PaperSize.mm80, paperSize == PrinterConst.paperSize58mm ? PaperSize.mm58 : PaperSize.mm80,
profile, profile,
); );
String codeTable = 'CP866'; String codeTable = 'CP866';
if (encoding == PrinterConst.encodingCP866) { if (encoding == PrinterConst.encodingCP866) {
codeTable = 'CP866'; codeTable = 'CP866';
} else if (encoding == PrinterConst.encodingCP1251) { } else if (encoding == PrinterConst.encodingCP1251) {
codeTable = 'CP1251'; codeTable = 'CP1251';
} }
List<int> bytes = [];
generator.setGlobalCodeTable(codeTable); generator.setGlobalCodeTable(codeTable);
for (CheckRowBean row in checkBean.rows) {
String type = row.text.runtimeType.toString();
final bool isList = type == 'List<dynamic>' || type == 'List<String>';
final bool isNull = type == 'Null';
final bool isString = type == 'String';
final bool center = row.center;
if (isNull) {
continue;
}
if (isString) {
String text = row.text as String;
text = text.replaceAll('&laquo;', '«');
text = text.replaceAll('&raquo;', '»');
final bool isLine = '{line}' == text;
final bool isBr = '{br}' == text;
if (isBr) {
bytes += generator.feed(1);
} else if (isLine) {
bytes += generator.hr();
} else {
final Uint8List firstCol = final Uint8List firstCol =
await CharsetConverter.encode(encoding.toLowerCase(), 'Тестовый чек'); await CharsetConverter.encode(encoding.toLowerCase(), text);
bytes += generator.textEncoded(firstCol); bytes += generator.textEncoded(firstCol,
styles: PosStyles(align: center ? PosAlign.center : PosAlign.left));
bytes += }
generator.text('CENTER', styles: const PosStyles(align: PosAlign.center)); }
bytes += generator.text('Right',
styles: const PosStyles(align: PosAlign.right), linesAfter: 1);
if (isList) {
final List<dynamic> list = row.text as List<dynamic>;
if (list.length == 2) {
final Uint8List firstCol = await CharsetConverter.encode(
encoding.toLowerCase(), list[0] as String);
final Uint8List secondCol = await CharsetConverter.encode(
encoding.toLowerCase(), list[1] as String);
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: 'col3', textEncoded: firstCol,
width: 3,
styles: const PosStyles(align: PosAlign.center, underline: true),
),
PosColumn(
text: 'col6',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.center, underline: true), styles: const PosStyles(align: PosAlign.left, underline: true),
), ),
PosColumn( PosColumn(
text: 'col3', textEncoded: secondCol,
width: 3, width: 6,
styles: const PosStyles(align: PosAlign.center, underline: true), styles: const PosStyles(align: PosAlign.right, underline: true),
), ),
]); ]);
}
}
bytes += generator.text( print('${row.text.runtimeType}, ${row.text}');
'Text size 200%', }
styles: const PosStyles( if (checkBean.qr != null) {
height: PosTextSize.size2, bytes += generator.feed(2);
width: PosTextSize.size2, const double qrSize = 200;
), try {
); final ByteData? uiImg = await QrPainter(
data: checkBean.qr!,
version: QrVersions.auto,
gapless: true,
).toImageData(qrSize);
final img = Im.decodePng(uiImg!.buffer.asUint8List());
bytes += generator.image(img!);
} catch (e) {
print(e);
}
// Print barcode }
final List<int> barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4];
bytes += generator.barcode(Barcode.upcA(barData));
bytes += generator.feed(2); bytes += generator.feed(2);
bytes += generator.cut(); bytes += generator.cut();
return bytes; return bytes;
} }
Future<List<int>> getReceiptImg(String paperSize) async { Future<List<int>> getTestReceiptImg(String paperSize) async {
final ByteData data =
await rootBundle.load('assets/images/aman_kassa_check.png');
final Uint8List imgBytes = data.buffer.asUint8List();
return _getReceiptImg(paperSize, imgBytes);
}
Future<List<int>> getReceiptImg(String paperSize, Uint8List imgBytes) async {
return _getReceiptImg(paperSize, imgBytes);
}
Future<List<int>> _getReceiptImg(String paperSize, Uint8List imgBytes) async {
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
final generator = Generator( final generator = Generator(
paperSize == PrinterConst.paperSize58mm ? PaperSize.mm58 : PaperSize.mm80, paperSize == PrinterConst.paperSize58mm ? PaperSize.mm58 : PaperSize.mm80,
profile, profile,
); );
List<int> bytes = []; List<int> bytes = [];
final ByteData data = final Im.Image? image = Im.decodeImage(imgBytes);
await rootBundle.load('assets/images/aman_kassa_check.png');
final Uint8List imgBytes = data.buffer.asUint8List();
final Image? image = decodeImage(imgBytes);
// Using `ESC *` // Using `ESC *`
bytes += generator.image(image!); bytes += generator.image(image!);
bytes += generator.feed(2); bytes += generator.feed(2);
@ -87,6 +156,22 @@ Future<List<int>> getReceiptImg(String paperSize) async {
return bytes; return bytes;
} }
int getChunkSize() {
final bool isIos = Platform.isIOS;
if (isIos) {
return 75;
}
return 1024;
}
int getQueueSleep() {
final bool isIos = Platform.isIOS;
if (isIos) {
return 10;
}
return 100;
}
BluetoothDevice printerDeviceToBluetoothDevice(PrinterDevice device) { BluetoothDevice printerDeviceToBluetoothDevice(PrinterDevice device) {
return BluetoothDevice() return BluetoothDevice()
..name = device.name ..name = device.name

View File

@ -34,7 +34,7 @@ class _PrinterViewState extends State<PrinterView> {
super.initState(); super.initState();
} }
void printTest() async { void print() async {
setState(() { setState(() {
printerLocked = true; printerLocked = true;
}); });
@ -45,40 +45,29 @@ class _PrinterViewState extends State<PrinterView> {
printerDeviceToBluetoothDevice(printerSetting.device!), printerDeviceToBluetoothDevice(printerSetting.device!),
), ),
); );
final int chunkSizeBytes = getChunkSize();
final int queueSleepTimeMs = getQueueSleep();
bool isIos = Platform.isIOS; List<int> data = List.empty();
int chunkSizeBytes = 3096;
int queueSleepTimeMs = 100;
if (isIos) {
chunkSizeBytes = 75;
queueSleepTimeMs = 10;
}
if (PrinterConst.encodingBigEncoding == printerSetting.encoding) { if (PrinterConst.encodingBigEncoding == printerSetting.encoding) {
PosPrintResult printResult = await printerManager.writeBytes( data = await getTestReceiptImg(
await getReceiptImg(
printerSetting.paperSize!, printerSetting.paperSize!,
),
chunkSizeBytes: chunkSizeBytes,
queueSleepTimeMs: queueSleepTimeMs,
); );
if(printResult.value != 1) {
_dialogService.showDialog(description: printResult.msg);
}
} else { } else {
PosPrintResult printResult = await printerManager.writeBytes( data = await getTestReceipt(
await getReceipt(
printerSetting.encoding!, printerSetting.encoding!,
printerSetting.paperSize!, printerSetting.paperSize!,
), );
}
final PosPrintResult printResult = await printerManager.writeBytes(
data,
chunkSizeBytes: chunkSizeBytes, chunkSizeBytes: chunkSizeBytes,
queueSleepTimeMs: queueSleepTimeMs, queueSleepTimeMs: queueSleepTimeMs,
); );
if (printResult.value != 1) { if (printResult.value != 1) {
_dialogService.showDialog(description: printResult.msg); _dialogService.showDialog(description: printResult.msg);
} }
}
} finally { } finally {
await Future.delayed(const Duration(seconds: 7)); await Future.delayed(const Duration(seconds: 7));
setState(() { setState(() {
@ -99,7 +88,7 @@ class _PrinterViewState extends State<PrinterView> {
final bool success = final bool success =
snapshot.device != null && printerLocked == false; snapshot.device != null && printerLocked == false;
return IconButton( return IconButton(
onPressed: success ? printTest : null, onPressed: success ? print : null,
icon: Icon( icon: Icon(
Icons.print, Icons.print,
color: success ? textColor : placeholderColor, color: success ? textColor : placeholderColor,

View File

@ -1,11 +1,13 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:satu/core/models/entity_data/transaction_data.dart';
import 'package:satu/core/redux/state/sell_state.dart'; import 'package:satu/core/redux/state/sell_state.dart';
import 'package:satu/core/redux/store.dart'; import 'package:satu/core/redux/store.dart';
import 'package:satu/core/services/navigator_service.dart'; import 'package:satu/core/services/navigator_service.dart';
import 'package:satu/core/utils/locator.dart'; import 'package:satu/core/utils/locator.dart';
import 'package:satu/core/utils/utils_parse.dart'; import 'package:satu/core/utils/utils_parse.dart';
import 'package:satu/routes/route_names.dart';
import 'package:satu/shared/app_colors.dart'; import 'package:satu/shared/app_colors.dart';
import 'package:satu/core/services/data_service.dart'; import 'package:satu/core/services/data_service.dart';
import 'package:satu/views/work/views/payment/component/combine_dock.dart'; import 'package:satu/views/work/views/payment/component/combine_dock.dart';
@ -135,7 +137,10 @@ class _PaymentViewState extends State<PaymentView> {
padding: padding:
const EdgeInsets.symmetric(horizontal: 45, vertical: 30), const EdgeInsets.symmetric(horizontal: 45, vertical: 30),
child: BusyButton( child: BusyButton(
title: 'ОПЛАТА', busy: loading, onPressed: _payment), title: 'ОПЛАТА',
busy: loading,
onPressed: _payment,
),
), ),
], ],
), ),
@ -161,10 +166,11 @@ class _PaymentViewState extends State<PaymentView> {
nal = _cashSum; nal = _cashSum;
} }
final bool result = final TransactionData? transactionData =
await _dataService.sellBtnHandler(card: card, nal: nal, total: _sum); await _dataService.sellBtnHandler(card: card, nal: nal, total: _sum);
if(result) { if (transactionData !=null) {
_navigatorService.pop(); _navigatorService.pop();
_navigatorService.push(receiptViewRoute, arguments: transactionData);
} }
setState(() { setState(() {
loading = false; loading = false;

View File

@ -1,12 +1,20 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:satu/core/models/entity_data/transaction_data.dart'; import 'package:satu/core/models/entity_data/transaction_data.dart';
import 'package:satu/core/models/flow/sell_response.dart'; import 'package:satu/core/models/flow/sell_response.dart';
import 'package:satu/core/models/settings/printer_setting.dart';
import 'package:satu/core/redux/store.dart';
import 'package:satu/core/services/dialog_service.dart';
import 'package:satu/core/utils/locator.dart';
import 'package:satu/core/utils/pos_printer.dart';
import 'package:satu/shared/app_colors.dart'; import 'package:satu/shared/app_colors.dart';
import 'package:satu/widgets/bar/products_app_bar.dart'; import 'package:satu/widgets/bar/products_app_bar.dart';
class ReceiptView extends StatelessWidget { class ReceiptView extends StatefulWidget {
const ReceiptView({ const ReceiptView({
required this.transactionData, required this.transactionData,
Key? key, Key? key,
@ -14,23 +22,106 @@ class ReceiptView extends StatelessWidget {
final TransactionData transactionData; final TransactionData transactionData;
@override
State<ReceiptView> createState() => _ReceiptViewState();
}
class _ReceiptViewState extends State<ReceiptView> {
final DialogService _dialogService = locator<DialogService>();
PrinterBluetoothManager printerManager = PrinterBluetoothManager();
bool printerLocked = false;
void print() async {
setState(() {
printerLocked = true;
});
try {
PrinterSetting printerSetting = Redux.store!.state.settingState!.printer!;
printerManager.selectPrinter(
PrinterBluetooth(
printerDeviceToBluetoothDevice(printerSetting.device!),
),
);
final int chunkSizeBytes = getChunkSize();
final int queueSleepTimeMs = getQueueSleep();
List<int> data = List.empty();
if (PrinterConst.encodingBigEncoding == printerSetting.encoding) {
data = await getReceiptImg(
printerSetting.paperSize!,
base64Decode(widget.transactionData.sellResponse!.checkPng!),
);
} else {
data = await getReceipt(
printerSetting.encoding!,
printerSetting.paperSize!,
widget.transactionData.sellResponse!.check!
);
}
final PosPrintResult printResult = await printerManager.writeBytes(
data,
chunkSizeBytes: chunkSizeBytes,
queueSleepTimeMs: queueSleepTimeMs,
);
if (printResult.value != 1) {
_dialogService.showDialog(description: printResult.msg);
}
} finally {
await Future.delayed(const Duration(seconds: 7));
setState(() {
printerLocked = false;
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: whiteColor, backgroundColor: whiteColor,
appBar: const ProductsAppBar( appBar: ProductsAppBar(
title: 'Просмотр чека', title: 'Просмотр чека',
actions: [
StoreConnector<AppState, PrinterSetting>(
converter: (store) => store.state.settingState!.printer!,
builder: (context, snapshot) {
final bool success =
snapshot.device != null && printerLocked == false;
return IconButton(
onPressed: success ? print : null,
icon: Icon(
Icons.print,
color: success ? textColor : placeholderColor,
),
);
},
)
],
), ),
body: Column( body: Column(
children: [ children: [
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
child: Center(child: imageFromBase64String(transactionData.sellResponse)), child: Center(
child:
imageFromBase64String(widget.transactionData.sellResponse),
),
), ),
) )
], ],
)); ),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Add your onPressed code here!
},
backgroundColor: successColor,
child: const Icon(
Icons.share_rounded,
size: 20,
),
),
);
} }
} }

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:satu/widgets/bar/products_app_bar.dart';
class ShareView extends StatelessWidget {
const ShareView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Scaffold(
appBar: ProductsAppBar(
title: 'Поделится',
),
);
}
}