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

A Flutter plugin for TSC printers.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:printer_tsc/printer_tsc.dart';
import 'package:printer_tsc/printer_tsc_platform_interface.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF0B6E4F),
          brightness: Brightness.light,
        ),
        scaffoldBackgroundColor: const Color(0xFFF4F1E8),
        inputDecorationTheme: const InputDecorationTheme(
          filled: true,
          fillColor: Colors.white,
          border: OutlineInputBorder(
            borderRadius: BorderRadius.all(Radius.circular(18)),
          ),
        ),
      ),
      home: const PrinterConnectionTestPage(),
    );
  }
}

enum _FeedbackTone {
  idle,
  loading,
  success,
  error,
}

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

  @override
  State<PrinterConnectionTestPage> createState() => _PrinterConnectionTestPageState();
}

class _PrinterConnectionTestPageState extends State<PrinterConnectionTestPage> {
  final PrinterTsc _printer = PrinterTsc();

  final TextEditingController _bluetoothMacController =
      TextEditingController(text: '00:19:0E:A0:04:E1');
  final TextEditingController _ethernetIpController =
      TextEditingController(text: '192.168.1.50');
  final TextEditingController _ethernetPortController =
      TextEditingController(text: '9100');
  final TextEditingController _usbTimeoutController =
      TextEditingController(text: '3000');
  final TextEditingController _windowsPortController =
      TextEditingController(text: 'TSC MA2400');
  final TextEditingController _windowsFontXController =
      TextEditingController(text: '40');
  final TextEditingController _windowsFontYController =
      TextEditingController(text: '40');
  final TextEditingController _windowsFontHeightController =
      TextEditingController(text: '32');
  final TextEditingController _windowsFontRotationController =
      TextEditingController(text: '0');
  final TextEditingController _windowsFontStyleController =
      TextEditingController(text: '0');
  final TextEditingController _windowsFontUnderlineController =
      TextEditingController(text: '0');
  final TextEditingController _windowsFontFaceController =
      TextEditingController(text: '黑体');
  final TextEditingController _windowsFontContentController =
      TextEditingController(text: '测试中文Abc123');
  final TextEditingController _tsplCommandController = TextEditingController(
    text:
        'CLS\r\nTEXT 40,40,"3",0,1,1,"printer_tsc"\r\nPRINT 1,1\r\nCLS\r\n',
  );

  String _platformVersion = 'Loading...';
  String _activeConnectionLabel = 'Not connected';
  String _lastStatus = '-';
  String _lastBatteryInfo = '-';
  int _connectionTabIndex = 0;
  String _feedbackTitle = 'Ready';
  String _feedbackDetail = 'Select a connection type and start testing.';
  _FeedbackTone _feedbackTone = _FeedbackTone.loading;
  bool _busy = false;

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

  @override
  void dispose() {
    _bluetoothMacController.dispose();
    _ethernetIpController.dispose();
    _ethernetPortController.dispose();
    _usbTimeoutController.dispose();
    _windowsPortController.dispose();
    _windowsFontXController.dispose();
    _windowsFontYController.dispose();
    _windowsFontHeightController.dispose();
    _windowsFontRotationController.dispose();
    _windowsFontStyleController.dispose();
    _windowsFontUnderlineController.dispose();
    _windowsFontFaceController.dispose();
    _windowsFontContentController.dispose();
    _tsplCommandController.dispose();
    super.dispose();
  }

  Future<void> _loadPlatformVersion() async {
    try {
      final String version =
          await _printer.getPlatformVersion() ?? 'Unknown platform version';
      if (!mounted) {
        return;
      }
      setState(() {
        _platformVersion = version;
      });
    } on PlatformException catch (exception) {
      if (!mounted) {
        return;
      }
      setState(() {
        _platformVersion = 'Failed: ${exception.message ?? exception.code}';
      });
    }
  }

  void _setFeedback(
    _FeedbackTone tone,
    String title,
    String detail,
  ) {
    if (!mounted) {
      return;
    }

    setState(() {
      _feedbackTone = tone;
      _feedbackTitle = title;
      _feedbackDetail = detail;
    });

    if (tone == _FeedbackTone.loading) {
      return;
    }

    final Color backgroundColor;
    switch (tone) {
      case _FeedbackTone.success:
        backgroundColor = const Color(0xFF166534);
      case _FeedbackTone.error:
        backgroundColor = const Color(0xFFB42318);
      case _FeedbackTone.idle:
      case _FeedbackTone.loading:
        backgroundColor = const Color(0xFF334155);
    }

    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(
        SnackBar(
          backgroundColor: backgroundColor,
          content: Text('$title: $detail'),
        ),
      );
  }

