x_printer_pck 0.0.4 copy "x_printer_pck: ^0.0.4" to clipboard
x_printer_pck: ^0.0.4 copied to clipboard

PlatformiOS

Flutter plugin for iOS Bluetooth thermal printer integration

example/lib/main.dart

import 'dart:developer';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:x_printer_pck/x_printer_pck.dart';
import 'package:x_printer_pck/x_printer_pck_method_channel.dart';

void main() {
  runApp(const MaterialApp(home: Scaffold(body: Base64PrinterExample())));
}

@immutable
class Base64PrinterExample extends StatelessWidget {
  const Base64PrinterExample({super.key});

  @override
  Widget build(BuildContext context) => const Base64PrinterPage();
}

@immutable
class Base64PrinterPage extends StatefulWidget {
  const Base64PrinterPage({super.key});

  @override
  _Base64PrinterPageState createState() => _Base64PrinterPageState();
}

class _Base64PrinterPageState extends State<Base64PrinterPage> {
  final List<BluetoothDevice> _devices = [];
  ConnectionStatus? _connectionStatus;
  final TextEditingController _base64Controller = TextEditingController();
  bool _isScanning = false;
  String _statusMessage = '';

  // Configuration variables
  PrinterCommandType _commandType = PrinterCommandType.tspl;
  int _printerWidth = 384;
  int _printerHeight = 600;
  int _rotation = 0;
  double _scale = 0.9;
  double _quality = 1.0;
  PrintAlignment _alignment = PrintAlignment.center;
  int _customX = 0;
  int _customY = 0;

  // Sample base64 images for testing
  final Map<String, String> _sampleImages = {
    'Small Test Image':
        'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==',
    'Data URL Format':
        'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==',
    'QR Code Sample':
        '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=',
  };

  @override
  void initState() {
    super.initState();

    XPrinterPck.initialize();
    // Set up event listeners
    XPrinterPck.onScanResults = (devices) {
      setState(() {
        _devices.clear();
        _devices.addAll(devices);
      });
    };

    XPrinterPck.onConnectionChanged = (status) {
      setState(() {
        _connectionStatus = status;
        _statusMessage = 'Printer ${status.name} is ${status.status}';
        if (status.error.isNotEmpty) {
          _statusMessage += ' (${status.error})';
        }
      });
    };
  }

  void _scanDevices() async {
    setState(() {
      _isScanning = true;
      _devices.clear();
      _statusMessage = 'Scanning for devices...';
    });

    try {
      await XPrinterPck.scanDevices();

      // Auto-stop scan after 10 seconds
      Future.delayed(const Duration(seconds: 10), () {
        if (_isScanning) {
          _stopScan();
        }
      });
    } catch (e) {
      setState(() {
        _statusMessage = 'Error starting scan: $e';
        _isScanning = false;
      });
    }
  }

  void _stopScan() async {
    try {
      await XPrinterPck.stopScan();
    } catch (e) {
      setState(() {
        _statusMessage = 'Error stopping scan: $e';
      });
    } finally {
      setState(() {
        _isScanning = false;
      });
    }
  }

  void _connectToDevice(int index) async {
    if (index < 0 || index >= _devices.length) return;

    try {
      setState(() {
        _statusMessage = 'Connecting to ${_devices[index].name}...';
      });
      await XPrinterPck.connectDevice(index);
    } catch (e) {
      setState(() {
        _statusMessage = 'Error connecting: $e';
      });
    }
  }

  void _disconnectDevice() async {
    try {
      await XPrinterPck.disconnectDevice();
    } catch (e) {
      setState(() {
        _statusMessage = 'Error disconnecting: $e';
      });
    }
  }

