This commit is contained in:
Developer
2026-05-18 17:52:09 +08:00
commit cae04eead5
62 changed files with 11230 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
/// Multi-byte character encoding for ticket printing.
///
/// Maps to the autoreplyprint AAR SDK CP_MultiByteEncoding constants.
/// The printer uses the specified encoding to interpret received data.
///
/// Note: value 2 is reserved/unused per SDK specification. The sequence
/// jumps from UTF8(1) to BIG5(3).
enum MultiByteEncoding {
/// GBK encoding (Simplified Chinese)
gbk(0),
/// UTF-8 encoding (Unicode)
utf8(1),
// Note: value 2 is reserved/unused per SDK specification
/// BIG5 encoding (Traditional Chinese)
big5(3),
/// Shift-JIS encoding (Japanese)
shiftJis(4),
/// EUC-KR encoding (Korean)
eucKr(5);
/// The integer value matching the SDK constant.
final int value;
const MultiByteEncoding(this.value);
}

View File

@@ -0,0 +1,17 @@
/// Alignment for ticket printing.
///
/// Maps to the autoreplyprint AAR SDK CP_Pos_Alignment constants.
enum PrinterAlignment {
/// Left alignment
left(0),
/// Center alignment
center(1),
/// Right alignment
right(2);
/// The integer value matching the SDK constant.
final int value;
const PrinterAlignment(this.value);
}

View File

@@ -0,0 +1,20 @@
/// Flow control setting for serial port communication.
///
/// Maps to the autoreplyprint AAR SDK flow control constants.
enum SerialFlowControl {
/// No flow control
none(0),
/// XON/XOFF software flow control
xonXoff(1),
/// RTS/CTS hardware flow control
rtsCts(2),
/// DTR/DSR hardware flow control
dtrDsr(3);
/// The integer value matching the AAR SDK constant.
final int value;
const SerialFlowControl(this.value);
}

View File

@@ -0,0 +1,23 @@
/// Parity setting for serial port communication.
///
/// Maps to the autoreplyprint AAR SDK parity constants.
enum SerialParity {
/// No parity
none(0),
/// Odd parity
odd(1),
/// Even parity
even(2),
/// Mark parity
mark(3),
/// Space parity
space(4);
/// The integer value matching the AAR SDK constant.
final int value;
const SerialParity(this.value);
}

View File

@@ -0,0 +1,17 @@
/// Stop bits setting for serial port communication.
///
/// Maps to the autoreplyprint AAR SDK stop bits constants.
enum SerialStopBits {
/// 1 stop bit
one(0),
/// 1.5 stop bits
onePointFive(1),
/// 2 stop bits
two(2);
/// The integer value matching the AAR SDK constant.
final int value;
const SerialStopBits(this.value);
}

View File

@@ -0,0 +1,24 @@
/// Custom exception for printer operations.
///
/// Encapsulates structured error information from the native layer,
/// including error code, message, and optional details.
class PrinterException implements Exception {
/// Machine-readable error code (e.g., 'INVALID_ARGUMENT', 'PORT_OPEN_FAILED').
final String code;
/// Human-readable error message.
final String message;
/// Additional error details (may be null).
final dynamic details;
/// Creates a [PrinterException] with the given [code] and [message].
const PrinterException({
required this.code,
required this.message,
this.details,
});
@override
String toString() => 'PrinterException($code): $message';
}

View File

@@ -0,0 +1,44 @@
/// Close callback type for [PrinterPortHandle].
typedef ClosePortCallback = Future<bool> Function(int handle);
/// Represents an open printer port handle.
///
/// Wraps an integer handle and provides a [close] method to release
/// the underlying native port resource. Once closed, the handle
/// becomes invalid.
class PrinterPortHandle {
/// The integer handle returned by the native layer.
final int handle;
/// Callback to close the port. If null, close() is a no-op.
final ClosePortCallback? _closeCallback;
/// Whether the port is still valid (not closed).
bool _isValid;
/// Creates a [PrinterPortHandle] with the given [handle] and optional [closeCallback].
PrinterPortHandle({
required this.handle,
ClosePortCallback? closeCallback,
}) : _closeCallback = closeCallback,
_isValid = true;
/// Returns true if the port is still open and not closed.
bool get isValid => _isValid;
/// Closes the port and marks the handle as invalid.
///
/// Subsequent calls to [close] are no-ops if the port is already closed.
Future<void> close() async {
if (!_isValid) {
return;
}
_isValid = false;
if (_closeCallback != null) {
await _closeCallback(handle);
}
}
@override
String toString() => 'PrinterPortHandle(handle: $handle, valid: $_isValid)';
}