  String _describePlatformException(PlatformException exception) {
    final String message = exception.message?.trim() ?? '';
    if (message.isNotEmpty) {
      return message;
    }
    return exception.code;
  }

  String _statusText(PrinterStatus? status) {
    switch (status) {
      case PrinterStatus.idle:
        return 'Idle';
      case PrinterStatus.headOpened:
        return 'Head Opened';
      case PrinterStatus.paperJam:
        return 'Paper Jam';
      case PrinterStatus.paperJamAndHeadOpened:
        return 'Paper Jam + Head Opened';
      case PrinterStatus.paperEmpty:
        return 'Paper Empty';
      case PrinterStatus.paperEmptyAndHeadOpened:
        return 'Paper Empty + Head Opened';
      case PrinterStatus.ribbonEmpty:
        return 'Ribbon Empty';
      case PrinterStatus.ribbonEmptyAndHeadOpened:
        return 'Ribbon Empty + Head Opened';
      case PrinterStatus.ribbonEmptyAndPaperJam:
        return 'Ribbon Empty + Paper Jam';
      case PrinterStatus.ribbonEmptyAndPaperJamAndHeadOpened:
        return 'Ribbon Empty + Paper Jam + Head Opened';
      case PrinterStatus.ribbonEmptyAndPaperEmpty:
        return 'Ribbon Empty + Paper Empty';
      case PrinterStatus.ribbonEmptyAndPaperEmptyAndHeadOpened:
        return 'Ribbon Empty + Paper Empty + Head Opened';
      case PrinterStatus.paused:
        return 'Paused';
      case PrinterStatus.printing:
        return 'Printing';
      case PrinterStatus.otherError:
        return 'Other Error';
      case PrinterStatus.unknown:
      case null:
        return 'Unknown';
    }
  }

  Future<void> _runBoolAction({
    required String pendingTitle,
    required String pendingDetail,
    required Future<bool> Function() action,
    required String successTitle,
    required String successDetail,
    required String failureTitle,
    required String failureDetail,
    void Function(bool success)? onCompleted,
  }) async {
    if (_busy) {
      return;
    }

    setState(() {
      _busy = true;
    });
    _setFeedback(_FeedbackTone.loading, pendingTitle, pendingDetail);

    try {
      final bool success = await action();
      onCompleted?.call(success);
      _setFeedback(
        success ? _FeedbackTone.success : _FeedbackTone.error,
        success ? successTitle : failureTitle,
        success ? successDetail : failureDetail,
      );
    } on PlatformException catch (exception) {
      _setFeedback(
        _FeedbackTone.error,
        failureTitle,
        _describePlatformException(exception),
      );
    } catch (exception) {
      _setFeedback(
        _FeedbackTone.error,
        failureTitle,
        exception.toString(),
      );
    } finally {
      if (!mounted) {
        return;
      }
      setState(() {
        _busy = false;
      });
    }
  }

  Future<void> _connectBluetooth() async {
    final String macAddress = _bluetoothMacController.text.trim();
    await _runBoolAction(
      pendingTitle: 'Connecting Bluetooth',
      pendingDetail: 'Trying $macAddress',
      action: () async {
        final bool connected = await _printer.openBluetoothPort(macAddress);
        if (!connected) {
          return false;
        }
        return _initializePrinterAfterConnect();
      },
      successTitle: 'Bluetooth Connected',
      successDetail: '$macAddress (initialized with setup 65x147)',
      failureTitle: 'Bluetooth Failed',
      failureDetail: 'Unable to open Bluetooth printer or setup failed.',
      onCompleted: (bool success) {
        setState(() {
          _activeConnectionLabel = success
              ? 'Bluetooth: $macAddress'
              : 'Bluetooth connection failed';
        });
      },
    );
  }

