zap_scan 1.1.2 copy "zap_scan: ^1.1.2" to clipboard
zap_scan: ^1.1.2 copied to clipboard

Flutter scanning package for cards, barcodes, and IATA boarding passes using on-device OCR and EMV NFC.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:zap_scan/zap_scan.dart';
import 'package:image_picker/image_picker.dart';

/// Entry point for the Zap Scan example application.
void main() => runApp(const MaterialApp(home: ZapScanExample()));

/// A simple example application demonstrating the use of [ZapScanWidget]
/// and [EmvNfcService] for scanning payment cards and barcodes.
class ZapScanExample extends StatefulWidget {
  /// Default constructor for [ZapScanExample].
  const ZapScanExample({super.key});

  @override
  State<ZapScanExample> createState() => _ZapScanExampleState();
}

class _ZapScanExampleState extends State<ZapScanExample> {
  late UniversalScannerController _controller;
  String _status = "Select an action below";
  bool _isScanning = false;

  @override
  void initState() {
    super.initState();
    // 1. Initialize the controller with callbacks
    _controller = UniversalScannerController(
      onResultScanned: (result) {
        setState(() {
          _isScanning = false;
          if (result is ZapCardResult) {
            _status = "💳 Credit Card\nNumber: ${result.cardNumber}\nExpiry: ${result.expiryDate ?? 'N/A'}\nCVV: ${result.cvv ?? 'N/A'}";
          } else if (result is FlightTicketResult) {
            _status = "✈️ Boarding Pass\n"
                "Name: ${result.passengerName ?? 'N/A'}\n"
                "Flight: ${result.flightNumber ?? 'N/A'}\n"
                "PNR: ${result.pnr}\n"
                "Seat: ${result.seat ?? 'N/A'} | Seq: ${result.sequence ?? 'N/A'}\n"
                "Route: ${result.origin}${result.originTerminal ?? ''} -> ${result.destination}${result.destinationTerminal ?? ''}\n"
                "Boarding: ${result.boardingTime ?? 'N/A'}\n"
                "Departure: ${result.departureTime ?? 'N/A'}\n"
                "Baggage: ${result.cabinBaggage ?? 'None'} / ${result.checkInBaggage ?? 'None'}\n"
                "Add-ons: ${result.addOns ?? 'None'}";
          } else if (result is BarcodeResult) {
            _status = "📊 Barcode (${result.format})\nPayload: ${result.payload}";
          }
        });
      },
    )..showDebugOverlay = true;
  }

  Future<void> _readNfc() async {
    setState(() => _status = "Hold card near the back of the device...");
    try {
      final card = await EmvNfcService.scanCard();
      if (card != null) {
        setState(() => _status = "🔓 NFC Found Card!\nNumber: ${card.cardNumber}\nExpiry: ${card.expiryDate}");
      }
    } catch (e) {
      setState(() => _status = "❌ NFC Error: $e");
    }
  }