227
lib/printer.dart Normal file
View File

@@ -0,0 +1,227 @@
import 'enums/multi_byte_encoding.dart';
import 'enums/printer_alignment.dart';
import 'enums/serial_flow_control.dart';
import 'enums/serial_parity.dart';
import 'enums/serial_stop_bits.dart';
import 'models/printer_port_handle.dart';
import 'printer_platform_interface.dart';
/// Main entry point for the printer plugin.
///
/// Provides a thin wrapper around [PrinterPlatform] for port management
/// operations. All methods delegate to the platform interface.
class Printer {
/// Returns the platform version string.
Future<String?> getPlatformVersion() {
return PrinterPlatform.instance.getPlatformVersion();
}
/// Opens a serial (COM) port with the specified parameters.
///
/// Returns an integer handle on success.
/// Throws [PrinterException] on failure.
///
/// Example:
/// ```dart
/// final printer = Printer();
/// final handle = await printer.openComPort(
/// portName: '/dev/ttyS0',
/// baudRate: 115200,
/// );
/// ```
Future<int> openComPort({
required String portName,
required int baudRate,
int dataBits = 8,
SerialParity parity = SerialParity.none,
SerialStopBits stopBits = SerialStopBits.one,
SerialFlowControl flowControl = SerialFlowControl.none,
bool autoReplyMode = true,
}) {
return PrinterPlatform.instance.openComPort(
portName: portName,
baudRate: baudRate,
dataBits: dataBits,
parity: parity,
stopBits: stopBits,
flowControl: flowControl,
autoReplyMode: autoReplyMode,
);
}
/// Opens a USB port with the specified parameters.
///
/// Returns an integer handle on success.
/// Throws [PrinterException] on failure.
Future<int> openUsbPort({
required String portName,
bool autoReplyMode = true,
}) {
return PrinterPlatform.instance.openUsbPort(
portName: portName,
autoReplyMode: autoReplyMode,
);
}
/// Closes a port by its integer handle.
///
/// Returns true if successfully closed, false otherwise.
Future<bool> closePort(int handle) {
return PrinterPlatform.instance.closePort(handle);
}
/// Checks if a port is currently opened.
///
/// Returns true if the port is open, false otherwise.
Future<bool> isPortOpened(int handle) {
return PrinterPlatform.instance.isPortOpened(handle);
}
/// Enumerates available serial (COM) ports.
///
/// Returns a list of port name strings.
Future<List<String>> enumComPorts() {
return PrinterPlatform.instance.enumComPorts();
}
/// Enumerates available USB ports.
///
/// Returns a list of port name strings.
Future<List<String>> enumUsbPorts() {
return PrinterPlatform.instance.enumUsbPorts();
}
/// Opens a serial port and returns a [PrinterPortHandle] for safe resource management.
///
/// The returned handle can be closed via [PrinterPortHandle.close].
Future<PrinterPortHandle> openComPortWithHandle({
required String portName,
required int baudRate,
int dataBits = 8,
SerialParity parity = SerialParity.none,
SerialStopBits stopBits = SerialStopBits.one,
SerialFlowControl flowControl = SerialFlowControl.none,
bool autoReplyMode = true,
}) async {
final handle = await openComPort(
portName: portName,
baudRate: baudRate,
dataBits: dataBits,
parity: parity,
stopBits: stopBits,
flowControl: flowControl,
autoReplyMode: autoReplyMode,
);
return PrinterPortHandle(
handle: handle,
closeCallback: closePort,
);
}
/// Opens a USB port and returns a [PrinterPortHandle] for safe resource management.
///
/// The returned handle can be closed via [PrinterPortHandle.close].
Future<PrinterPortHandle> openUsbPortWithHandle({
required String portName,
bool autoReplyMode = true,
}) async {
final handle = await openUsbPort(
portName: portName,
autoReplyMode: autoReplyMode,
);
return PrinterPortHandle(
handle: handle,
closeCallback: closePort,
);
}
/// Sets the printer to multi-byte encoding mode.
///
/// Returns true on success.
Future<bool> setMultiByteMode(int handle) {
return PrinterPlatform.instance.setMultiByteMode(handle);
}
/// Sets the multi-byte character encoding.
///
/// Returns true on success.
Future<bool> setMultiByteEncoding(int handle, MultiByteEncoding encoding) {
return PrinterPlatform.instance.setMultiByteEncoding(handle, encoding);
}
/// Prints text using UTF-8 encoding.
///
/// The caller should ensure multi-byte mode is set to UTF-8 before calling:
/// ```dart
/// await printer.setMultiByteMode(handle);
/// await printer.setMultiByteEncoding(handle, MultiByteEncoding.utf8);
/// await printer.printText(handle, 'Hello 中文');
/// ```
///
/// Returns true on success.
Future<bool> printText(int handle, String text) {
return PrinterPlatform.instance.printText(handle, text);
}
/// Sets text alignment.
///
/// Returns true on success.
Future<bool> setAlignment(int handle, PrinterAlignment alignment) {
return PrinterPlatform.instance.setAlignment(handle, alignment);
}
/// Sets text scale (width and height magnification).
///
/// Both scales must be between 1 and 8.
/// Returns true on success.
Future<bool> setTextScale(int handle, {required int widthScale, required int heightScale}) {
return PrinterPlatform.instance.setTextScale(
handle,
widthScale: widthScale,
heightScale: heightScale,
);
}
/// Sets text bold on or off.
///
/// Returns true on success.
Future<bool> setTextBold(int handle, bool bold) {
return PrinterPlatform.instance.setTextBold(handle, bold);
}
/// Sets text underline level.
///
/// 0 = no underline, 1 = 1-dot underline, 2 = 2-dot underline.
/// Returns true on success.
Future<bool> setTextUnderline(int handle, int underline) {
return PrinterPlatform.instance.setTextUnderline(handle, underline);
}
/// Feeds paper by specified number of lines.
///
/// Returns true on success.
Future<bool> feedLine(int handle, int numLines) {
return PrinterPlatform.instance.feedLine(handle, numLines);
}
/// Feeds paper by specified number of dots.
///
/// Returns true on success.
Future<bool> feedDot(int handle, int numDots) {
return PrinterPlatform.instance.feedDot(handle, numDots);
}
/// Performs a half cut of the paper.
///
/// Returns true on success.
Future<bool> halfCutPaper(int handle) {
return PrinterPlatform.instance.halfCutPaper(handle);
}
/// Performs a full cut of the paper.
///
/// Returns true on success.
Future<bool> fullCutPaper(int handle) {
return PrinterPlatform.instance.fullCutPaper(handle);
}
}