  // Print Base64 image with current configuration
  Future<void> _printBase64Image() async {
    if (_base64Controller.text.isEmpty) {
      setState(() {
        _statusMessage = 'Please enter a Base64 string';
      });
      return;
    }

    try {
      setState(() {
        _statusMessage = 'Printing Base64 image...';
      });

      bool result = await XPrinterPck.printImageBase64(
        _base64Controller.text,
        commandType: _commandType,
        printerWidth: _printerWidth,
        printerHeight: _printerHeight,
        rotation: _rotation,
        scale: _scale,
        quality: _quality,
        alignment: _alignment,
        x: _alignment == PrintAlignment.custom ? _customX : null,
        y: _alignment == PrintAlignment.custom ? _customY : null,
      );

      setState(() {
        _statusMessage = result
            ? 'Base64 image printed successfully!'
            : 'Failed to print Base64 image';
      });
    } catch (e) {
      setState(() {
        _statusMessage = 'Error printing Base64 image: $e';
      });
      print('Print error: $e');
    }
  }

  // Convert image file to Base64 and print
  Future<void> _pickImageAndConvertToBase64() async {
    try {
      final ImagePicker picker = ImagePicker();
      final XFile? image = await picker.pickImage(source: ImageSource.gallery);

      if (image != null) {
        final bytes = await image.readAsBytes();
        final base64String = base64Encode(bytes);

        setState(() {
          _base64Controller.text = base64String;
          _statusMessage = 'Image converted to Base64. Ready to print!';
        });
      }
    } catch (e) {
      setState(() {
        _statusMessage = 'Error converting image to Base64: $e';
      });
    }
  }

  // Test different configurations
  Future<void> _testDifferentConfigurations() async {
    if (_base64Controller.text.isEmpty) {
      setState(() {
        _statusMessage = 'Please enter a Base64 string first';
      });
      return;
    }

    final configurations = [
      {
        'name': 'Center Aligned',
        'alignment': PrintAlignment.center,
        'scale': 0.8,
      },
      {
        'name': 'Top Left',
        'alignment': PrintAlignment.topLeft,
        'scale': 0.6,
      },
      {
        'name': 'Rotated 90°',
        'alignment': PrintAlignment.center,
        'rotation': 90,
        'scale': 0.7,
      },
      {
        'name': 'High Quality',
        'alignment': PrintAlignment.center,
        'quality': 1.0,
        'scale': 0.9,
      },
      {
        'name': 'Custom Position',
        'alignment': PrintAlignment.custom,
        'x': 100,
        'y': 50,
        'scale': 0.5,
      },
    ];

    for (var config in configurations) {
      setState(() {
        _statusMessage = 'Printing: ${config['name']}...';
      });

      try {
        await XPrinterPck.printImageBase64(
          _base64Controller.text,
          commandType: _commandType,
          printerWidth: _printerWidth,
          printerHeight: _printerHeight,
          rotation: int.parse(config['rotation']?.toString() ?? '0'),
          scale: double.tryParse(config['scale']?.toString() ?? '0') ?? 0.9,
          quality: double.tryParse(config['quality']?.toString() ?? '0') ?? 1.0,
          // alignment: config['alignment'] ?? PrintAlignment.center,
          x: int.tryParse(config['x']?.toString() ?? '0'),
          y: int.tryParse(config['y']?.toString() ?? '0'),
          // config['y'],
        );

        // Wait between prints
        await Future.delayed(const Duration(seconds: 2));
      } catch (e) {
        setState(() {
          _statusMessage = 'Error in ${config['name']}: $e';
        });
        return;
      }
    }

    setState(() {
      _statusMessage = 'All test configurations completed!';
    });
  }

  // Performance test
  Future<void> _performanceTest() async {
    if (_base64Controller.text.isEmpty) {
      setState(() {
        _statusMessage = 'Please enter a Base64 string first';
      });
      return;
    }

    final stopwatch = Stopwatch()..start();

    try {
      setState(() {
        _statusMessage = 'Running performance test...';
      });

      await XPrinterPck.printImageBase64(
        _base64Controller.text,
        commandType: _commandType,
        printerWidth: _printerWidth,
        printerHeight: _printerHeight,
        quality: 1.0,
      );

      stopwatch.stop();
      setState(() {
        _statusMessage =
            'Performance test completed in ${stopwatch.elapsedMilliseconds}ms';
      });
    } catch (e) {
      stopwatch.stop();
      setState(() {
        _statusMessage =
            'Performance test failed: $e (${stopwatch.elapsedMilliseconds}ms)';
      });
    }
  }

