evolute_printer_flutter 0.0.1 copy "evolute_printer_flutter: ^0.0.1" to clipboard
evolute_printer_flutter: ^0.0.1 copied to clipboard

PlatformAndroid

A Flutter plugin for Evolute handheld devices with integrated thermal printer. Supports text, image, and QR code printing via the Evolute SDK AIDL service.

example/lib/main.dart

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:evolute_printer_flutter/evolute_printer_flutter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Evolute Printer Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1565C0)),
        useMaterial3: true,
      ),
      home: const PrinterDemoScreen(),
    );
  }
}

// =============================================================================
// Main Demo Screen
// =============================================================================

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

  @override
  State<PrinterDemoScreen> createState() => _PrinterDemoScreenState();
}

class _PrinterDemoScreenState extends State<PrinterDemoScreen> {
  final EvolutePrinter _printer = EvolutePrinter();

  // Form controllers — mirrors the Java activity's input fields
  final _plateController = TextEditingController();
  final _descController = TextEditingController();

  // UI state
  bool _isConnecting = false;
  bool _isPrinting = false;
  PrinterStatus? _lastStatus;
  final List<String> _logs = [];

  // -----------------------------------------------------------------------
  // Lifecycle
  // -----------------------------------------------------------------------

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

  @override
  void dispose() {
    _plateController.dispose();
    _descController.dispose();
    _disconnectPrinter();
    super.dispose();
  }

  // -----------------------------------------------------------------------
  // Printer lifecycle
  // -----------------------------------------------------------------------

  Future<void> _connectPrinter() async {
    setState(() => _isConnecting = true);
    _addLog('Connecting to printer service...');
    try {
      await _printer.connect();
      _addLog('✅ Printer service connected');
    } on PrinterException catch (e) {
      _addLog('❌ Connect failed: ${e.message}');
      _showSnackBar(e.message, isError: true);
    } finally {
      setState(() => _isConnecting = false);
    }
  }

  Future<void> _disconnectPrinter() async {
    try {
      await _printer.disconnect();
    } catch (_) {}
  }

  // -----------------------------------------------------------------------
  // Print job — replicates Printdata() from EntryTicketScreen.java
  // -----------------------------------------------------------------------

