bluetooth_pos_printer_utils 1.0.3 copy "bluetooth_pos_printer_utils: ^1.0.3" to clipboard
bluetooth_pos_printer_utils: ^1.0.3 copied to clipboard

This package is a fork of flutter_esc_pos_utils customized for bluetooth_pos_printer.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:bluetooth_pos_printer_utils/bluetooth_pos_printer_utils.dart';
import 'package:bluetooth_pos_printer/bluetooth_pos_printer.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'POS Utils Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const PrinterScreen(),
    );
  }
}

class PrinterScreen extends StatefulWidget {
  const PrinterScreen({super.key});

  @override
  State<PrinterScreen> createState() => _PrinterScreenState();
}

class _PrinterScreenState extends State<PrinterScreen> {
  List<BluetoothPrinterDevice> _devices = [];
  bool _isScanning = false;
  BluetoothPrinterDevice? _connectedDevice;
  final _printerPlugin = BluetoothPosPrinter.instance;
  PrinterStatus _connectionStatus = PrinterStatus.disconnected;
  bool _is80mm = true;
  late StreamSubscription<PrinterStatus> _statusSubscription;

  @override
  void initState() {
    super.initState();
    _statusSubscription = _printerPlugin.statusStream.listen((status) {
      if (mounted) {
        setState(() {
          _connectionStatus = status;
        });
      }
    });
    _startScan();
  }

  @override
  void dispose() {
    _statusSubscription.cancel();
    super.dispose();
  }

  void _startScan() async {
    final isBluetoothOn = await _printerPlugin.isBluetoothEnabled();
    if (!isBluetoothOn && mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Ative o Bluetooth para escanear.')),
      );
      return;
    }

    setState(() {
      _isScanning = true;
      _devices.clear();
    });