  Future<void> _pickAndScan() async {
    await _controller.stopCamera(); // Kill the cam
    setState(() => _isScanning = false); // Update UI
    await Future.delayed(const Duration(milliseconds: 300));
    final picker = ImagePicker();
    final image = await picker.pickImage(source: ImageSource.gallery, requestFullMetadata: false);

    if (image == null) return;

    setState(() {
      _status = "Processing image...";
      _isScanning = false;
    });

    await _controller.stopCamera();

    try {
      final result = await _controller.scanFromImage(image);

      if (result is ScanErrorResult) {
        setState(() => _status = "Scan Error [${result.code}]: ${result.message}");
      } else if (result is ZapCardResult) {
        setState(() => _status = "💳 Credit Card\n"
            "Number: ${result.cardNumber}\n"
            "Guessed: ${result.guessedCardNumber ?? 'N/A'}\n"
            "Expiry: ${result.expiryDate ?? 'N/A'}\n"
            "CVV: ${result.cvv ?? 'N/A'}\n"
            "Raw: ${result.rawText}\n");
      } else if (result is FlightTicketResult) {
        setState(() => _status = "✈️ Boarding Pass\n"
            "Name: ${result.passengerName ?? 'N/A'}\n"
            "Flight: ${result.flightNumber ?? 'N/A'}\n"
            "PNR: ${result.pnr}\n"
            "Route: ${result.origin} -> ${result.destination}");
      } else if (result is BarcodeResult) {
        setState(() => _status = "📊 Barcode (${result.format})\nPayload: ${result.payload}");
      } else {
        setState(() => _status = "No recognizable data found in image.");
      }
    } catch (e) {
      setState(() => _status = "Unexpected Error: $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Zap Scan Example'),
        elevation: 2,
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
        actions: [
          IconButton(
            icon: Icon(_controller.showDebugOverlay ? Icons.bug_report : Icons.bug_report_outlined),
            onPressed: () {
              setState(() {
                _controller.showDebugOverlay = !_controller.showDebugOverlay;
              });
            },
            tooltip: "Toggle OCR Debug Overlay",
          ),
        ],
      ),
      body: Column(
        children: [
          // THE SCANNER WIDGET
          if (_isScanning)
            Expanded(
              child: Stack(
                children: [
                  ZapScanWidget(controller: _controller),
                  // SHOW REAL-TIME GUESSED CARD
                  ListenableBuilder(
                    listenable: _controller,
                    builder: (context, _) {
                      if (_controller.guessedCard == null) return const SizedBox.shrink();
                      return Positioned(
                        top: 40,
                        left: 20,
                        right: 20,
                        child: Container(
                          padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
                          decoration: BoxDecoration(
                            color: Colors.black54,
                            borderRadius: BorderRadius.circular(8),
                          ),
                          child: Text(
                            "Detecting: ${_controller.guessedCard}",
                            textAlign: TextAlign.center,
                            style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
                          ),
                        ),
                      );
                    },
                  ),
                  Positioned(
                    bottom: 20,
                    left: 0,
                    right: 0,
                    child: Center(
                      child: ElevatedButton(
                        onPressed: () => setState(() => _isScanning = false),
                        style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white),
                        child: const Text("Cancel"),
                      ),
                    ),
                  ),
                ],
              ),
            )
          else
            Expanded(
              child: Center(
                child: SingleChildScrollView(
                  padding: const EdgeInsets.all(24.0),
                  child: Text(_status, textAlign: TextAlign.center, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
                ),
              ),
            ),

          // BUTTONS
          Container(
            padding: const EdgeInsets.all(20.0),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              border: Border(top: BorderSide(color: Colors.grey[300]!)),
            ),
            child: Wrap(
              alignment: WrapAlignment.center,
              spacing: 12,
              runSpacing: 12,
              children: [
                ElevatedButton.icon(
                  onPressed: () {
                    setState(() {
                      _status = "Scanning...";
                      _isScanning = true;
                    });
                  },
                  icon: const Icon(Icons.camera_alt),
                  label: const Text("Live Cam"),
                ),
                ElevatedButton.icon(
                  onPressed: _pickAndScan,
                  icon: const Icon(Icons.image),
                  label: const Text("Upload File"),
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.blue, foregroundColor: Colors.white),
                ),
                ElevatedButton.icon(
                  onPressed: _readNfc,
                  icon: const Icon(Icons.nfc),
                  label: const Text("Read NFC"),
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.orange, foregroundColor: Colors.white),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
150
points
147
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter scanning package for cards, barcodes, and IATA boarding passes using on-device OCR and EMV NFC.

Repository (GitHub)
View/report issues

Topics

#scanner #ocr #nfc #card #barcode

License

MIT (license)

Dependencies

camera, flutter, flutter_web_plugins, google_mlkit_commons, nfc_manager

More

Packages that depend on zap_scan

Packages that implement zap_scan