  Future<void> _connectEthernet() async {
    final String ipAddress = _ethernetIpController.text.trim();
    final String port = _ethernetPortController.text.trim();
    await _runBoolAction(
      pendingTitle: 'Connecting Ethernet',
      pendingDetail: 'Trying $ipAddress:$port',
      action: () async {
        final bool connected = await _printer.openEthernetPort(ipAddress, port);
        if (!connected) {
          return false;
        }
        return _initializePrinterAfterConnect();
      },
      successTitle: 'Ethernet Connected',
      successDetail: '$ipAddress:$port (initialized with setup 65x147)',
      failureTitle: 'Ethernet Failed',
      failureDetail: 'Unable to open Ethernet printer or setup failed.',
      onCompleted: (bool success) {
        setState(() {
          _activeConnectionLabel = success
              ? 'Ethernet: $ipAddress:$port'
              : 'Ethernet connection failed';
        });
      },
    );
  }

  Future<void> _connectUsb() async {
    final int? timeoutMs = int.tryParse(_usbTimeoutController.text.trim());
    await _runBoolAction(
      pendingTitle: 'Connecting USB',
      pendingDetail: 'Waiting for USB permission or device open result.',
      action: () async {
        final bool connected = await _printer.openUsbPort(timeoutMs: timeoutMs);
        if (!connected) {
          return false;
        }
        return _initializePrinterAfterConnect();
      },
      successTitle: 'USB Connected',
      successDetail: 'Printer opened and initialized with setup 65x147.',
      failureTitle: 'USB Failed',
      failureDetail: 'USB open returned false or setup failed.',
      onCompleted: (bool success) {
        setState(() {
          _activeConnectionLabel = success ? 'USB connected' : 'USB connection failed';
        });
      },
    );
  }

  Future<void> _testWindowsFont() async {
    final int? x = int.tryParse(_windowsFontXController.text.trim());
    final int? y = int.tryParse(_windowsFontYController.text.trim());
    final int? fontheight = int.tryParse(_windowsFontHeightController.text.trim());
    final int? rotation = int.tryParse(_windowsFontRotationController.text.trim());
    final int? fontstyle = int.tryParse(_windowsFontStyleController.text.trim());
    final int? fontunderline = int.tryParse(_windowsFontUnderlineController.text.trim());
    final String szFaceName = _windowsFontFaceController.text.trim();
    final String content = _windowsFontContentController.text.trim();

    if (x == null || y == null || fontheight == null || rotation == null ||
        fontstyle == null || fontunderline == null ||
        szFaceName.isEmpty || content.isEmpty) {
      _setFeedback(
        _FeedbackTone.error,
        'Invalid Input',
        'All windowsfont fields are required.',
      );
      return;
    }

    await _runBoolAction(
      pendingTitle: 'windowsfont',
      pendingDetail: 'Printing text with Windows font "$szFaceName".',
      action: () async {
        final bool cleared = await _printer.clearBuffer();
        if (!cleared) return false;
        final bool printed = await _printer.windowsfont(
          x, y, fontheight, rotation, fontstyle, fontunderline, szFaceName, content,
        );
        if (!printed) return false;
        return _printer.printlabel(1, 1);
      },
      successTitle: 'windowsfont Sent',
      successDetail: 'Text printed with font "$szFaceName".',
      failureTitle: 'windowsfont Failed',
      failureDetail: 'One of the commands returned false.',
    );
  }

  Future<void> _connectWindowsPort() async {
    final String portName = _windowsPortController.text.trim();
    await _runBoolAction(
      pendingTitle: 'Connecting Windows Port',
      pendingDetail: 'Trying $portName',
      action: () async {
        final bool connected = await _printer.openPortForWindows(portName);
        if (!connected) return false;
        return _initializePrinterAfterConnect();
      },
      successTitle: 'Windows Port Connected',
      successDetail: '$portName (initialized with setup 65x147)',
      failureTitle: 'Windows Port Failed',
      failureDetail: 'Unable to open port or setup failed.',
      onCompleted: (bool success) {
        setState(() {
          _activeConnectionLabel =
              success ? 'Windows: $portName' : 'Windows port connection failed';
        });
      },
    );
  }

  Future<bool> _initializePrinterAfterConnect() {
    return _printer.setup(65, 147, 4, 12, LabelSensorType.blackMark, 3, 0);
  }