    try {
      final devices = await _printerPlugin.scan(mode: BluetoothScanMode.active);
      if (mounted) {
        setState(() {
          _devices = devices;
        });
      }
    } catch (e) {
      debugPrint("Error scanning: \$e");
    } finally {
      if (mounted) {
        setState(() {
          _isScanning = false;
        });
      }
    }
  }

  void _connect(BluetoothPrinterDevice device) async {
    final success = await _printerPlugin.connect(device.address);
    if (success && mounted) {
      setState(() {
        _connectedDevice = device;
      });
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('Conectado a \${device.name}')));
    }
  }

  void _disconnect() async {
    await _printerPlugin.disconnect();
    if (mounted) {
      setState(() {
        _connectedDevice = null;
      });
    }
  }

  Future<void> _printAllFeatures() async {
    if (_connectedDevice == null) return;

    try {
      final profile = await CapabilityProfile.load();
      final generator = Generator(
        _is80mm ? PaperSize.mm80 : PaperSize.mm58,
        profile,
      );
      final bytes = <int>[];

      // O comando reset injeta a tabela CP1252 por padrão para suportar o Euro
      bytes.addAll(generator.reset());

      // Teste 0: Imagem com Halftone
      bytes.addAll(
        generator.text(
          '0. TESTE DE IMAGEM',
          styles: const PosStyles(bold: true),
        ),
      );
      try {
        final data = await rootBundle.load('assets/image1.jpg');
        final Uint8List imageBytes = data.buffer.asUint8List();

        bytes.addAll(generator.image(imageBytes, align: PosAlign.center));
        bytes.addAll(
          generator.image(imageBytes, align: PosAlign.left, width: 150),
        );
        bytes.addAll(
          generator.image(imageBytes, align: PosAlign.right, width: 50),
        );
        bytes.addAll(generator.feed(2));
      } catch (e) {
        debugPrint('Erro ao carregar a imagem do asset: \$e');
      }
      // Cabeçalho
      bytes.addAll(
        generator.text(
          'TESTE COMPLETO DE FEATURES',
          styles: const PosStyles(
            align: PosAlign.center,
            bold: true,
            height: PosTextSize.size2,
            width: PosTextSize.size2,
          ),
        ),
      );
      bytes.addAll(
        generator.text(
          '================================',
          styles: const PosStyles(align: PosAlign.center),
        ),
      );

      // Teste 1: Acentuação e Euro (Valida o remove_diacritic)
      bytes.addAll(
        generator.text(
          '1. ACENTUACAO E EURO',
          styles: const PosStyles(bold: true),
        ),
      );
      bytes.addAll(generator.text('Símbolo do Euro: €'));
      bytes.addAll(generator.text('Acentuação: Ação, Coração, Avó'));
      bytes.addAll(generator.text('Moedas: \$ £ ¥ ¢'));
      bytes.addAll(generator.feed(1));

      // Teste 2: Alinhamentos
      bytes.addAll(
        generator.text('2. ALINHAMENTO', styles: const PosStyles(bold: true)),
      );
      bytes.addAll(
        generator.text(
          'Alinhado à Esquerda',
          styles: const PosStyles(align: PosAlign.left),
        ),
      );
      bytes.addAll(
        generator.text(
          'Centralizado',
          styles: const PosStyles(align: PosAlign.center),
        ),
      );
      bytes.addAll(
        generator.text(
          'Alinhado à Direita',
          styles: const PosStyles(align: PosAlign.right),
        ),
      );
      bytes.addAll(generator.feed(1));

      // Teste 3: Estilos (Negrito, Sublinhado, Reverso, Fontes)
      bytes.addAll(
        generator.text(
          '3. ESTILOS E FONTES',
          styles: const PosStyles(bold: true),
        ),
      );
      bytes.addAll(
        generator.text('Texto em Negrito', styles: const PosStyles(bold: true)),
      );
      bytes.addAll(
        generator.text(
          'Texto Sublinhado',
          styles: const PosStyles(underline: true),
        ),
      );
      bytes.addAll(
        generator.text('Texto Reverso', styles: const PosStyles(reverse: true)),
      );
      bytes.addAll(
        generator.text(
          'Fonte A (Padrão)',
          styles: const PosStyles(fontType: PosFontType.fontA),
        ),
      );
      bytes.addAll(
        generator.text(
          'Fonte B (Menor)',
          styles: const PosStyles(fontType: PosFontType.fontB),
        ),
      );
      bytes.addAll(
        generator.text(
          'Fonte C (Opcional)',
          styles: const PosStyles(fontType: PosFontType.fontC),
        ),
      );
      // Restaura para Fonte A
      bytes.addAll(
        generator.text(
          'De volta para Fonte A',
          styles: const PosStyles(fontType: PosFontType.fontA),
        ),
      );
      bytes.addAll(generator.feed(1));

      // Teste 4: Códigos de Barras
      bytes.addAll(
        generator.text(
          '4. CÓDIGOS DE BARRAS',
          styles: const PosStyles(bold: true),
        ),
      );
      final List<int> barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2];
      bytes.addAll(generator.barcode(Barcode.upcA(barData)));
      bytes.addAll(generator.feed(1));

      // Teste 5: QR Code
      bytes.addAll(
        generator.text('5. QR CODE', styles: const PosStyles(bold: true)),
      );
      bytes.addAll(generator.qrcode('https://github.com/senuntech'));
      bytes.addAll(generator.feed(2));

      // Teste 6: Colunas
      bytes.addAll(
        generator.text('6. COLUNAS', styles: const PosStyles(bold: true)),
      );
      bytes.addAll(
        generator.row([
          PosColumn(text: 'Produto', width: 6),
          PosColumn(
            text: 'Qtd',
            width: 2,
            styles: const PosStyles(align: PosAlign.center),
          ),
          PosColumn(
            text: 'Valor',
            width: 4,
            styles: const PosStyles(align: PosAlign.right),
          ),
        ]),
      );
      bytes.addAll(
        generator.row([
          PosColumn(text: 'Coca Cola', width: 6),
          PosColumn(
            text: '2',
            width: 2,
            styles: const PosStyles(align: PosAlign.center),
          ),
          PosColumn(
            text: '€10.00',
            width: 4,
            styles: const PosStyles(align: PosAlign.right),
          ),
        ]),
      );
      bytes.addAll(generator.feed(2));

      // Corte final
      bytes.addAll(generator.cut());

      final success = await _printerPlugin.printRawBytes(bytes);
      if (!success && mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Falha ao enviar impressão!')),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text('Erro: \$e')));
      }
    }
  }

  Future<void> _printLongImage() async {
    if (_connectedDevice == null) return;

    try {
      final profile = await CapabilityProfile.load();
      final generator = Generator(
        _is80mm ? PaperSize.mm80 : PaperSize.mm58,
        profile,
      );
      final bytes = <int>[];

      bytes.addAll(generator.reset());

      final data = await rootBundle.load('assets/image2.png');
      final Uint8List imageBytes = data.buffer.asUint8List();

      bytes.addAll(
        generator.text(
          'IMPRESSAO COMPRIDA',
          styles: const PosStyles(align: PosAlign.center, bold: true),
        ),
      );

      // Definimos halftone: false para textos ficarem nítidos (threshold puro)
      // Passar width 576 garante que ele preencha toda a folha,
      // pois o Generator fará o clamp automático para 384 se a impressora for 58mm!
      bytes.addAll(
        generator.image(
          imageBytes,
          align: PosAlign.center,
          halftone: false,
          width: 576,
        ),
      );
      bytes.addAll(generator.feed(2));

      final success = await _printerPlugin.printRawBytes(bytes);
      if (!success && mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Falha ao enviar impressão longa!')),
        );
      }
    } catch (e) {
      debugPrint('Erro ao imprimir a imagem comprida: \$e');
    }
  }

  Future<void> _printAllFeaturesAsImage() async {
    if (_connectedDevice == null) return;

    try {
      final profile = await CapabilityProfile.load();
      final generator = ImageGenerator(
        _is80mm ? PaperSize.mm80 : PaperSize.mm58,
        profile,
      );

      // Teste 0: Imagem com Halftone
      generator.text('0. TESTE DE IMAGEM', styles: const PosStyles(bold: true));
      try {
        final data = await rootBundle.load('assets/image1.jpg');
        final Uint8List imageBytes = data.buffer.asUint8List();

        generator.image(imageBytes, align: PosAlign.center);
        generator.image(imageBytes, align: PosAlign.left, width: 150);
        generator.image(imageBytes, align: PosAlign.right, width: 50);
        generator.feed(2);
      } catch (e) {
        debugPrint('Erro ao carregar a imagem do asset: \$e');
      }

      // Cabeçalho
      generator.text(
        'TESTE COMPLETO DE FEATURES',
        styles: const PosStyles(
          align: PosAlign.center,
          bold: true,
          height: PosTextSize.size2,
          width: PosTextSize.size2,
        ),
      );
      generator.text(
        '================================',
        styles: const PosStyles(align: PosAlign.center),
      );

      // Teste 1: Acentuação e Euro
      generator.text(
        '1. ACENTUACAO E EURO',
        styles: const PosStyles(bold: true),
      );
      generator.text('Símbolo do Euro: €', styles: const PosStyles(bold: true));
      generator.text(
        'Acentuação: Ação, Coração, Avó',
        styles: const PosStyles(bold: true),
      );
      generator.text('Moedas: \$ £ ¥ ¢', styles: const PosStyles(bold: true));
      generator.feed(1);

      // Teste 2: Alinhamentos
      generator.text('2. ALINHAMENTO', styles: const PosStyles(bold: true));
      generator.text(
        'Alinhado à Esquerda',
        styles: const PosStyles(align: PosAlign.left, bold: true),
      );
      generator.text(
        'Centralizado',
        styles: const PosStyles(align: PosAlign.center, bold: true),
      );
      generator.text(
        'Alinhado à Direita',
        styles: const PosStyles(align: PosAlign.right, bold: true),
      );
      generator.feed(1);

      // Teste 3: Estilos (Negrito, Sublinhado, Reverso, Fontes)
      generator.text(
        '3. ESTILOS E FONTES',
        styles: const PosStyles(bold: true),
      );
      generator.text('Texto em Negrito', styles: const PosStyles(bold: true));
      generator.text(
        'Texto Sublinhado',
        styles: const PosStyles(underline: true, bold: true),
      );
      generator.text(
        'Texto Reverso',
        styles: const PosStyles(reverse: true, bold: true),
      );
      generator.text(
        'Fonte A (Padrão)',
        styles: const PosStyles(fontType: PosFontType.fontA, bold: true),
      );
      generator.text(
        'Fonte B (Menor)',
        styles: const PosStyles(fontType: PosFontType.fontB, bold: true),
      );
      generator.text(
        'Fonte C (Opcional)',
        styles: const PosStyles(fontType: PosFontType.fontC, bold: true),
      );
      // Restaura para Fonte A
      generator.text(
        'De volta para Fonte A',
        styles: const PosStyles(fontType: PosFontType.fontA, bold: true),
      );
      generator.feed(1);

      // Teste 4: Códigos de Barras
      generator.text(
        '4. CÓDIGOS DE BARRAS',
        styles: const PosStyles(bold: true),
      );
      final List<int> barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2];
      generator.barcode(Barcode.upcA(barData));
      generator.feed(1);

      // Teste 5: QR Code
      generator.text('5. QR CODE', styles: const PosStyles(bold: true));
      generator.qrcode('https://github.com/senuntech');
      generator.feed(2);

      // Teste 6: Colunas
      generator.text('6. COLUNAS', styles: const PosStyles(bold: true));
      generator.row([
        PosColumn(
          text: 'Produto',
          width: 6,
          styles: const PosStyles(bold: true),
        ),
        PosColumn(
          text: 'Qtd',
          width: 2,
          styles: const PosStyles(align: PosAlign.center, bold: true),
        ),
        PosColumn(
          text: 'Valor',
          width: 4,
          styles: const PosStyles(align: PosAlign.right, bold: true),
        ),
      ]);
      generator.row([
        PosColumn(
          text: 'Coca Cola',
          width: 6,
          styles: const PosStyles(bold: true),
        ),
        PosColumn(
          text: '2',
          width: 2,
          styles: const PosStyles(align: PosAlign.center, bold: true),
        ),
        PosColumn(
          text: '€10.00',
          width: 4,
          styles: const PosStyles(align: PosAlign.right, bold: true),
        ),
      ]);
      generator.feed(2);

      // O parâmetro threshold (padrão 127) controla o contraste.
      // Valores menores que 127 deixam mais claro, maiores deixam mais escuro (0-255).
      final bytes = await generator.buildImageBytes(threshold: 90);

      final success = await _printerPlugin.printRawBytes(bytes);
      if (!success && mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Falha ao enviar impressão!')),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text('Erro: \$e')));
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Utils POS Printer Demo'),
        actions: [
          Row(
            children: [
              const Text('58mm', style: TextStyle(fontSize: 12)),
              Switch(
                value: _is80mm,
                onChanged: (val) {
                  setState(() => _is80mm = val);
                },
                activeTrackColor: Colors.grey,
                activeThumbColor: Colors.white,
              ),
              const Text('80mm', style: TextStyle(fontSize: 12)),
              const SizedBox(width: 10),
            ],
          ),
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _isScanning ? null : _startScan,
          ),
        ],
      ),
      body: Column(
        children: [
          if (_isScanning) const LinearProgressIndicator(),
          if (_connectionStatus == PrinterStatus.reconnecting)
            const Text(
              'Reconectando impressora...',
              style: TextStyle(color: Colors.orange),
            ),
          Expanded(
            child: ListView.builder(
              itemCount: _devices.length,
              itemBuilder: (context, index) {
                final device = _devices[index];
                final isConnected = _connectedDevice?.address == device.address;

                return ListTile(
                  leading: const Icon(Icons.print),
                  title: Text(
                    device.name.isNotEmpty ? device.name : 'Unknown Device',
                  ),
                  subtitle: Text(device.address),
                  trailing: ElevatedButton(
                    onPressed: isConnected
                        ? _disconnect
                        : () => _connect(device),
                    child: Text(isConnected ? 'Desconectar' : 'Conectar'),
                  ),
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: _connectedDevice != null
          ? Column(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                FloatingActionButton.extended(
                  heroTag: 'btn1',
                  onPressed: _printLongImage,
                  icon: const Icon(Icons.receipt_long),
                  label: const Text('Imprimir Print Comprido'),
                ),
                const SizedBox(height: 10),
                FloatingActionButton.extended(
                  heroTag: 'btn2',
                  onPressed: _printAllFeatures,
                  icon: const Icon(Icons.print),
                  label: const Text('Imprimir Funcionalidades'),
                ),
                const SizedBox(height: 10),
                FloatingActionButton.extended(
                  heroTag: 'btn3',
                  onPressed: _printAllFeaturesAsImage,
                  icon: const Icon(Icons.image),
                  label: const Text('Imprimir como Imagem (€)'),
                ),
              ],
            )
          : null,
    );
  }
}
0
likes
0
points
251
downloads

Publisher

unverified uploader

Weekly Downloads

This package is a fork of flutter_esc_pos_utils customized for bluetooth_pos_printer.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

barcode_widget, flutter, gbk_codec, hex, image, qr_flutter, remove_diacritic, screenshot

More

Packages that depend on bluetooth_pos_printer_utils