View File

@@ -0,0 +1,339 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'enums/multi_byte_encoding.dart';
import 'enums/printer_alignment.dart';
import 'enums/serial_flow_control.dart';
import 'enums/serial_parity.dart';
import 'enums/serial_stop_bits.dart';
import 'models/printer_exception.dart';
import 'printer_platform_interface.dart';
/// An implementation of [PrinterPlatform] that uses method channels.
class MethodChannelPrinter extends PrinterPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('printer');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>(
'getPlatformVersion',
);
return version;
}
@override
Future<int> openComPort({
required String portName,
required int baudRate,
int dataBits = 8,
SerialParity parity = SerialParity.none,
SerialStopBits stopBits = SerialStopBits.one,
SerialFlowControl flowControl = SerialFlowControl.none,
bool autoReplyMode = true,
}) async {
try {
final result = await methodChannel.invokeMethod<int>('openComPort', {
'portName': portName,
'baudRate': baudRate,
'dataBits': dataBits,
'parity': parity.value,
'stopBits': stopBits.value,
'flowControl': flowControl.value,
'autoReplyMode': autoReplyMode ? 1 : 0,
});
return result!;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to open COM port',
details: e.details,
);
}
}
@override
Future<int> openUsbPort({
required String portName,
bool autoReplyMode = true,
}) async {
try {
final result = await methodChannel.invokeMethod<int>('openUsbPort', {
'portName': portName,
'autoReplyMode': autoReplyMode ? 1 : 0,
});
return result!;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to open USB port',
details: e.details,
);
}
}
@override
Future<bool> closePort(int handle) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'closePort',
{'handle': handle},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to close port',
details: e.details,
);
}
}
@override
Future<bool> isPortOpened(int handle) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'isPortOpened',
{'handle': handle},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to check port status',
details: e.details,
);
}
}
@override
Future<List<String>> enumComPorts() async {
try {
final result = await methodChannel.invokeMethod<List>('enumComPorts');
return (result ?? []).map((e) => e.toString()).toList();
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to enumerate COM ports',
details: e.details,
);
}
}
@override
Future<List<String>> enumUsbPorts() async {
try {
final result = await methodChannel.invokeMethod<List>('enumUsbPorts');
return (result ?? []).map((e) => e.toString()).toList();
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to enumerate USB ports',
details: e.details,
);
}
}
/// Sets the printer to multi-byte encoding mode.
@override
Future<bool> setMultiByteMode(int handle) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'setMultiByteMode',
{'handle': handle},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to set multi-byte mode',
details: e.details,
);
}
}
/// Sets the multi-byte character encoding.
@override
Future<bool> setMultiByteEncoding(int handle, MultiByteEncoding encoding) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'setMultiByteEncoding',
{'handle': handle, 'encoding': encoding.value},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to set multi-byte encoding',
details: e.details,
);
}
}
/// Prints text using UTF-8 encoding.
@override
Future<bool> printText(int handle, String text) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'printText',
{'handle': handle, 'text': text},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to print text',
details: e.details,
);
}
}
/// Sets text alignment.
@override
Future<bool> setAlignment(int handle, PrinterAlignment alignment) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'setAlignment',
{'handle': handle, 'alignment': alignment.value},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to set alignment',
details: e.details,
);
}
}
/// Sets text scale (width and height magnification).
@override
Future<bool> setTextScale(int handle, {required int widthScale, required int heightScale}) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'setTextScale',
{
'handle': handle,
'widthScale': widthScale,
'heightScale': heightScale,
},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to set text scale',
details: e.details,
);
}
}
/// Sets text bold on or off.
@override
Future<bool> setTextBold(int handle, bool bold) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'setTextBold',
{'handle': handle, 'bold': bold ? 1 : 0},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to set text bold',
details: e.details,
);
}
}
/// Sets text underline level.
@override
Future<bool> setTextUnderline(int handle, int underline) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'setTextUnderline',
{'handle': handle, 'underline': underline},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to set text underline',
details: e.details,
);
}
}
/// Feeds paper by specified number of lines.
@override
Future<bool> feedLine(int handle, int numLines) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'feedLine',
{'handle': handle, 'numLines': numLines},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to feed line',
details: e.details,
);
}
}
/// Feeds paper by specified number of dots.
@override
Future<bool> feedDot(int handle, int numDots) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'feedDot',
{'handle': handle, 'numDots': numDots},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to feed dot',
details: e.details,
);
}
}
/// Performs a half cut of the paper.
@override
Future<bool> halfCutPaper(int handle) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'halfCutPaper',
{'handle': handle},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to half cut paper',
details: e.details,
);
}
}
/// Performs a full cut of the paper.
@override
Future<bool> fullCutPaper(int handle) async {
try {
final result = await methodChannel.invokeMethod<bool>(
'fullCutPaper',
{'handle': handle},
);
return result ?? false;
} on PlatformException catch (e) {
throw PrinterException(
code: e.code,
message: e.message ?? 'Failed to full cut paper',
details: e.details,
);
}
}
}