  void _getPrinterStatus() async {
    try {
      final status = await XPrinterPck.getPrinterStatus();
      setState(() {
        _statusMessage =
            'Printer Status: ${status.message} (Code: 0x${status.code.toRadixString(16).toUpperCase()})';
      });
    } catch (e) {
      setState(() {
        _statusMessage = 'Error getting printer status: $e';
      });
    }
  }

  @override
  void setState(VoidCallback fn) {
    super.setState(fn);

    log(_statusMessage);
  }

  @override
  Widget build(BuildContext context) {
    XPrinterPck.initialize();

    final bool isConnected = _connectionStatus?.isConnected ?? false;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Base64 Printer Test'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
        actions: [
          if (isConnected)
            IconButton(
              icon: const Icon(Icons.info_outline),
              onPressed: _getPrinterStatus,
              tooltip: 'Get Printer Status',
            ),
        ],
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Status Card
              Card(
                color: isConnected ? Colors.green[50] : Colors.red[50],
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    children: [
                      Icon(
                        isConnected ? Icons.check_circle : Icons.error,
                        color: isConnected ? Colors.green : Colors.red,
                        size: 32,
                      ),
                      const SizedBox(height: 8),
                      Text(
                        isConnected
                            ? 'Connected to ${_connectionStatus?.name}'
                            : 'Not Connected',
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 16,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        _statusMessage,
                        textAlign: TextAlign.center,
                        style: const TextStyle(fontSize: 14),
                      ),
                    ],
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // Device Scanning Section
              if (!isConnected) ...[
                Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      children: [
                        Text(
                          'Bluetooth Devices',
                          style: Theme.of(context).textTheme.titleLarge,
                        ),
                        const SizedBox(height: 16),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            Expanded(
                              child: ElevatedButton.icon(
                                icon: Icon(
                                    _isScanning ? Icons.stop : Icons.search),
                                label: Text(
                                    _isScanning ? 'Stop Scan' : 'Scan Devices'),
                                onPressed:
                                    _isScanning ? _stopScan : _scanDevices,
                                style: ElevatedButton.styleFrom(
                                  backgroundColor:
                                      _isScanning ? Colors.red : Colors.blue,
                                  foregroundColor: Colors.white,
                                ),
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 16),
                        if (_devices.isEmpty && _isScanning)
                          const Center(child: CircularProgressIndicator())
                        else if (_devices.isEmpty && !_isScanning)
                          const Center(
                              child: Text('No devices found. Try scanning.'))
                        else
                          ListView.separated(
                            shrinkWrap: true,
                            physics: const NeverScrollableScrollPhysics(),
                            itemCount: _devices.length,
                            separatorBuilder: (context, index) =>
                                const Divider(),
                            itemBuilder: (context, index) {
                              final device = _devices[index];
                              return ListTile(
                                leading: const Icon(Icons.bluetooth,
                                    color: Colors.blue),
                                title: Text(device.name),
                                subtitle: Text('RSSI: ${device.rssi} dBm'),
                                trailing: ElevatedButton(
                                  onPressed: () => _connectToDevice(index),
                                  child: const Text('Connect'),
                                ),
                              );
                            },
                          ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 16),
              ],

              // Disconnect Button
              if (isConnected) ...[
                ElevatedButton.icon(
                  icon: const Icon(Icons.bluetooth_disabled),
                  label: const Text('Disconnect'),
                  onPressed: _disconnectDevice,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red,
                    foregroundColor: Colors.white,
                  ),
                ),
                const SizedBox(height: 16),
              ],

              // Configuration Panel
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Printer Configuration',
                        style: Theme.of(context).textTheme.titleLarge,
                      ),
                      const SizedBox(height: 16),

                      // Command Type
                      Row(
                        children: [
                          const Text('Command Type: '),
                          const SizedBox(width: 8),
                          DropdownButton<PrinterCommandType>(
                            value: _commandType,
                            onChanged: (PrinterCommandType? value) {
                              if (value != null) {
                                setState(() {
                                  _commandType = value;
                                });
                              }
                            },
                            items: PrinterCommandType.values
                                .map((PrinterCommandType type) {
                              return DropdownMenuItem<PrinterCommandType>(
                                value: type,
                                child: Text(type.name.toUpperCase()),
                              );
                            }).toList(),
                          ),
                        ],
                      ),

                      // Printer Size
                      Row(
                        children: [
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                const Text('Width (dots)'),
                                Slider(
                                  value: _printerWidth.toDouble(),
                                  min: 200,
                                  max: 1000,
                                  divisions: 16,
                                  label: _printerWidth.toString(),
                                  onChanged: (double value) {
                                    setState(() {
                                      _printerWidth = value.round();
                                    });
                                  },
                                ),
                              ],
                            ),
                          ),
                          const SizedBox(width: 16),
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                const Text('Height (dots)'),
                                Slider(
                                  value: _printerHeight.toDouble(),
                                  min: 200,
                                  max: 1000,
                                  divisions: 16,
                                  label: _printerHeight.toString(),
                                  onChanged: (double value) {
                                    setState(() {
                                      _printerHeight = value.round();
                                    });
                                  },
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),

                      // Scale and Quality
                      Row(
                        children: [
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text('Scale: ${_scale.toStringAsFixed(2)}'),
                                Slider(
                                  value: _scale,
                                  min: 0.1,
                                  max: 1.0,
                                  divisions: 9,
                                  onChanged: (double value) {
                                    setState(() {
                                      _scale = value;
                                    });
                                  },
                                ),
                              ],
                            ),
                          ),
                          const SizedBox(width: 16),
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text('Quality: ${_quality.toStringAsFixed(2)}'),
                                Slider(
                                  value: _quality,
                                  min: 0.1,
                                  max: 1.0,
                                  divisions: 9,
                                  onChanged: (double value) {
                                    setState(() {
                                      _quality = value;
                                    });
                                  },
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),