  /// Full valet parking ticket print — exact replica of the Java Printdata() flow.
  Future<void> _printValetTicket() async {
    final plateNumber = _plateController.text.trim().toUpperCase();
    final description = _descController.text.trim();

    if (plateNumber.isEmpty) {
      _showSnackBar('Please enter a plate number', isError: true);
      return;
    }

    if (!_printer.isConnected) {
      _showSnackBar('Printer not connected. Tap "Reconnect".', isError: true);
      return;
    }

    setState(() => _isPrinting = true);
    _addLog('--- Starting print job ---');

    try {
      final now = DateTime.now();
      final inTime =
          '${_pad(now.day)}/${_pad(now.month)}/${now.year} '
          '${_pad(now.hour)}:${_pad(now.minute)}:${_pad(now.second)}';
      final ticketNumber = 'TKT${now.millisecondsSinceEpoch}';

      // ── Section 1: Header ──────────────────────────────────────────────
      _addLog('Printing header...');
      await _printer.setProperties(
        alignment: PrintAlignment.center,
        fontSize: PrintFontSize.normal,
      );
      await _printer.appendText('\nValet Parking Ticket No:');
      await _printer.startPrinting();

      // ── Section 2: Logo image ──────────────────────────────────────────
      _addLog('Printing logo...');
      await _printer.setProperties(
        alignment: PrintAlignment.center,
        fontSize: PrintFontSize.normal,
      );
      final Uint8List logoBytes = await _loadAssetBytes('assets/logo.png');
      await _printer.printImage(logoBytes);

      // ── Section 3: Ticket number & location ───────────────────────────
      _addLog('Printing ticket info...');
      await _printer.flushBuffer();
      await _printer.setProperties(
        alignment: PrintAlignment.center,
        fontSize: PrintFontSize.normal,
      );
      await _printer.appendText('\n$ticketNumber');
      await _printer.appendText('\n\nMANAGED BY');
      await _printer.appendText('\nOMNYPARK MANAGEMENT SERVICES LLC');
      await _printer.appendText('\n\nLocation  : EMAAR BUSINESS PARK BUILDING 3');
      await _printer.startPrinting();

      // ── Section 4: Vehicle details ────────────────────────────────────
      _addLog('Printing vehicle details...');
      await _printer.flushBuffer();
      await _printer.setProperties(
        alignment: PrintAlignment.left,
        fontSize: PrintFontSize.normal,
      );
      await _printer.appendText('\nIN TIME : $inTime');
      await _printer.appendText('\nPLATE No : $plateNumber');
      if (description.isNotEmpty) {
        await _printer.appendText('\nDESC : $description');
      }
      await _printer.startPrinting();

      // ── Section 5: Terms & conditions ─────────────────────────────────
      _addLog('Printing terms...');
      await _printer.flushBuffer();
      await _printer.setProperties(
        alignment: PrintAlignment.center,
        fontSize: PrintFontSize.normal,
      );
      await _printer.appendText('\n_________________');
      await _printer.appendText('\n\nVALET PARKING AGREEMENT');
      await _printer.appendText('\nPLEASE READ');
      await _printer.appendText('\n$_valetTerms');
      await _printer.startPrinting();

      // ── Section 6: Paper feed ─────────────────────────────────────────
      _addLog('Feeding paper...');
      await _printer.feedLines(9);

      // ── Status check ──────────────────────────────────────────────────
      final status = await _printer.getStatus();
      setState(() => _lastStatus = status);

      if (status.isSuccess) {
        _addLog('✅ Print job completed successfully');
        _showSnackBar('Ticket printed successfully!');
        _plateController.clear();
        _descController.clear();
      } else {
        _addLog('⚠️ Printer status: ${status.message}');
        _showSnackBar(status.message, isError: true);
      }
    } on PrinterException catch (e) {
      _addLog('❌ Print failed: ${e.message}');
      _showSnackBar(e.message, isError: true);
    } catch (e) {
      _addLog('❌ Unexpected error: $e');
      _showSnackBar('Unexpected error: $e', isError: true);
    } finally {
      setState(() => _isPrinting = false);
      _addLog('--- Print job ended ---');
    }
  }

  /// Minimal QR-only print — replicates Printdata1() from the Java activity.
  Future<void> _printQRTicket() async {
    final plateNumber = _plateController.text.trim().toUpperCase();
    if (plateNumber.isEmpty) {
      _showSnackBar('Please enter a plate number', isError: true);
      return;
    }

    if (!_printer.isConnected) {
      _showSnackBar('Printer not connected.', isError: true);
      return;
    }

    setState(() => _isPrinting = true);
    _addLog('--- Starting QR print job ---');

    try {
      final ticketNumber = 'TKT${DateTime.now().millisecondsSinceEpoch}';
      final now = DateTime.now();
      final inTime =
          '${_pad(now.day)}/${_pad(now.month)}/${now.year} '
          '${_pad(now.hour)}:${_pad(now.minute)}:${_pad(now.second)}';

      await _printer.flushBuffer();
      await _printer.setProperties(
        alignment: PrintAlignment.center,
        fontSize: PrintFontSize.normal,
      );
      await _printer.appendText('\nValet Parking Ticket No:');

      await _printer.printQRCode(
        data: ticketNumber,
        width: QRWidth.inch2,
        alignment: PrintAlignment.center,
      );

      await _printer.appendText('\n$ticketNumber');
      await _printer.appendText('\n\nManaged by:');
      await _printer.appendText('\nOMNYPARK MANAGEMENT SERVICES LLC');
      await _printer.appendText('\n\nLocation  : EMAAR BUSINESS PARK BUILDING 3');
      await _printer.appendText('\n\nIN TIME : $inTime');
      await _printer.appendText('\nPLATE No : $plateNumber');
      await _printer.appendText('\n_________________');
      await _printer.appendText('\n\nVALET PARKING AGREEMENT');
      await _printer.appendText('\nPLEASE READ');
      await _printer.appendText('\n$_valetTerms');

      await _printer.startPrinting();
      await _printer.feedLines(9);

      final status = await _printer.getStatus();
      setState(() => _lastStatus = status);

      if (status.isSuccess) {
        _addLog('✅ QR ticket printed successfully');
        _showSnackBar('QR Ticket printed!');
        _plateController.clear();
        _descController.clear();
      } else {
        _addLog('⚠️ ${status.message}');
        _showSnackBar(status.message, isError: true);
      }
    } on PrinterException catch (e) {
      _addLog('❌ ${e.message}');
      _showSnackBar(e.message, isError: true);
    } finally {
      setState(() => _isPrinting = false);
      _addLog('--- QR print job ended ---');
    }
  }

