Skip to main content

eid_nfc 0.1.0 copy "eid_nfc: ^0.1.0" to clipboard
eid_nfc: ^0.1.0 copied to clipboard

Reads electronic identity documents through the NFC of an Android phone or an iPhone: passports and identity cards. The eid transport for the flutter_nfc_kit plugin.

example/lib/main.dart

import 'package:eid_nfc_example/src/palette.dart';
import 'package:eid_nfc_example/src/session.dart';
import 'package:eid_nfc_example/src/widgets/apdu_console.dart';
import 'package:eid_nfc_example/src/widgets/nfc_mark.dart';
import 'package:eid_nfc_example/src/widgets/panels.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

// A browser has no NFC: there, the simulated document comes first.
void main() => runApp(
      const ExampleApp(
        source: kIsWeb ? DocumentSource.simulated : DocumentSource.nfc,
      ),
    );

/// Reads a passport or an identity card held against the phone.
class ExampleApp extends StatelessWidget {
  /// The app, reading from [source] first.
  const ExampleApp({super.key, this.source = DocumentSource.nfc});

  /// Where documents come from at launch.
  final DocumentSource source;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'eid_nfc demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Palette.accent,
        scaffoldBackgroundColor: Palette.paper,
        filledButtonTheme: FilledButtonThemeData(
          style: FilledButton.styleFrom(
            backgroundColor: Palette.accent,
            minimumSize: const Size(0, 48),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(14),
            ),
          ),
        ),
        outlinedButtonTheme: OutlinedButtonThemeData(
          style: OutlinedButton.styleFrom(
            foregroundColor: Palette.ink,
            minimumSize: const Size(0, 48),
            side: const BorderSide(color: Palette.line, width: 1.5),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(14),
            ),
          ),
        ),
      ),
      home: HomePage(source: source),
    );
  }
}

/// The page: the phone and the key, the options, then what was read.
class HomePage extends StatefulWidget {
  /// The page, reading from [source] first.
  const HomePage({super.key, this.source = DocumentSource.nfc});

  /// Where documents come from at launch.
  final DocumentSource source;

  @override
  State<HomePage> createState() => _HomePageState();
}

// Rebuilds come from the session's listeners, not setState.
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  late final _session = ReadSession(source: widget.source);

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _session.checkAvailability();
  }

  // NFC may have been turned on in the settings meanwhile.
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) _session.checkAvailability();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _session.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ListenableBuilder(
          listenable: _session,
          builder: (context, _) => LayoutBuilder(
            builder: (context, constraints) {
              final wide = constraints.maxWidth >= 1000;
              final gutter = constraints.maxWidth < 600 ? 16.0 : 32.0;
              final header = _Header(session: _session);
              final reader = ReaderPanel(session: _session);
              final options = OptionsPanel(session: _session);
              final console = ApduConsole(
                log: _session.apduLog,
                height: wide ? 300 : 220,
              );
              final result = ResultView(session: _session, wide: wide);

              return SingleChildScrollView(
                padding: EdgeInsets.all(gutter),
                child: Center(
                  child: ConstrainedBox(
                    constraints: BoxConstraints(maxWidth: wide ? 1320 : 640),
                    child: wide
                        ? Column(
                            crossAxisAlignment: CrossAxisAlignment.stretch,
                            children: [
                              header,
                              const SizedBox(height: 28),
                              Row(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  SizedBox(
                                    width: 400,
                                    child: Column(
                                      children: [
                                        reader,
                                        const SizedBox(height: 20),
                                        options,
                                        const SizedBox(height: 20),
                                        console,
                                      ],
                                    ),
                                  ),
                                  const SizedBox(width: 28),
                                  Expanded(child: result),
                                ],
                              ),
                            ],
                          )
                        : Column(
                            crossAxisAlignment: CrossAxisAlignment.stretch,
                            children: [
                              header,
                              const SizedBox(height: 20),
                              reader,
                              const SizedBox(height: 20),
                              result,
                              const SizedBox(height: 20),
                              options,
                              const SizedBox(height: 20),
                              console,
                            ],
                          ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

class _Header extends StatelessWidget {
  const _Header({required this.session});

  final ReadSession session;

  @override
  Widget build(BuildContext context) {
    const title = Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'eid_nfc',
          style: TextStyle(
            fontSize: 30,
            fontWeight: FontWeight.w800,
            letterSpacing: -0.5,
            color: Palette.ink,
          ),
        ),
        SizedBox(height: 2),
        Text(
          'Read a passport or an identity card with the phone',
          style: TextStyle(fontSize: 15, color: Palette.muted),
        ),
      ],
    );
    return Wrap(
      alignment: WrapAlignment.spaceBetween,
      crossAxisAlignment: WrapCrossAlignment.center,
      runSpacing: 12,
      spacing: 16,
      children: [title, _SourcePicker(session: session)],
    );
  }
}

/// The phone's NFC, or a simulated document to try the app without one.
class _SourcePicker extends StatelessWidget {
  const _SourcePicker({required this.session});

  final ReadSession session;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 14),
      decoration: BoxDecoration(
        color: Palette.white,
        borderRadius: BorderRadius.circular(14),
        boxShadow: Palette.softShadow,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (session.source == DocumentSource.nfc)
            const NfcMark(size: 18, color: Palette.muted)
          else
            const Icon(Icons.science_outlined, size: 18, color: Palette.muted),
          const SizedBox(width: 8),
          DropdownButtonHideUnderline(
            child: DropdownButton<DocumentSource>(
              value: session.source,
              items: const [
                DropdownMenuItem(
                  value: DocumentSource.nfc,
                  child: Text("This phone's NFC"),
                ),
                DropdownMenuItem(
                  value: DocumentSource.simulated,
                  child: Text('Simulated document'),
                ),
              ],
              onChanged: session.reading
                  ? null
                  : (source) => session.setSource(source!),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
176
downloads
screenshot

Documentation

API reference

Publisher

verified publishercomapps.be

Weekly Downloads

Reads electronic identity documents through the NFC of an Android phone or an iPhone: passports and identity cards. The eid transport for the flutter_nfc_kit plugin.

Homepage
Repository (GitHub)
View/report issues

Topics

#eid #nfc #smartcard #passport #identity

Funding

Consider supporting this project:

www.paypal.com

License

MIT (license)

Dependencies

eid, flutter, flutter_nfc_kit

More

Packages that depend on eid_nfc