import 'dart:async'; import 'dart:ui'; import 'package:bluetooth_print_plus/bluetooth_print_plus.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:iconly/iconly.dart'; import 'package:intl/intl.dart'; import 'package:mobile_pos/Provider/profile_provider.dart'; import 'package:mobile_pos/constant.dart'; import 'package:mobile_pos/currency.dart'; import 'package:mobile_pos/generated/l10n.dart' as lang; import 'package:pdf/widgets.dart' as pw; import '../../Const/api_config.dart'; import '../../GlobalComponents/glonal_popup.dart'; import '../../Provider/product_provider.dart'; import '../../thermal priting invoices/barcode_widget.dart'; import '../../thermal priting invoices/label_print_test.dart'; import '../../thermal priting invoices/sticker_image_generation.dart'; import '../Products/Model/product_model.dart'; import '../../service/check_user_role_permission_provider.dart'; import 'barcode_preview.dart'; class BarcodeGeneratorScreen extends StatefulWidget { const BarcodeGeneratorScreen({super.key}); @override _BarcodeGeneratorScreenState createState() => _BarcodeGeneratorScreenState(); } class _BarcodeGeneratorScreenState extends State { List products = []; List selectedProducts = []; bool showBusinessName = true; bool showName = true; bool showPrice = true; bool showPackageDate = true; bool showCode = true; final Map _controllers = {}; String formatDateString(String? dateString) { if (dateString == null) return 'N/A'; try { final parsed = DateTime.parse(dateString); return DateFormat('yyyy-MM-dd').format(parsed); } catch (e) { return 'N/A'; } } Future _preview({required String businessName}) async { final pdf = pw.Document(); List barcodeWidgets = []; for (var selectedProduct in selectedProducts) { for (int i = 0; i < selectedProduct.quantity; i++) { final stock = selectedProduct.product.stocks?.isNotEmpty == true ? selectedProduct.product.stocks!.first : null; barcodeWidgets.add(pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.center, mainAxisAlignment: pw.MainAxisAlignment.center, children: [ if (showBusinessName) pw.Text( businessName, style: pw.TextStyle( fontSize: double.tryParse(showNameFontSizeController.text) ?? 9, fontWeight: pw.FontWeight.normal, ), ), if (showName) pw.Text( '${selectedProduct.product.productName}', style: pw.TextStyle( fontSize: double.tryParse(showNameFontSizeController.text) ?? 9, fontWeight: pw.FontWeight.bold, ), ), if (showPrice && stock != null) pw.RichText( text: pw.TextSpan( text: '${lang.S.of(context).price}: ', style: pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.normal), children: [ pw.TextSpan( text: '${stock.productSalePrice}', style: pw.TextStyle( fontSize: double.tryParse(showPriceFontSizeController.text) ?? 8, fontWeight: pw.FontWeight.bold), ) ], ), ), if (showPackageDate && stock != null) pw.Text( '${lang.S.of(context).packingDate}: ${formatDateString(stock.mfgDate)}', textAlign: pw.TextAlign.center, style: pw.TextStyle( fontSize: double.tryParse(showPackageDateFontSizeController.text) ?? 7, fontWeight: pw.FontWeight.normal, ), ), pw.SizedBox(height: 2), pw.BarcodeWidget( drawText: showCode, data: selectedProduct.product.productCode ?? 'n/a', barcode: pw.Barcode.code128(), width: 80, height: 30, textPadding: 4, textStyle: pw.TextStyle(fontSize: double.tryParse(showCodeFontSizeController.text) ?? 8), ), ], )); } } pdf.addPage( pw.MultiPage( build: (pw.Context context) => [ pw.GridView( crossAxisCount: 4, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 0.68, children: barcodeWidgets, ), ], ), ); return pdf; } void _toggleCheckbox(bool value, void Function(bool) updateFunction) { setState(() { updateFunction(value); }); } //---------label print BluetoothDevice? _device; late StreamSubscription _isScanningSubscription; late StreamSubscription _blueStateSubscription; late StreamSubscription _connectStateSubscription; late StreamSubscription _receivedDataSubscription; late StreamSubscription> _scanResultsSubscription; List _scanResults = []; String _selectedSize = '0'; void _updateFontSizeControllers() { showCodeFontSizeController.text = getFontSize(_selectedSize, 'code'); showPriceFontSizeController.text = getFontSize(_selectedSize, 'price'); showNameFontSizeController.text = getFontSize(_selectedSize, 'name'); showPackageDateFontSizeController.text = getFontSize(_selectedSize, 'packageDate'); } String getFontSize(String selectedSize, String field) { if (selectedSize == '0') { switch (field) { case 'code': case 'price': return '8'; case 'name': return '9'; case 'businessName': return '8'; case 'packageDate': return '7'; default: return '8'; } } else if (selectedSize == '1') { switch (field) { case 'code': case 'price': case 'name': case 'businessName': case 'packageDate': return '19.5'; default: return '19.5'; } } else if (selectedSize == '2') { switch (field) { case 'code': case 'price': case 'name': case 'businessName': case 'packageDate': return '20.0'; default: return '20'; } } else { return '20'; } } late TextEditingController showCodeFontSizeController; late TextEditingController showPriceFontSizeController; late TextEditingController showNameFontSizeController; late TextEditingController showBusinessNameFontSizeController; late TextEditingController showPackageDateFontSizeController; @override void initState() { super.initState(); initBluetoothPrintPlusListen(); showCodeFontSizeController = TextEditingController(text: getFontSize(_selectedSize, 'code')); showPriceFontSizeController = TextEditingController(text: getFontSize(_selectedSize, 'price')); showNameFontSizeController = TextEditingController(text: getFontSize(_selectedSize, 'name')); showBusinessNameFontSizeController = TextEditingController(text: getFontSize(_selectedSize, 'businessName')); showPackageDateFontSizeController = TextEditingController(text: getFontSize(_selectedSize, 'packageDate')); } @override void dispose() { super.dispose(); for (final controller in _controllers.values) { controller.dispose(); } _isScanningSubscription.cancel(); _blueStateSubscription.cancel(); _connectStateSubscription.cancel(); _receivedDataSubscription.cancel(); _scanResultsSubscription.cancel(); _scanResults.clear(); showCodeFontSizeController.dispose(); showPriceFontSizeController.dispose(); showNameFontSizeController.dispose(); showPackageDateFontSizeController.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Consumer( builder: (context, ref, __) { final productData = ref.watch(productProvider); final businessInfo = ref.watch(businessInfoProvider); final permissionService = PermissionService(ref); return GlobalPopup( child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( titleSpacing: 0, centerTitle: false, title: Text( lang.S.of(context).barcodeGenerator, ), backgroundColor: Colors.white, ), body: productData.when( data: (snapshot) { products = snapshot; return Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( children: [ //-----------------search_bar TypeAheadField( builder: (context, controller, focusNode) { return TextField( controller: controller, focusNode: focusNode, autofocus: false, decoration: kInputDecoration.copyWith( fillColor: kWhite, border: const OutlineInputBorder( borderSide: BorderSide( color: kMainColor, ), ), hintText: lang.S.of(context).searchProduct, suffixIcon: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: kMainColor, ), child: const Icon( Icons.search, color: Colors.white, ), ), contentPadding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), ), ); }, suggestionsCallback: (pattern) { return products .where( (product) => product.productName!.toLowerCase().startsWith(pattern.toLowerCase())) .toList(); }, itemBuilder: (context, Product suggestion) { return Container( color: Colors.white, child: ListTile( leading: suggestion.productPicture != null ? Container( height: 40, width: 40, decoration: BoxDecoration( borderRadius: BorderRadiusGeometry.circular(2), border: Border.all( color: kBorderColorTextField, width: 0.3, ), image: DecorationImage( image: NetworkImage( '${APIConfig.domain}${suggestion.productPicture}', ), fit: BoxFit.cover, ), ), ) : Container( height: 40, width: 40, decoration: BoxDecoration( borderRadius: BorderRadiusGeometry.circular(2), border: Border.all( color: CupertinoColors.systemGrey6, width: 0.3, ), color: CupertinoColors.systemGrey6, ), child: Icon(IconlyLight.image), ), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: Text( suggestion.productName ?? 'n/a', maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium?.copyWith( color: kTitleColor, fontWeight: FontWeight.w700, fontSize: 14, ), ), ), SizedBox(width: 4), Flexible( child: Text( '${lang.S.of(context).code}: ${suggestion.productCode?.toString() ?? '0'}', maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium?.copyWith( color: kGreyTextColor, fontWeight: FontWeight.w400, fontSize: 13, ), ), ), ], ), isThreeLine: true, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 0), minVerticalPadding: 8, visualDensity: VisualDensity(vertical: -4), subtitle: ListView.builder( shrinkWrap: true, itemCount: suggestion.stocks?.length, itemBuilder: (context, i) { return Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: Text( '${lang.S.of(context).batch}: ${suggestion.stocks?[i].batchNo?.toString() ?? 'n/a'}', maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium?.copyWith( color: kTitleColor, fontWeight: FontWeight.w400, fontSize: 13, ), ), ), SizedBox(width: 4), Text( suggestion.stocks?[i].productStock != 0 ? ', ${lang.S.of(context).inStock}: ${suggestion.stocks?[i].productStock?.toString() ?? 'n/a'}' : ', ${lang.S.of(context).outOfStock}', style: theme.textTheme.bodyMedium?.copyWith( color: suggestion.stocks?[i].productStock != 0 ? Colors.green : Colors.red, fontWeight: FontWeight.w400, fontSize: 13, ), ), Spacer(), Text( '${suggestion.stocks?.isNotEmpty == true ? ('$currency${suggestion.stocks?[i].productSalePrice ?? 0}') : null}', style: theme.textTheme.bodyMedium?.copyWith( color: kTitleColor, fontWeight: FontWeight.w600, fontSize: 13, ), ), ], ); })), ); }, onSelected: (Product product) { setState(() { if (product.stocks != null && product.stocks!.isNotEmpty) { if (product.stocks!.length > 1 || product.productType == 'variant') { for (var stock in product.stocks!) { final variantKey = '${product.id}_${stock.batchNo}'; final initialQty = stock.productStock ?? 1; final existingIndex = selectedProducts.indexWhere( (p) => '${p.product.id}_${p.product.stocks?.first.batchNo}' == variantKey); if (existingIndex != -1) { selectedProducts[existingIndex].quantity = (selectedProducts[existingIndex].quantity + 1) .clamp(1, double.maxFinite.toInt()); _controllers[variantKey]?.text = selectedProducts[existingIndex].quantity.toString(); } else { selectedProducts.add(SelectedProduct( product: Product( id: product.id, productName: product.productName, productCode: product.productCode, productType: product.productType, stocksSumProductStock: stock.productStock, stocks: [stock], unit: product.unit, brand: product.brand, category: product.category, ), quantity: initialQty, )); _controllers[variantKey] = TextEditingController(text: initialQty.toString()); } } } else { final initialQty = product.stocks!.first.productStock ?? 1; final existingIndex = selectedProducts.indexWhere((p) => p.product.id == product.id); if (existingIndex != -1) { selectedProducts[existingIndex].quantity = (selectedProducts[existingIndex].quantity + 1) .clamp(1, double.maxFinite.toInt()); _controllers[product.id.toString()]?.text = selectedProducts[existingIndex].quantity.toString(); } else { selectedProducts.add(SelectedProduct( product: product, quantity: initialQty, )); _controllers[product.id.toString()] = TextEditingController(text: initialQty.toString()); } } } }); }, ), const SizedBox(height: 14), //-----------------check_box Theme( data: Theme.of(context).copyWith(dividerColor: Colors.transparent), child: ExpansionTile( leading: Icon(Icons.settings), title: Text( lang.S.of(context).informationShowInLabels, style: theme.textTheme.bodyMedium?.copyWith(color: kTitleColor, fontSize: 14), ), tilePadding: EdgeInsets.zero, childrenPadding: EdgeInsets.zero, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: _buildCheckboxWithFontSize( context, value: showCode, label: lang.S.of(context).showCode, fontSizeController: showCodeFontSizeController, onChanged: (val) => _toggleCheckbox(val, (v) => showCode = v), ), ), SizedBox(width: 10), Expanded( child: _buildCheckboxWithFontSize( context, value: showPrice, label: lang.S.of(context).showPrice, fontSizeController: showPriceFontSizeController, onChanged: (val) => _toggleCheckbox(val, (v) => showPrice = v), ), ), ], ), Row( children: [ Expanded( child: _buildCheckboxWithFontSize( context, value: showName, label: lang.S.of(context).showName, fontSizeController: showNameFontSizeController, onChanged: (val) => _toggleCheckbox(val, (v) => showName = v), ), ), SizedBox(width: 10), Expanded( child: _buildCheckboxWithFontSize( context, value: showPackageDate, label: lang.S.of(context).packageDate, fontSizeController: showPackageDateFontSizeController, onChanged: (val) => _toggleCheckbox(val, (v) => showPackageDate = v), ), ), ], ), SizedBox( width: MediaQuery.of(context).size.width / 2 - 20, child: _buildCheckboxWithFontSize( context, value: showBusinessName, label: lang.S.of(context).businessName, fontSizeController: showBusinessNameFontSizeController, onChanged: (val) => _toggleCheckbox(val, (v) => showBusinessName = v), ), ), SizedBox(height: 16), SizedBox( height: 40, child: DropdownButtonFormField( isExpanded: true, value: _selectedSize, decoration: InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), isDense: true, labelText: lang.S.of(context).barCodePrintLabelSetting), onChanged: (value) { if (value != null) { setState(() { _selectedSize = value; _updateFontSizeControllers(); }); } }, items: [ DropdownMenuItem( value: '2', child: SizedBox( width: MediaQuery.of(context).size.width - 72, child: Text( lang.S.of(context).labelRoleLabelSize2Inch, style: theme.textTheme.bodySmall?.copyWith(color: kTitleColor, fontSize: 12), overflow: TextOverflow.ellipsis, ), ), ), DropdownMenuItem( value: '1', child: SizedBox( width: MediaQuery.of(context).size.width - 72, child: Text( lang.S.of(context).labelRoleLabelSize1_5Inch, style: theme.textTheme.bodySmall?.copyWith(color: kTitleColor, fontSize: 12), overflow: TextOverflow.ellipsis, ), ), ), DropdownMenuItem( value: '0', child: SizedBox( width: MediaQuery.of(context).size.width - 72, child: Text( lang.S.of(context).thirtyTwoLabelPerSheet, style: theme.textTheme.bodySmall?.copyWith(color: kTitleColor, fontSize: 12), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ), ], ), ), const SizedBox(height: 16), ], ), ], ), ), //-----------------data_table selectedProducts.isNotEmpty ? SizedBox( width: double.maxFinite, child: DataTable( headingRowColor: WidgetStateProperty.all(Colors.red.shade50), showBottomBorder: true, horizontalMargin: 8, columns: [ DataColumn(label: Text(lang.S.of(context).name)), DataColumn(label: Text(lang.S.of(context).quantity)), DataColumn(label: Text(lang.S.of(context).actions)), ], rows: selectedProducts.map((selectedProduct) { // final controllerKey = (selectedProduct.product.stocks?.length ?? 0) > 1 || selectedProduct.product.productType == 'variant' // ? '${selectedProduct.product.id}_${selectedProduct.product.stocks?.first.batchNo}' // : selectedProduct.product.id.toString(); // final controller = _controllers[controllerKey]; final controllerKey = (selectedProduct.product.stocks?.length ?? 0) > 1 || selectedProduct.product.productType == 'variant' ? '${selectedProduct.product.id}_${selectedProduct.product.stocks?.first.batchNo}' : selectedProduct.product.id.toString(); final controller = _controllers.putIfAbsent( controllerKey, () => TextEditingController(text: selectedProduct.quantity.toString()), ); // Add error state for this product final hasError = ValueNotifier(false); return DataRow( cells: [ DataCell( Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( selectedProduct.product.productName ?? 'N/A', style: theme.textTheme.bodyMedium, maxLines: 1, ), Text( selectedProduct.product.productCode ?? 'N/A', style: theme.textTheme.bodySmall?.copyWith(color: kGreyTextColor), ), ], ), ), DataCell( SizedBox( height: 38, width: 60, // child: TextFormField( // controller: controller, // keyboardType: TextInputType.number, // textAlign: TextAlign.center, // onChanged: (value) { // setState(() { // final newQty = (int.tryParse(value) ?? 1).clamp(1, double.maxFinite.toInt()); // selectedProduct.quantity = newQty; // controller?.text = newQty.toString(); // }); // }, // decoration: const InputDecoration( // border: OutlineInputBorder(), // contentPadding: EdgeInsets.symmetric(vertical: 8.0), // ), // inputFormatters: [ // FilteringTextInputFormatter.digitsOnly, // ], // ), child: TextFormField( controller: controller, keyboardType: TextInputType.number, textAlign: TextAlign.center, // onChanged: (value) { // setState(() { // final newQty = (int.tryParse(value) ?? 1).clamp(1, double.maxFinite.toInt()); // selectedProduct.quantity = newQty; // }); // }, onChanged: (value) { final newQty = int.tryParse(value) ?? 0; if (newQty < 1) { hasError.value = true; selectedProduct.quantity = 0; } else { hasError.value = false; selectedProduct.quantity = newQty; } }, decoration: const InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(vertical: 8.0), ), inputFormatters: [ FilteringTextInputFormatter.digitsOnly, ], ), ), ), DataCell( IconButton( icon: const Icon( Icons.delete, color: kMainColor, ), onPressed: () { setState(() { final controllerKey = (selectedProduct.product.stocks?.length ?? 0) > 1 || selectedProduct.product.productType == 'variant' ? '${selectedProduct.product.id}_${selectedProduct.product.stocks?.first.batchNo}' : selectedProduct.product.id.toString(); _controllers[controllerKey]?.dispose(); _controllers.remove(controllerKey); selectedProducts.remove(selectedProduct); }); }, ), ), ], ); }).toList(), ), ) : Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 50), const Icon( IconlyLight.document, color: kMainColor, size: 70, ), Text( lang.S.of(context).noItemSelected, style: theme.textTheme.titleLarge?.copyWith( fontSize: 18, fontWeight: FontWeight.w600, ), ), ], ), ), ], ), ), ); }, error: (e, stack) => Center(child: Text(e.toString())), loading: () => const Center(child: CircularProgressIndicator()), ), bottomNavigationBar: businessInfo.when( data: (details) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: ElevatedButton.icon( style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), backgroundColor: kMainColor, minimumSize: const Size(double.maxFinite, 48), textStyle: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600, ), ), onPressed: () async { if (!permissionService.hasPermission(Permit.barcodesCreate.value)) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: Colors.red, content: Text(lang.S.of(context).youDoNotHaveAnyPermissionToGenerateBarCode), ), ); return; } if (selectedProducts.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(lang.S.of(context).pleaseSelectAProductFirst)), ); return; } bool hasInvalidQuantity = false; for (var product in selectedProducts) { if (product.quantity < 1) { hasInvalidQuantity = true; break; } } if (hasInvalidQuantity) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(lang.S.of(context).pleaseEnterAValidQuantity)), ); return; } if (selectedProducts.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(lang.S.of(context).pleaseSelectAProductFirst), )); return; } if (_selectedSize == '0') { final pdfDocument = await _preview(businessName: details.data?.companyName ?? 'n/a'); Navigator.of(context).push( MaterialPageRoute( builder: (context) => PdfPreviewScreen(pdfDocument: pdfDocument), ), ); return; } // Check Bluetooth status if (!BluetoothPrintPlus.isBlueOn) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(lang.S.of(context).bluetoothIsTurnedOff)), ); return; } if (!BluetoothPrintPlus.isConnected) { await listOfBluDialog(context: context); if (_device == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(lang.S.of(context).noBluetoothDeviceSelected)), ); return; } } // Begin printing loop for (var selectedProduct in selectedProducts) { final product = selectedProduct.product; final stock = product.stocks?.isNotEmpty == true ? product.stocks!.first : null; for (int i = 0; i < selectedProduct.quantity; i++) { final pngBytes = await createImageFromWidget( context, StickerWidget( data: StickerData( businessName: details.data?.companyName ?? 'n/a', name: product.productName ?? 'N/A', price: stock?.productSalePrice ?? 0.0, code: product.productCode ?? 'N/A', mfg: stock?.mfgDate ?? 'N/A', isTwoIch: _selectedSize == '2', showBusinessName: showBusinessName, showName: showName, showPrice: showPrice, showCode: showCode, showMfg: showPackageDate, nameFontSize: double.tryParse(showNameFontSizeController.text) ?? 20, codeFontSize: double.tryParse(showCodeFontSizeController.text) ?? 20, mfgFontSize: double.tryParse(showPackageDateFontSizeController.text) ?? 20, priceFontSize: double.tryParse(showPriceFontSizeController.text) ?? 20, ), ), logicalSize: Size(_selectedSize == '2' ? 350 : 280, 180), imageSize: Size(_selectedSize == '2' ? 350 : 280, 180), ); await printLabelTest( productName: product.productName ?? 'N/A', date: stock?.mfgDate ?? 'N/A', price: '\$${stock?.productSalePrice ?? 0.0}', barcodeData: product.productCode ?? 'N/A', pngBytes: pngBytes!, isTwoInch: _selectedSize == '2', ); } } }, icon: Icon(_selectedSize == '0' ? Icons.preview : Icons.print), label: Text( _selectedSize == '0' ? lang.S.of(context).previewPdf : lang.S.of(context).printLabel, style: theme.textTheme.titleLarge?.copyWith( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 18, ), ), ), ); }, error: (e, stack) => Center(child: Text(e.toString())), loading: () => const Center(child: CircularProgressIndicator()), ), ), ); }, ); } Future initBluetoothPrintPlusListen() async { /// listen scanResults _scanResultsSubscription = BluetoothPrintPlus.scanResults.listen((event) { if (mounted) { setState(() { _scanResults = event; }); } }); /// listen isScanning _isScanningSubscription = BluetoothPrintPlus.isScanning.listen((event) { print('********** isScanning: $event **********'); if (mounted) { setState(() {}); } }); /// listen blue state _blueStateSubscription = BluetoothPrintPlus.blueState.listen((event) { print('********** blueState change: $event **********'); if (mounted) { setState(() {}); } }); /// listen connect state _connectStateSubscription = BluetoothPrintPlus.connectState.listen((event) async { print('********** connectState change: $event **********'); switch (event) { case ConnectState.connected: setState(() {}); break; case ConnectState.disconnected: setState(() { _device = null; }); break; } }); /// listen received data _receivedDataSubscription = BluetoothPrintPlus.receivedData.listen((data) { print('********** received data: $data **********'); /// do something... }); } Future listOfBluDialog({required BuildContext context}) async { return showCupertinoDialog( context: context, builder: (_) { // Start scanning when dialog is shown WidgetsBinding.instance.addPostFrameCallback((_) { onScanPressed(); }); return WillPopScope( onWillPop: () async => false, child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: StatefulBuilder( builder: (context, setDialogState) { return CupertinoAlertDialog( insetAnimationCurve: Curves.bounceInOut, content: Container( height: 300, width: double.maxFinite, child: BluetoothPrintPlus.isBlueOn ? StreamBuilder>( stream: BluetoothPrintPlus.scanResults, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text(lang.S.of(context).caningForDevices), ], ), ); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.bluetooth_disabled, size: 40, color: Colors.grey), SizedBox(height: 8), Text(lang.S.of(context).noDeviceFound), SizedBox(height: 8), ElevatedButton( onPressed: () => onScanPressed(), child: Text(lang.S.of(context).retryScan), ), ], ), ); } else { return ListView.builder( padding: EdgeInsets.all(0), itemCount: snapshot.data!.length, itemBuilder: (context1, index) { final device = snapshot.data![index]; return ListTile( contentPadding: EdgeInsets.all(16), title: Text(device.name), subtitle: Text(device.address), onTap: () async { setDialogState(() {}); await BluetoothPrintPlus.connect(device); _device = device; Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('${lang.S.current.connectedTo}${device.name}')), ); }, ); }, ); } }, ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.bluetooth_disabled, size: 40, color: Colors.red), SizedBox(height: 8), Text(lang.S.of(context).pleaseEnableBluetooth), ], ), ), ), title: Text(lang.S.of(context).connectPrinter), actions: [ CupertinoDialogAction( child: Text(lang.S.of(context).cancel), onPressed: () { BluetoothPrintPlus.stopScan(); Navigator.pop(context); }, ), ], ); }, ), ), ); }, ); } Widget _buildCheckboxWithFontSize( BuildContext context, { required bool value, required String label, required TextEditingController fontSizeController, required ValueChanged onChanged, }) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Column( children: [ Row( children: [ Checkbox( activeColor: kMainColor, value: value, visualDensity: const VisualDensity(horizontal: -4, vertical: -4), onChanged: (val) => onChanged(val!), ), Expanded( child: Text( label, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 4), SizedBox( height: 40, child: TextFormField( controller: fontSizeController, keyboardType: TextInputType.number, style: TextStyle(fontSize: 14), decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), prefixIcon: Container( width: 50, decoration: BoxDecoration( color: Color(0xffD8D8D8).withOpacity(0.3), borderRadius: BorderRadius.only( topLeft: Radius.circular(8), bottomLeft: Radius.circular(8), ), ), child: Center( child: Text( lang.S.of(context).size, style: TextStyle(fontSize: 12), ), ), ), ), ), ), ], ), ); } Widget buildBlueOffWidget() { return Center( child: Text( "${lang.S.of(context).bluetoothIsTurnedOff}...", style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: Colors.red), textAlign: TextAlign.center, )); } Widget buildScanButton(BuildContext context) { if (BluetoothPrintPlus.isScanningNow) { return FloatingActionButton( onPressed: onStopPressed, backgroundColor: Colors.red, child: Icon(Icons.stop), ); } else { return FloatingActionButton(onPressed: onScanPressed, backgroundColor: Colors.green, child: Text("SCAN")); } } Future onScanPressed() async { try { await BluetoothPrintPlus.startScan(timeout: Duration(seconds: 10)); setState(() {}); } catch (e) { print("onScanPressed error: $e"); } } Future onStopPressed() async { try { BluetoothPrintPlus.stopScan(); } catch (e) { print("onStopPressed error: $e"); } } } class SelectedProduct { final Product product; num quantity; SelectedProduct({required this.product, required this.quantity}); }