  // -----------------------------------------------------------------------
  // Helpers
  // -----------------------------------------------------------------------

  Future<Uint8List> _loadAssetBytes(String assetPath) async {
    final ByteData data = await rootBundle.load(assetPath);
    return data.buffer.asUint8List();
  }

  String _pad(int n) => n.toString().padLeft(2, '0');

  void _addLog(String message) {
    setState(() {
      _logs.add('[${TimeOfDay.now().format(context)}] $message');
      if (_logs.length > 50) _logs.removeAt(0); // keep log bounded
    });
  }

  void _showSnackBar(String message, {bool isError = false}) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: isError ? Colors.red.shade700 : Colors.green.shade700,
        behavior: SnackBarBehavior.floating,
      ),
    );
  }

  // -----------------------------------------------------------------------
  // Build
  // -----------------------------------------------------------------------

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Evolute Printer Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          // Connection status indicator
          Padding(
            padding: const EdgeInsets.only(right: 12),
            child: _isConnecting
                ? const SizedBox(
              width: 20,
              height: 20,
              child: CircularProgressIndicator(strokeWidth: 2),
            )
                : Icon(
              _printer.isConnected ? Icons.print : Icons.print_disabled,
              color: _printer.isConnected ? Colors.green : Colors.red,
            ),
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // ── Connection card ──────────────────────────────────────────
            _ConnectionStatusCard(
              isConnected: _printer.isConnected,
              isConnecting: _isConnecting,
              lastStatus: _lastStatus,
              onReconnect: _connectPrinter,
            ),

            const SizedBox(height: 16),

            // ── Input form ───────────────────────────────────────────────
            _InputFormCard(
              plateController: _plateController,
              descController: _descController,
            ),

            const SizedBox(height: 16),

            // ── Print actions ────────────────────────────────────────────
            _PrintActionsCard(
              isPrinting: _isPrinting,
              isConnected: _printer.isConnected,
              onPrintTicket: _printValetTicket,
              onPrintQRTicket: _printQRTicket,
            ),

            const SizedBox(height: 16),

            // ── Log console ──────────────────────────────────────────────
            _LogConsoleCard(logs: _logs),
          ],
        ),
      ),
    );
  }
}

// =============================================================================
// Sub-widgets
// =============================================================================

class _ConnectionStatusCard extends StatelessWidget {
  final bool isConnected;
  final bool isConnecting;
  final PrinterStatus? lastStatus;
  final VoidCallback onReconnect;