                      // Rotation
                      Row(
                        children: [
                          const Text('Rotation: '),
                          const SizedBox(width: 8),
                          DropdownButton<int>(
                            value: _rotation,
                            onChanged: (int? value) {
                              if (value != null) {
                                setState(() {
                                  _rotation = value;
                                });
                              }
                            },
                            items: [0, 90, 180, 270].map((int rotation) {
                              return DropdownMenuItem<int>(
                                value: rotation,
                                child: Text('${rotation}°'),
                              );
                            }).toList(),
                          ),
                        ],
                      ),

                      // Alignment
                      Row(
                        children: [
                          const Text('Alignment: '),
                          const SizedBox(width: 8),
                          DropdownButton<PrintAlignment>(
                            value: _alignment,
                            onChanged: (PrintAlignment? value) {
                              if (value != null) {
                                setState(() {
                                  _alignment = value;
                                });
                              }
                            },
                            items: PrintAlignment.values
                                .map((PrintAlignment alignment) {
                              return DropdownMenuItem<PrintAlignment>(
                                value: alignment,
                                child: Text(alignment.name),
                              );
                            }).toList(),
                          ),
                        ],
                      ),

                      // Custom Position (only if alignment is custom)
                      if (_alignment == PrintAlignment.custom)
                        Row(
                          children: [
                            Expanded(
                              child: TextField(
                                decoration: const InputDecoration(
                                  labelText: 'X Position',
                                  border: OutlineInputBorder(),
                                ),
                                keyboardType: TextInputType.number,
                                onChanged: (String value) {
                                  _customX = int.tryParse(value) ?? 0;
                                },
                              ),
                            ),
                            const SizedBox(width: 16),
                            Expanded(
                              child: TextField(
                                decoration: const InputDecoration(
                                  labelText: 'Y Position',
                                  border: OutlineInputBorder(),
                                ),
                                keyboardType: TextInputType.number,
                                onChanged: (String value) {
                                  _customY = int.tryParse(value) ?? 0;
                                },
                              ),
                            ),
                          ],
                        ),
                    ],
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // Base64 Input Section
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      Text(
                        'Base64 Image Input',
                        style: Theme.of(context).textTheme.titleLarge,
                      ),
                      const SizedBox(height: 16),

                      // Sample images buttons
                      Wrap(
                        spacing: 8,
                        children: _sampleImages.keys.map((String name) {
                          return ElevatedButton(
                            onPressed: () {
                              setState(() {
                                _base64Controller.text = _sampleImages[name]!;
                                _statusMessage = 'Loaded sample: $name';
                              });
                            },
                            child: Text(name),
                          );
                        }).toList(),
                      ),

                      const SizedBox(height: 16),

                      // Base64 input field
                      TextField(
                        controller: _base64Controller,
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          labelText: 'Base64 String',
                          hintText: 'Paste Base64 image data here...',
                        ),
                        maxLines: 5,
                      ),

                      const SizedBox(height: 16),

                      // Action buttons
                      Row(
                        children: [
                          Expanded(
                            child: ElevatedButton.icon(
                              icon: const Icon(Icons.image),
                              label: const Text('Pick & Convert'),
                              onPressed: _pickImageAndConvertToBase64,
                            ),
                          ),
                          const SizedBox(width: 8),
                          Expanded(
                            child: ElevatedButton.icon(
                              icon: const Icon(Icons.clear),
                              label: const Text('Clear'),
                              onPressed: () {
                                setState(() {
                                  _base64Controller.clear();
                                  _statusMessage = 'Base64 input cleared';
                                });
                              },
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // Print Actions
              if (isConnected) ...[
                Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      children: [
                        Text(
                          'Print Actions',
                          style: Theme.of(context).textTheme.titleLarge,
                        ),
                        const SizedBox(height: 16),

                        // Main print button
                        ElevatedButton.icon(
                          icon: const Icon(Icons.print),
                          label: const Text('Print Base64 Image'),
                          onPressed: _base64Controller.text.isNotEmpty
                              ? _printBase64Image
                              : null,
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.green,
                            foregroundColor: Colors.white,
                            padding: const EdgeInsets.all(16),
                          ),
                        ),

                        const SizedBox(height: 12),

                        // Test buttons
                        Row(
                          children: [
                            Expanded(
                              child: ElevatedButton.icon(
                                icon: const Icon(Icons.science),
                                label: const Text('Test Configs'),
                                onPressed: _base64Controller.text.isNotEmpty
                                    ? _testDifferentConfigurations
                                    : null,
                              ),
                            ),
                            const SizedBox(width: 8),
                            Expanded(
                              child: ElevatedButton.icon(
                                icon: const Icon(Icons.speed),
                                label: const Text('Performance'),
                                onPressed: _base64Controller.text.isNotEmpty
                                    ? _performanceTest
                                    : null,
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
              ] else ...[
                const Card(
                  child: Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Center(
                      child: Text(
                        'Please connect to a printer to enable printing functions',
                        style: TextStyle(
                          fontSize: 16,
                          color: Colors.grey,
                        ),
                        textAlign: TextAlign.center,
                      ),
                    ),
                  ),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _base64Controller.dispose();
    super.dispose();
  }
}
0
likes
160
points
2
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for iOS Bluetooth thermal printer integration

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on x_printer_pck

Packages that implement x_printer_pck