  Future<void> _closePort() async {
    await _runBoolAction(
      pendingTitle: 'Closing Port',
      pendingDetail: 'Sending close command.',
      action: _printer.close,
      successTitle: 'Port Closed',
      successDetail: 'Printer connection has been closed.',
      failureTitle: 'Close Failed',
      failureDetail: 'The printer did not close cleanly.',
      onCompleted: (bool success) {
        setState(() {
          _activeConnectionLabel = success ? 'Not connected' : 'Close command failed';
        });
      },
    );
  }

  Future<void> _queryStatus() async {
    if (_busy) {
      return;
    }

    setState(() {
      _busy = true;
    });
    _setFeedback(_FeedbackTone.loading, 'Reading Status', 'Querying printer status.');

    try {
      final PrinterStatus? status = await _printer.printerstatus(500);
      final String statusText = _statusText(status);
      setState(() {
        _lastStatus = statusText;
      });
      _setFeedback(_FeedbackTone.success, 'Status Ready', statusText);
    } on PlatformException catch (exception) {
      _setFeedback(
        _FeedbackTone.error,
        'Status Failed',
        _describePlatformException(exception),
      );
    } catch (exception) {
      _setFeedback(_FeedbackTone.error, 'Status Failed', exception.toString());
    } finally {
      if (!mounted) {
        return;
      }
      setState(() {
        _busy = false;
      });
    }
  }

  Future<void> _queryBattery() async {
    if (_busy) {
      return;
    }

    setState(() {
      _busy = true;
    });
    _setFeedback(_FeedbackTone.loading, 'Reading Battery', 'Querying smart battery voltage.');

    try {
      final String? value = await _printer.smartbatteryStatus(
        SmartBatteryStatusType.voltage,
      );
      final String batteryText = (value == null || value.isEmpty) ? 'No data' : value;
      setState(() {
        _lastBatteryInfo = batteryText;
      });
      _setFeedback(_FeedbackTone.success, 'Battery Ready', batteryText);
    } on PlatformException catch (exception) {
      _setFeedback(
        _FeedbackTone.error,
        'Battery Failed',
        _describePlatformException(exception),
      );
    } catch (exception) {
      _setFeedback(_FeedbackTone.error, 'Battery Failed', exception.toString());
    } finally {
      if (!mounted) {
        return;
      }
      setState(() {
        _busy = false;
      });
    }
  }

  Future<void> _sendRawCommand() async {
    await _runBoolAction(
      pendingTitle: 'Sending Command',
      pendingDetail: 'Dispatching TSPL command to the printer.',
      action: () => _printer.sendcommand(_tsplCommandController.text),
      successTitle: 'Command Sent',
      successDetail: 'TSPL command was accepted.',
      failureTitle: 'Command Failed',
      failureDetail: 'TSPL command returned false.',
    );
  }

  Future<void> _clearBufferOnly() async {
    await _runBoolAction(
      pendingTitle: 'Clearing Buffer',
      pendingDetail: 'Sending clear buffer command to the printer.',
      action: _printer.clearBuffer,
      successTitle: 'Buffer Cleared',
      successDetail: 'Printer buffer was cleared successfully.',
      failureTitle: 'Clear Failed',
      failureDetail: 'clearBuffer returned false.',
    );
  }