  const _ConnectionStatusCard({
    required this.isConnected,
    required this.isConnecting,
    required this.lastStatus,
    required this.onReconnect,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Printer Service',
                style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 12),
            Row(
              children: [
                Container(
                  width: 12,
                  height: 12,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: isConnecting
                        ? Colors.orange
                        : isConnected
                        ? Colors.green
                        : Colors.red,
                  ),
                ),
                const SizedBox(width: 8),
                Text(
                  isConnecting
                      ? 'Connecting...'
                      : isConnected
                      ? 'Connected'
                      : 'Disconnected',
                  style: TextStyle(
                    color: isConnecting
                        ? Colors.orange
                        : isConnected
                        ? Colors.green
                        : Colors.red,
                    fontWeight: FontWeight.w600,
                  ),
                ),
                const Spacer(),
                if (!isConnected && !isConnecting)
                  TextButton.icon(
                    onPressed: onReconnect,
                    icon: const Icon(Icons.refresh, size: 16),
                    label: const Text('Reconnect'),
                  ),
              ],
            ),
            if (lastStatus != null) ...[
              const SizedBox(height: 8),
              Text(
                'Last status: ${lastStatus!.message}',
                style: Theme.of(context).textTheme.bodySmall?.copyWith(
                  color: lastStatus!.isSuccess
                      ? Colors.green
                      : Colors.red.shade700,
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

class _InputFormCard extends StatelessWidget {
  final TextEditingController plateController;
  final TextEditingController descController;

  const _InputFormCard({
    required this.plateController,
    required this.descController,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Vehicle Details',
                style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 16),
            TextField(
              controller: plateController,
              textCapitalization: TextCapitalization.characters,
              decoration: const InputDecoration(
                labelText: 'Plate Number *',
                hintText: 'e.g. AB1234',
                prefixIcon: Icon(Icons.directions_car),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 12),
            TextField(
              controller: descController,
              decoration: const InputDecoration(
                labelText: 'Description (optional)',
                hintText: 'e.g. Red sedan, scratch on door',
                prefixIcon: Icon(Icons.notes),
                border: OutlineInputBorder(),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _PrintActionsCard extends StatelessWidget {
  final bool isPrinting;
  final bool isConnected;
  final VoidCallback onPrintTicket;
  final VoidCallback onPrintQRTicket;

  const _PrintActionsCard({
    required this.isPrinting,
    required this.isConnected,
    required this.onPrintTicket,
    required this.onPrintQRTicket,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Print Actions',
                style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 4),
            Text(
              'Both flows replicate the original Java Printdata() methods',
              style: Theme.of(context).textTheme.bodySmall,
            ),
            const SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: FilledButton.icon(
                onPressed: (!isPrinting && isConnected) ? onPrintTicket : null,
                icon: isPrinting
                    ? const SizedBox(
                  width: 16,
                  height: 16,
                  child: CircularProgressIndicator(
                    strokeWidth: 2,
                    color: Colors.white,
                  ),
                )
                    : const Icon(Icons.print),
                label: Text(isPrinting ? 'Printing...' : 'Print Valet Ticket'),
              ),
            ),
            const SizedBox(height: 8),
            SizedBox(
              width: double.infinity,
              child: OutlinedButton.icon(
                onPressed: (!isPrinting && isConnected) ? onPrintQRTicket : null,
                icon: const Icon(Icons.qr_code),
                label: const Text('Print QR Ticket'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _LogConsoleCard extends StatelessWidget {
  final List<String> logs;

  const _LogConsoleCard({required this.logs});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Text('Log', style: Theme.of(context).textTheme.titleMedium),
                const Spacer(),
                Text('${logs.length} entries',
                    style: Theme.of(context).textTheme.bodySmall),
              ],
            ),
            const SizedBox(height: 8),
            Container(
              width: double.infinity,
              height: 200,
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.grey.shade900,
                borderRadius: BorderRadius.circular(8),
              ),
              child: logs.isEmpty
                  ? const Center(
                child: Text('No logs yet',
                    style: TextStyle(color: Colors.grey)),
              )
                  : ListView.builder(
                reverse: true,
                itemCount: logs.length,
                itemBuilder: (_, i) {
                  final log = logs[logs.length - 1 - i];
                  final color = log.contains('❌')
                      ? Colors.red.shade300
                      : log.contains('✅')
                      ? Colors.green.shade300
                      : log.contains('⚠️')
                      ? Colors.orange.shade300
                      : Colors.grey.shade300;
                  return Text(
                    log,
                    style: TextStyle(
                      color: color,
                      fontSize: 11,
                      fontFamily: 'monospace',
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// =============================================================================
// Constants
// =============================================================================

const String _valetTerms =
    'Omnypark reserves the right to move the valet vehicle to the designated '
    'valet parking area depending on the availability of parking space without '
    'any prior notice. Omnypark, Building Management or The Landlord will not '
    'take any responsibility in case the parked vehicle being stolen or '
    'vandalized by third party. The valet service of your vehicle is handled '
    'by our valet drivers on your behalf at your own risk without any liability '
    'on us. All valuable should be removed from the vehicle at the time of '
    'valet service. In the event of losing this valet ticket your ID and the '
    'car registration card will be required to claim your vehicle along with '
    'the lost ticket payment as per the tariff.';
1
likes
150
points
68
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Evolute handheld devices with integrated thermal printer. Supports text, image, and QR code printing via the Evolute SDK AIDL service.

Repository (GitHub)
View/report issues

Topics

#printer #thermal-printer #evolute #hardware #android

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on evolute_printer_flutter

Packages that implement evolute_printer_flutter