View File

@@ -0,0 +1,173 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'enums/multi_byte_encoding.dart';
import 'enums/printer_alignment.dart';
import 'enums/serial_flow_control.dart';
import 'enums/serial_parity.dart';
import 'enums/serial_stop_bits.dart';
import 'printer_method_channel.dart';
/// Platform interface for printer operations.
///
/// Concrete implementations (e.g., [MethodChannelPrinter]) provide
/// the actual platform-specific behavior.
abstract class PrinterPlatform extends PlatformInterface {
/// Constructs a PrinterPlatform.
PrinterPlatform() : super(token: _token);
static final Object _token = Object();
static PrinterPlatform _instance = MethodChannelPrinter();
/// The default instance of [PrinterPlatform] to use.
///
/// Defaults to [MethodChannelPrinter].
static PrinterPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [PrinterPlatform] when
/// they register themselves.
static set instance(PrinterPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// Returns the platform version string.
Future<String?> getPlatformVersion() {
throw UnimplementedError('getPlatformVersion() has not been implemented.');
}
/// Opens a serial (COM) port with the specified parameters.
///
/// Returns an integer handle on success.
/// Throws [PrinterException] on failure.
Future<int> openComPort({
required String portName,
required int baudRate,
int dataBits = 8,
SerialParity parity = SerialParity.none,
SerialStopBits stopBits = SerialStopBits.one,
SerialFlowControl flowControl = SerialFlowControl.none,
bool autoReplyMode = true,
}) {
throw UnimplementedError('openComPort() has not been implemented.');
}
/// Opens a USB port with the specified parameters.
///
/// Returns an integer handle on success.
/// Throws [PrinterException] on failure.
Future<int> openUsbPort({
required String portName,
bool autoReplyMode = true,
}) {
throw UnimplementedError('openUsbPort() has not been implemented.');
}
/// Closes a port by its integer handle.
///
/// Returns true if successfully closed, false otherwise.
Future<bool> closePort(int handle) {
throw UnimplementedError('closePort() has not been implemented.');
}
/// Checks if a port is currently opened.
///
/// Returns true if the port is open, false otherwise.
Future<bool> isPortOpened(int handle) {
throw UnimplementedError('isPortOpened() has not been implemented.');
}
/// Enumerates available serial (COM) ports.
///
/// Returns a list of port name strings.
Future<List<String>> enumComPorts() {
throw UnimplementedError('enumComPorts() has not been implemented.');
}
/// Enumerates available USB ports.
///
/// Returns a list of port name strings.
Future<List<String>> enumUsbPorts() {
throw UnimplementedError('enumUsbPorts() has not been implemented.');
}
/// Sets the printer to multi-byte encoding mode.
///
/// Returns true on success.
Future<bool> setMultiByteMode(int handle) {
throw UnimplementedError('setMultiByteMode() has not been implemented.');
}
/// Sets the multi-byte character encoding.
///
/// Returns true on success.
Future<bool> setMultiByteEncoding(int handle, MultiByteEncoding encoding) {
throw UnimplementedError('setMultiByteEncoding() has not been implemented.');
}
/// Prints text using UTF-8 encoding.
///
/// Returns true on success.
Future<bool> printText(int handle, String text) {
throw UnimplementedError('printText() has not been implemented.');
}
/// Sets text alignment.
///
/// Returns true on success.
Future<bool> setAlignment(int handle, PrinterAlignment alignment) {
throw UnimplementedError('setAlignment() has not been implemented.');
}
/// Sets text scale (width and height magnification).
///
/// Both scales must be between 1 and 8.
/// Returns true on success.
Future<bool> setTextScale(int handle, {required int widthScale, required int heightScale}) {
throw UnimplementedError('setTextScale() has not been implemented.');
}
/// Sets text bold on or off.
///
/// Returns true on success.
Future<bool> setTextBold(int handle, bool bold) {
throw UnimplementedError('setTextBold() has not been implemented.');
}
/// Sets text underline level.
///
/// 0 = no underline, 1 = 1-dot underline, 2 = 2-dot underline.
/// Returns true on success.
Future<bool> setTextUnderline(int handle, int underline) {
throw UnimplementedError('setTextUnderline() has not been implemented.');
}
/// Feeds paper by specified number of lines.
///
/// Returns true on success.
Future<bool> feedLine(int handle, int numLines) {
throw UnimplementedError('feedLine() has not been implemented.');
}
/// Feeds paper by specified number of dots.
///
/// Returns true on success.
Future<bool> feedDot(int handle, int numDots) {
throw UnimplementedError('feedDot() has not been implemented.');
}
/// Performs a half cut of the paper.
///
/// Returns true on success.
Future<bool> halfCutPaper(int handle) {
throw UnimplementedError('halfCutPaper() has not been implemented.');
}
/// Performs a full cut of the paper.
///
/// Returns true on success.
Future<bool> fullCutPaper(int handle) {
throw UnimplementedError('fullCutPaper() has not been implemented.');
}
}