  Future<void> _printTestLabel() async {
    if (_busy) {
      return;
    }

    setState(() {
      _busy = true;
    });
    _setFeedback(
      _FeedbackTone.loading,
      'Printing Test Label',
      'Clearing buffer and sending test content.',
    );

    try {
      final bool cleared = await _printer.clearBuffer();
      if (!cleared) {
        _setFeedback(_FeedbackTone.error, 'Print Failed', 'Clear buffer returned false.');
        return;
      }

      final bool textPrinted =
          await _printer.printerfont(40, 30, '3', Rotation.deg0, 1, 1, 'printer_tsc');
      final bool barcodePrinted = await _printer.barcode(
        40,
        90,
        BarcodeType.code128,
        80,
        HumanReadable.visible,
        Rotation.deg0,
        2,
        2,
        '1234567890',
      );
      final bool labelPrinted = await _printer.printlabel(1, 1);

      final bool success = textPrinted && barcodePrinted && labelPrinted;
      _setFeedback(
        success ? _FeedbackTone.success : _FeedbackTone.error,
        success ? 'Print Sent' : 'Print Failed',
        success
            ? 'Test label command sequence completed.'
            : 'One of the print commands returned false.',
      );
    } on PlatformException catch (exception) {
      _setFeedback(
        _FeedbackTone.error,
        'Print Failed',
        _describePlatformException(exception),
      );
    } catch (exception) {
      _setFeedback(_FeedbackTone.error, 'Print Failed', exception.toString());
    } finally {
      if (!mounted) {
        return;
      }
      setState(() {
        _busy = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Positioned(
            top: -120,
            right: -40,
            child: Container(
              width: 240,
              height: 240,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                gradient: LinearGradient(
                  colors: <Color>[Color(0xFFB7E4C7), Color(0x00B7E4C7)],
                ),
              ),
            ),
          ),
          Positioned(
            left: -60,
            bottom: -80,
            child: Container(
              width: 220,
              height: 220,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                gradient: LinearGradient(
                  colors: <Color>[Color(0xFFD9ED92), Color(0x00D9ED92)],
                ),
              ),
            ),
          ),
          SafeArea(
            child: ListView(
              padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
              children: <Widget>[
                _HeroHeader(
                  platformVersion: _platformVersion,
                  activeConnectionLabel: _activeConnectionLabel,
                  busy: _busy,
                ),
                const SizedBox(height: 16),
                _FeedbackBanner(
                  tone: _feedbackTone,
                  title: _feedbackTitle,
                  detail: _feedbackDetail,
                ),
                const SizedBox(height: 16),
                _OverviewGrid(
                  lastStatus: _lastStatus,
                  lastBatteryInfo: _lastBatteryInfo,
                  activeConnectionLabel: _activeConnectionLabel,
                ),
                const SizedBox(height: 16),
                SegmentedButton<int>(
                  segments: const <ButtonSegment<int>>[
                    ButtonSegment<int>(
                      value: 0,
                      label: Text('Android'),
                      icon: Icon(Icons.android_rounded),
                    ),
                    ButtonSegment<int>(
                      value: 1,
                      label: Text('Windows'),
                      icon: Icon(Icons.window_rounded),
                    ),
                  ],
                  selected: <int>{_connectionTabIndex},
                  onSelectionChanged: (Set<int> value) {
                    setState(() {
                      _connectionTabIndex = value.first;
                    });
                  },
                ),
                const SizedBox(height: 16),
                if (_connectionTabIndex == 0) ...<Widget>[
                  _ConnectionCard(
                    title: 'Bluetooth Test',
                    subtitle: 'Open printer by MAC address.',
                    icon: Icons.bluetooth_rounded,
                    accentColor: const Color(0xFF155E75),
                    child: Column(
                      children: <Widget>[
                        TextField(
                          controller: _bluetoothMacController,
                          decoration: const InputDecoration(
                            labelText: 'MAC address',
                            hintText: '00:19:0E:A0:04:E1',
                          ),
                        ),
                        const SizedBox(height: 12),
                        SizedBox(
                          width: double.infinity,
                          child: FilledButton.icon(
                            onPressed: _busy ? null : _connectBluetooth,
                            icon: const Icon(Icons.link_rounded),
                            label: const Text('Connect Bluetooth'),
                          ),
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(height: 16),
                  _ConnectionCard(
                    title: 'Ethernet Test',
                    subtitle: 'Open printer by IP and port.',
                    icon: Icons.lan_rounded,
                    accentColor: const Color(0xFF0F766E),
                    child: Column(
                      children: <Widget>[
                        TextField(
                          controller: _ethernetIpController,
                          decoration: const InputDecoration(
                            labelText: 'IP address',
                            hintText: '192.168.1.50',
                          ),
                        ),
                        const SizedBox(height: 12),
                        TextField(
                          controller: _ethernetPortController,
                          keyboardType: TextInputType.number,
                          decoration: const InputDecoration(
                            labelText: 'Port',
                            hintText: '9100',
                          ),
                        ),
                        const SizedBox(height: 12),
                        SizedBox(
                          width: double.infinity,
                          child: FilledButton.icon(
                            onPressed: _busy ? null : _connectEthernet,
                            icon: const Icon(Icons.cable_rounded),
                            label: const Text('Connect Ethernet'),
                          ),
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(height: 16),
                  _ConnectionCard(
                    title: 'USB Test',
                    subtitle: 'Open printer and request permission if needed.',
                    icon: Icons.usb_rounded,
                    accentColor: const Color(0xFF1D4ED8),
                    child: Column(
                      children: <Widget>[
                        TextField(
                          controller: _usbTimeoutController,
                          keyboardType: TextInputType.number,
                          decoration: const InputDecoration(
                            labelText: 'Timeout ms',
                            hintText: '3000',
                          ),
                        ),
                        const SizedBox(height: 12),
                        SizedBox(
                          width: double.infinity,
                          child: FilledButton.icon(
                            onPressed: _busy ? null : _connectUsb,
                            icon: const Icon(Icons.usb_rounded),
                            label: const Text('Connect USB'),
                          ),
                        ),
                      ],
                    ),
                  ),
                ] else ...<Widget>[
                  _ConnectionCard(
                    title: 'Windows Port',
                    subtitle: 'Open printer by port name (e.g. USB001, COM1).',
                    icon: Icons.print_rounded,
                    accentColor: const Color(0xFF6B21A8),
                    child: Column(
                      children: <Widget>[
                        TextField(
                          controller: _windowsPortController,
                          decoration: const InputDecoration(
                            labelText: 'Port name',
                            hintText: 'TSC MA2400',
                          ),
                        ),
                        const SizedBox(height: 12),
                        SizedBox(
                          width: double.infinity,
                          child: FilledButton.icon(
                            onPressed: _busy ? null : _connectWindowsPort,
                            icon: const Icon(Icons.link_rounded),
                            label: const Text('Connect Windows Port'),
                          ),
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(height: 16),
                  _ConnectionCard(
                    title: 'windowsfont Test',
                    subtitle: 'Print text using a Windows TrueType font (Windows-only).',
                    icon: Icons.text_fields_rounded,
                    accentColor: const Color(0xFF7E22CE),
                    child: Column(
                      children: <Widget>[
                        Row(
                          children: <Widget>[
                            Expanded(
                              child: TextField(
                                controller: _windowsFontXController,
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(labelText: 'X'),
                              ),
                            ),
                            const SizedBox(width: 12),
                            Expanded(
                              child: TextField(
                                controller: _windowsFontYController,
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(labelText: 'Y'),
                              ),
                            ),
                            const SizedBox(width: 12),
                            Expanded(
                              child: TextField(
                                controller: _windowsFontHeightController,
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(labelText: 'Height (pt)'),
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 12),
                        Row(
                          children: <Widget>[
                            Expanded(
                              child: TextField(
                                controller: _windowsFontRotationController,
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(
                                  labelText: 'Rotation',
                                  hintText: '0/90/180/270',
                                ),
                              ),
                            ),
                            const SizedBox(width: 12),
                            Expanded(
                              child: TextField(
                                controller: _windowsFontStyleController,
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(
                                  labelText: 'Style',
                                  hintText: '0=normal 1=bold 2=italic',
                                ),
                              ),
                            ),
                            const SizedBox(width: 12),
                            Expanded(
                              child: TextField(
                                controller: _windowsFontUnderlineController,
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(
                                  labelText: 'Underline',
                                  hintText: '0=none 1=on',
                                ),
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 12),
                        TextField(
                          controller: _windowsFontFaceController,
                          decoration: const InputDecoration(
                            labelText: 'Font face name',
                            hintText: 'Arial',
                          ),
                        ),
                        const SizedBox(height: 12),
                        TextField(
                          controller: _windowsFontContentController,
                          decoration: const InputDecoration(
                            labelText: 'Content',
                            hintText: 'Hello Windows Font',
                          ),
                        ),
                        const SizedBox(height: 14),
                        SizedBox(
                          width: double.infinity,
                          child: FilledButton.icon(
                            onPressed: _busy ? null : _testWindowsFont,
                            icon: const Icon(Icons.print_rounded),
                            label: const Text('Print windowsfont'),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
                const SizedBox(height: 16),
                _ConnectionCard(
                  title: 'Command Console',
                  subtitle: 'Run printer actions after one connection is open.',
                  icon: Icons.terminal_rounded,
                  accentColor: const Color(0xFF7C2D12),
                  child: Column(
                    children: <Widget>[
                      TextField(
                        controller: _tsplCommandController,
                        minLines: 5,
                        maxLines: 9,
                        decoration: const InputDecoration(
                          labelText: 'TSPL command',
                          alignLabelWithHint: true,
                        ),
                      ),
                      const SizedBox(height: 14),
                      Wrap(
                        spacing: 10,
                        runSpacing: 10,
                        children: <Widget>[
                          FilledButton.icon(
                            onPressed: _busy ? null : _sendRawCommand,
                            icon: const Icon(Icons.send_rounded),
                            label: const Text('Send Command'),
                          ),
                          FilledButton.tonalIcon(
                            onPressed: _busy ? null : _clearBufferOnly,
                            icon: const Icon(Icons.cleaning_services_rounded),
                            label: const Text('Clear Buffer'),
                          ),
                          FilledButton.tonalIcon(
                            onPressed: _busy ? null : _printTestLabel,
                            icon: const Icon(Icons.print_rounded),
                            label: const Text('Print Test Label'),
                          ),
                          FilledButton.tonalIcon(
                            onPressed: _busy ? null : _queryStatus,
                            icon: const Icon(Icons.info_outline_rounded),
                            label: const Text('Read Status'),
                          ),
                          FilledButton.tonalIcon(
                            onPressed: _busy ? null : _queryBattery,
                            icon: const Icon(Icons.battery_charging_full_rounded),
                            label: const Text('Read Battery'),
                          ),
                          OutlinedButton.icon(
                            onPressed: _busy ? null : _closePort,
                            icon: const Icon(Icons.link_off_rounded),
                            label: const Text('Close Port'),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _HeroHeader extends StatelessWidget {
  const _HeroHeader({
    required this.platformVersion,
    required this.activeConnectionLabel,
    required this.busy,
  });

  final String platformVersion;
  final String activeConnectionLabel;
  final bool busy;

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);

    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(28),
        gradient: const LinearGradient(
          colors: <Color>[Color(0xFF16302B), Color(0xFF245C4A)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        boxShadow: const <BoxShadow>[
          BoxShadow(
            color: Color(0x22000000),
            blurRadius: 30,
            offset: Offset(0, 12),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text(
            'Printer Test Console',
            style: theme.textTheme.headlineSmall?.copyWith(
              color: Colors.white,
              fontWeight: FontWeight.w800,
            ),
          ),
          const SizedBox(height: 8),
          Text(
            'Test Bluetooth, Ethernet, and USB connection flows with direct runtime feedback.',
            style: theme.textTheme.bodyLarge?.copyWith(
              color: const Color(0xFFD1FAE5),
              height: 1.5,
            ),
          ),
          const SizedBox(height: 16),
          Wrap(
            spacing: 10,
            runSpacing: 10,
            children: <Widget>[
              _HeaderChip(
                label: 'Platform: $platformVersion',
                icon: Icons.android_rounded,
              ),
              _HeaderChip(
                label: activeConnectionLabel,
                icon: Icons.print_rounded,
              ),
              _HeaderChip(
                label: busy ? 'Busy' : 'Idle',
                icon: busy
                    ? Icons.hourglass_top_rounded
                    : Icons.check_circle_outline_rounded,
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class _HeaderChip extends StatelessWidget {
  const _HeaderChip({required this.label, required this.icon});

  final String label;
  final IconData icon;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
      decoration: BoxDecoration(
        color: const Color(0x22FFFFFF),
        borderRadius: BorderRadius.circular(999),
        border: Border.all(color: const Color(0x33FFFFFF)),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Icon(icon, color: Colors.white, size: 16),
          const SizedBox(width: 8),
          Text(
            label,
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.w600,
            ),
          ),
        ],
      ),
    );
  }
}

class _FeedbackBanner extends StatelessWidget {
  const _FeedbackBanner({
    required this.tone,
    required this.title,
    required this.detail,
  });

  final _FeedbackTone tone;
  final String title;
  final String detail;

  @override
  Widget build(BuildContext context) {
    final Color backgroundColor;
    final Color borderColor;
    final Color iconColor;
    final IconData icon;

    switch (tone) {
      case _FeedbackTone.success:
        backgroundColor = const Color(0xFFEAFBF1);
        borderColor = const Color(0xFF86EFAC);
        iconColor = const Color(0xFF166534);
        icon = Icons.check_circle_rounded;
      case _FeedbackTone.error:
        backgroundColor = const Color(0xFFFEF3F2);
        borderColor = const Color(0xFFFDA29B);
        iconColor = const Color(0xFFB42318);
        icon = Icons.error_rounded;
      case _FeedbackTone.loading:
        backgroundColor = const Color(0xFFEEF4FF);
        borderColor = const Color(0xFFB2CCFF);
        iconColor = const Color(0xFF1D4ED8);
        icon = Icons.hourglass_top_rounded;
      case _FeedbackTone.idle:
        backgroundColor = Colors.white;
        borderColor = const Color(0xFFE5E7EB);
        iconColor = const Color(0xFF475467);
        icon = Icons.notifications_active_outlined;
    }

    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: backgroundColor,
        borderRadius: BorderRadius.circular(24),
        border: Border.all(color: borderColor),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Icon(icon, color: iconColor, size: 24),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text(
                  title,
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.w800,
                        color: iconColor,
                      ),
                ),
                const SizedBox(height: 4),
                Text(detail),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _OverviewGrid extends StatelessWidget {
  const _OverviewGrid({
    required this.lastStatus,
    required this.lastBatteryInfo,
    required this.activeConnectionLabel,
  });

  final String lastStatus;
  final String lastBatteryInfo;
  final String activeConnectionLabel;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Row(
          children: <Widget>[
            Expanded(
              child: _MetricCard(
                label: 'Connection',
                value: activeConnectionLabel,
                accentColor: const Color(0xFF0F766E),
                icon: Icons.link_rounded,
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: _MetricCard(
                label: 'Printer Status',
                value: lastStatus,
                accentColor: const Color(0xFF1D4ED8),
                icon: Icons.receipt_long_rounded,
              ),
            ),
          ],
        ),
        const SizedBox(height: 12),
        _MetricCard(
          label: 'Battery Info',
          value: lastBatteryInfo,
          accentColor: const Color(0xFF7C2D12),
          icon: Icons.battery_charging_full_rounded,
        ),
      ],
    );
  }
}

class _MetricCard extends StatelessWidget {
  const _MetricCard({
    required this.label,
    required this.value,
    required this.accentColor,
    required this.icon,
  });

  final String label;
  final String value;
  final Color accentColor;
  final IconData icon;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(22),
        boxShadow: const <BoxShadow>[
          BoxShadow(
            color: Color(0x14000000),
            blurRadius: 18,
            offset: Offset(0, 8),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Icon(icon, color: accentColor),
          const SizedBox(height: 10),
          Text(
            label,
            style: Theme.of(context).textTheme.labelLarge?.copyWith(
                  color: const Color(0xFF667085),
                ),
          ),
          const SizedBox(height: 6),
          Text(
            value,
            style: Theme.of(context).textTheme.titleMedium?.copyWith(
                  fontWeight: FontWeight.w700,
                ),
          ),
        ],
      ),
    );
  }
}

class _ConnectionCard extends StatelessWidget {
  const _ConnectionCard({
    required this.title,
    required this.subtitle,
    required this.icon,
    required this.accentColor,
    required this.child,
  });

  final String title;
  final String subtitle;
  final IconData icon;
  final Color accentColor;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(28),
        color: Colors.white,
        boxShadow: const <BoxShadow>[
          BoxShadow(
            color: Color(0x16000000),
            blurRadius: 24,
            offset: Offset(0, 12),
          ),
        ],
      ),
      child: Padding(
        padding: const EdgeInsets.all(18),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Row(
              children: <Widget>[
                Container(
                  width: 44,
                  height: 44,
                  decoration: BoxDecoration(
                    color: accentColor.withValues(alpha: 0.12),
                    borderRadius: BorderRadius.circular(14),
                  ),
                  child: Icon(icon, color: accentColor),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text(
                        title,
                        style: Theme.of(context).textTheme.titleLarge?.copyWith(
                              color: accentColor,
                              fontWeight: FontWeight.w800,
                            ),
                      ),
                      const SizedBox(height: 4),
                      Text(subtitle),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            child,
          ],
        ),
      ),
    );
  }
}
0
likes
135
points
189
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for TSC printers.

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on printer_tsc

Packages that implement printer_tsc