Hello Sir !, thank you very much for the library, it was working very good with Algerian passports and Idcards, but I'm facing the next error with the new Idcards:
I/flutter (19080): passport FINE: 2024-08-05 16:26:15.734148: Reading EF.CardAccess
I/flutter (19080): passport FINE: 2024-08-05 16:26:15.740979: Selecting MF
I/flutter (19080): mrtd.api FINE: 2024-08-05 16:26:15.742924: Selecting root Master File
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.746479: Transceiving to ICC: C-APDU(CLA:00 INS:A4 P1:00 P2:00 Le:0 Lc:0 Data:null)
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.751509: Sending 4 byte(s) to ICC: data='00a40000'
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.795560: Received 2 byte(s) from ICC
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.797960: Received response from ICC: sw=6700 data_len=0
I/flutter (19080): mrtd.api WARNING: 2024-08-05 16:26:15.802688: Couldn't select MF by P1: 0, P2: 0 sw=sw=6700, re-trying to select MF with FileID=3F00
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.805214: Transceiving to ICC: C-APDU(CLA:00 INS:A4 P1:00 P2:00 Le:0 Lc:2 Data:3f00)
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.806180: Sending 7 byte(s) to ICC: data='00a40000023f00'
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.852395: Received 2 byte(s) from ICC
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.852691: Received response from ICC: sw=6700 data_len=0
I/flutter (19080): mrtd.api WARNING: 2024-08-05 16:26:15.853470: Couldn't select MF by P1=0, P2=0, FileID=3F00 sw=sw=6700, re-trying to select MF with P2=0x0C and FileID=3F00
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.854104: Transceiving to ICC: C-APDU(CLA:00 INS:A4 P1:00 P2:0C Le:0 Lc:2 Data:3f00)
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.854427: Sending 7 byte(s) to ICC: data='00a4000c023f00'
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.892469: Received 2 byte(s) from ICC
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.892794: Received response from ICC: sw=9000 data_len=0
I/flutter (19080): mrtd.api FINE: 2024-08-05 16:26:15.894335: Reading file sfi=0x1C
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.895624: Transceiving to ICC: C-APDU(CLA:00 INS:B0 P1:9C P2:00 Le:8 Lc:0 Data:null)
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.896093: Sending 5 byte(s) to ICC: data='00b09c0008'
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.948402: Received 2 byte(s) from ICC
I/flutter (19080): icc FINE: 2024-08-05 16:26:15.948822: Received response from ICC: sw=6282 data_len=0
I/flutter (19080): nfc.provider FINE: 2024-08-05 16:26:15.950810: Disconnecting
E/flutter (19080): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Null check operator used on a null value
E/flutter (19080): #0 MrtdApi.readFileBySFI (package:dmrtd/src/proto/mrtd_api.dart:167:51)
E/flutter (19080):
E/flutter (19080): #1 Passport._exec (package:dmrtd/src/passport.dart:428:14)
E/flutter (19080):
E/flutter (19080): #2 Passport.readEfCardAccess (package:dmrtd/src/passport.dart:104:11)
E/flutter (19080):
E/flutter (19080): #3 _MrtdHomePageState._readMRTD (package:mrtdeg/main.dart:264:31)
E/flutter (19080):
E/flutter (19080):
this is my code:
// Created by Crt Vavros, copyright © 2022 ZeroPass. All rights reserved.
// ignore_for_file: prefer_adjacent_string_concatenation, prefer_interpolation_to_compose_strings
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:image_picker/image_picker.dart';
import 'package:dmrtd/dmrtd.dart';
import 'package:dmrtd/extensions.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:toast/toast.dart';
class MrtdData {
EfCardAccess? cardAccess;
EfCardSecurity? cardSecurity;
EfCOM? com;
EfSOD? sod;
EfDG1? dg1;
EfDG2? dg2;
EfDG3? dg3;
EfDG4? dg4;
EfDG5? dg5;
EfDG6? dg6;
EfDG7? dg7;
EfDG8? dg8;
EfDG9? dg9;
EfDG10? dg10;
EfDG11? dg11;
EfDG12? dg12;
EfDG13? dg13;
EfDG14? dg14;
EfDG15? dg15;
EfDG16? dg16;
Uint8List? aaSig;
}
final Map<DgTag, String> dgTagToString = {
EfDG1.TAG: 'EF.DG1',
EfDG2.TAG: 'EF.DG2',
EfDG3.TAG: 'EF.DG3',
EfDG4.TAG: 'EF.DG4',
EfDG5.TAG: 'EF.DG5',
EfDG6.TAG: 'EF.DG6',
EfDG7.TAG: 'EF.DG7',
EfDG8.TAG: 'EF.DG8',
EfDG9.TAG: 'EF.DG9',
EfDG10.TAG: 'EF.DG10',
EfDG11.TAG: 'EF.DG11',
EfDG12.TAG: 'EF.DG12',
EfDG13.TAG: 'EF.DG13',
EfDG14.TAG: 'EF.DG14',
EfDG15.TAG: 'EF.DG15',
EfDG16.TAG: 'EF.DG16'
};
String formatEfCom(final EfCOM efCom) {
var str = "version: ${efCom.version}\n"
"unicode version: ${efCom.unicodeVersion}\n"
"DG tags:";
for (final t in efCom.dgTags) {
try {
str += " ${dgTagToString[t]!}";
} catch (e) {
str += " 0x${t.value.toRadixString(16)}";
}
}
return str;
}
String formatMRZ(final MRZ mrz) {
return "MRZ\n"
" version: ${mrz.version}\n" +
" doc code: ${mrz.documentCode}\n" +
" doc No.: ${mrz.documentNumber}\n" +
" country: ${mrz.country}\n" +
" nationality: ${mrz.nationality}\n" +
" name: ${mrz.firstName}\n" +
" surname: ${mrz.lastName}\n" +
" gender: ${mrz.gender}\n" +
" date of birth: ${DateFormat.yMd().format(mrz.dateOfBirth)}\n" +
" date of expiry: ${DateFormat.yMd().format(mrz.dateOfExpiry)}\n" +
" add. data: ${mrz.optionalData}\n" +
" add. data: ${mrz.optionalData2}";
}
String formatDG15(final EfDG15 dg15) {
var str = "EF.DG15:\n"
" AAPublicKey\n"
" type: ";
final rawSubPubKey = dg15.aaPublicKey.rawSubjectPublicKey();
if (dg15.aaPublicKey.type == AAPublicKeyType.RSA) {
final tvSubPubKey = TLV.fromBytes(rawSubPubKey);
var rawSeq = tvSubPubKey.value;
if (rawSeq[0] == 0x00) {
rawSeq = rawSeq.sublist(1);
}
final tvKeySeq = TLV.fromBytes(rawSeq);
final tvModule = TLV.decode(tvKeySeq.value);
final tvExp = TLV.decode(tvKeySeq.value.sublist(tvModule.encodedLen));
str += "RSA\n"
" exponent: ${tvExp.value.hex()}\n"
" modulus: ${tvModule.value.hex()}";
} else {
str += "EC\n SubjectPublicKey: ${rawSubPubKey.hex()}";
}
return str;
}
String formatProgressMsg(String message, int percentProgress) {
final p = (percentProgress / 20).round();
final full = "🟢 " * p;
final empty = "⚪️ " * (5 - p);
return message + "\n\n" + full + empty;
}
late SharedPreferences prefs;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
prefs = await SharedPreferences.getInstance();
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
print(
'${record.loggerName} ${record.level.name}: ${record.time}: ${record.message}');
});
runApp(MrtdEgApp());
}
class MrtdEgApp extends StatelessWidget {
@OverRide
Widget build(BuildContext context) {
return MaterialApp(localizationsDelegates: [
DefaultMaterialLocalizations.delegate,
DefaultCupertinoLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
], home: MrtdHomePage());
}
}
class MrtdHomePage extends StatefulWidget {
@OverRide
// ignore: library_private_types_in_public_api
_MrtdHomePageState createState() => _MrtdHomePageState();
}
class _MrtdHomePageState extends State {
var _alertMessage = "";
final _log = Logger("mrtdeg.app");
var _isNfcAvailable = false;
var _isReading = false;
final _mrzData = GlobalKey();
// mrz data
final _docNumber = TextEditingController(text: "411358813");
final _dob = TextEditingController(text: "10/18/1992"); // date of birth
final _doe = TextEditingController(text: "04/22/2034"); // date of doc expiry
MrtdData? _mrtdData;
final NfcProvider _nfc = NfcProvider();
// ignore: unused_field
late Timer _timerStateUpdater;
final _scrollController = ScrollController();
@OverRide
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
_initPlatformState();
// Update platform state every 3 sec
_timerStateUpdater = Timer.periodic(Duration(seconds: 3), (Timer t) {
_initPlatformState();
});
}
// Platform messages are asynchronous, so we initialize in an async method.
Future _initPlatformState() async {
bool isNfcAvailable;
try {
NfcStatus status = await NfcProvider.nfcStatus;
isNfcAvailable = status == NfcStatus.enabled;
} on PlatformException {
isNfcAvailable = false;
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_isNfcAvailable = isNfcAvailable;
});
}
DateTime? _getDOBDate() {
if (_dob.text.isEmpty) {
return null;
}
return DateFormat.yMd().parse(_dob.text);
}
DateTime? _getDOEDate() {
if (_doe.text.isEmpty) {
return null;
}
return DateFormat.yMd().parse(_doe.text);
}
Future<String?> _pickDate(BuildContext context, DateTime firstDate,
DateTime initDate, DateTime lastDate) async {
final locale = Localizations.localeOf(context);
final DateTime? picked = await showDatePicker(
context: context,
firstDate: firstDate,
initialDate: initDate,
lastDate: lastDate,
locale: locale);
if (picked != null) {
return DateFormat.yMd().format(picked);
}
return null;
}
void _readMRTD() async {
try {
setState(() {
_mrtdData = null;
_alertMessage = "Waiting for Passport tag ...";
_isReading = true;
});
await _nfc.connect(
iosAlertMessage: "Hold your phone near Biometric Passport");
final passport = Passport(_nfc);
setState(() {
_alertMessage = "Reading Passport ...";
});
_nfc.setIosAlertMessage("Trying to read EF.CardAccess ...");
final mrtdData = MrtdData();
try {
mrtdData.cardAccess = await passport.readEfCardAccess();
} on PassportError {
//if (e.code != StatusWord.fileNotFound) rethrow;
}
_nfc.setIosAlertMessage("Trying to read EF.CardSecurity ...");
try {
mrtdData.cardSecurity = await passport.readEfCardSecurity();
} on PassportError {
//if (e.code != StatusWord.fileNotFound) rethrow;
}
_nfc.setIosAlertMessage("Initiating session ...");
final bacKeySeed =
DBAKey(_docNumber.text, _getDOBDate()!, _getDOEDate()!);
await passport.startSession(bacKeySeed);
_nfc.setIosAlertMessage(formatProgressMsg("Reading EF.COM ...", 0));
mrtdData.com = await passport.readEfCOM();
_nfc.setIosAlertMessage(formatProgressMsg("Reading Data Groups ...", 20));
if (mrtdData.com!.dgTags.contains(EfDG1.TAG)) {
mrtdData.dg1 = await passport.readEfDG1();
}
if (mrtdData.com!.dgTags.contains(EfDG2.TAG)) {
mrtdData.dg2 = await passport.readEfDG2();
}
// To read DG3 and DG4 session has to be established with CVCA certificate (not supported).
// if(mrtdData.com!.dgTags.contains(EfDG3.TAG)) {
// mrtdData.dg3 = await passport.readEfDG3();
// }
// if(mrtdData.com!.dgTags.contains(EfDG4.TAG)) {
// mrtdData.dg4 = await passport.readEfDG4();
// }
if (mrtdData.com!.dgTags.contains(EfDG5.TAG)) {
mrtdData.dg5 = await passport.readEfDG5();
}
if (mrtdData.com!.dgTags.contains(EfDG6.TAG)) {
mrtdData.dg6 = await passport.readEfDG6();
}
if (mrtdData.com!.dgTags.contains(EfDG7.TAG)) {
mrtdData.dg7 = await passport.readEfDG7();
}
if (mrtdData.com!.dgTags.contains(EfDG8.TAG)) {
mrtdData.dg8 = await passport.readEfDG8();
}
if (mrtdData.com!.dgTags.contains(EfDG9.TAG)) {
mrtdData.dg9 = await passport.readEfDG9();
}
if (mrtdData.com!.dgTags.contains(EfDG10.TAG)) {
mrtdData.dg10 = await passport.readEfDG10();
}
if (mrtdData.com!.dgTags.contains(EfDG11.TAG)) {
mrtdData.dg11 = await passport.readEfDG11();
}
if (mrtdData.com!.dgTags.contains(EfDG12.TAG)) {
mrtdData.dg12 = await passport.readEfDG12();
}
if (mrtdData.com!.dgTags.contains(EfDG13.TAG)) {
mrtdData.dg13 = await passport.readEfDG13();
}
if (mrtdData.com!.dgTags.contains(EfDG14.TAG)) {
mrtdData.dg14 = await passport.readEfDG14();
}
if (mrtdData.com!.dgTags.contains(EfDG15.TAG)) {
mrtdData.dg15 = await passport.readEfDG15();
_nfc.setIosAlertMessage(formatProgressMsg("Doing AA ...", 60));
mrtdData.aaSig = await passport.activeAuthenticate(Uint8List(8));
}
if (mrtdData.com!.dgTags.contains(EfDG16.TAG)) {
mrtdData.dg16 = await passport.readEfDG16();
}
_nfc.setIosAlertMessage(formatProgressMsg("Reading EF.SOD ...", 80));
mrtdData.sod = await passport.readEfSOD();
setState(() {
_mrtdData = mrtdData;
});
setState(() {
_alertMessage = "";
});
_scrollController.animateTo(300.0,
duration: Duration(milliseconds: 500), curve: Curves.ease);
} on Exception catch (e) {
final se = e.toString().toLowerCase();
String alertMsg = "An error has occurred while reading Passport!";
if (e is PassportError) {
if (se.contains("security status not satisfied")) {
alertMsg =
"Failed to initiate session with passport.\nCheck input data!";
}
_log.error("PassportError: ${e.message}");
} else {
_log.error(
"An exception was encountered while trying to read Passport: $e");
}
if (se.contains('timeout')) {
alertMsg = "Timeout while waiting for Passport tag";
} else if (se.contains("tag was lost")) {
alertMsg = "Tag was lost. Please try again!";
} else if (se.contains("invalidated by user")) {
alertMsg = "";
}
setState(() {
_alertMessage = alertMsg;
});
} finally {
if (_alertMessage.isNotEmpty) {
await _nfc.disconnect(iosErrorMessage: _alertMessage);
} else {
await _nfc.disconnect(
iosAlertMessage: formatProgressMsg("Finished", 100));
}
setState(() {
_isReading = false;
});
}
}
bool _disabledInput() {
return _isReading || !_isNfcAvailable;
}
Widget _makeMrtdDataWidget(
{required String header,
required String collapsedText,
required dataText}) {
return ExpandablePanel(
theme: const ExpandableThemeData(
headerAlignment: ExpandablePanelHeaderAlignment.center,
tapBodyToCollapse: true,
hasIcon: true,
iconColor: Colors.red,
),
header: Text(header),
collapsed: Text(collapsedText,
softWrap: true, maxLines: 2, overflow: TextOverflow.ellipsis),
expanded: Container(
padding: const EdgeInsets.all(18),
color: Color.fromARGB(255, 239, 239, 239),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextButton(
child: Text('Copy'),
onPressed: () =>
Clipboard.setData(ClipboardData(text: dataText)),
),
SelectableText(dataText, textAlign: TextAlign.left)
])));
}
List _mrtdDataWidgets() {
List list = [];
if (_mrtdData == null) return list;
if (_mrtdData!.cardAccess != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.CardAccess',
collapsedText: '',
dataText: _mrtdData!.cardAccess!.toBytes().hex()));
}
if (_mrtdData!.cardSecurity != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.CardSecurity',
collapsedText: '',
dataText: _mrtdData!.cardSecurity!.toBytes().hex()));
}
if (_mrtdData!.sod != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.SOD',
collapsedText: '',
dataText: _mrtdData!.sod!.toBytes().hex()));
}
if (_mrtdData!.com != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.COM',
collapsedText: '',
dataText: formatEfCom(_mrtdData!.com!)));
}
if (_mrtdData!.dg1 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG1',
collapsedText: '',
dataText: formatMRZ(_mrtdData!.dg1!.mrz)));
}
if (_mrtdData!.dg2 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG2',
collapsedText: '',
dataText: _mrtdData!.dg2!.toBytes().hex()));
}
if (_mrtdData!.dg3 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG3',
collapsedText: '',
dataText: _mrtdData!.dg3!.toBytes().hex()));
}
if (_mrtdData!.dg4 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG4',
collapsedText: '',
dataText: _mrtdData!.dg4!.toBytes().hex()));
}
if (_mrtdData!.dg5 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG5',
collapsedText: '',
dataText: _mrtdData!.dg5!.toBytes().hex()));
}
if (_mrtdData!.dg6 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG6',
collapsedText: '',
dataText: _mrtdData!.dg6!.toBytes().hex()));
}
if (_mrtdData!.dg7 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG7',
collapsedText: '',
dataText: _mrtdData!.dg7!.toBytes().hex()));
}
if (_mrtdData!.dg8 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG8',
collapsedText: '',
dataText: _mrtdData!.dg8!.toBytes().hex()));
}
if (_mrtdData!.dg9 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG9',
collapsedText: '',
dataText: _mrtdData!.dg9!.toBytes().hex()));
}
if (_mrtdData!.dg10 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG10',
collapsedText: '',
dataText: _mrtdData!.dg10!.toBytes().hex()));
}
if (_mrtdData!.dg11 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG11',
collapsedText: '',
dataText: _mrtdData!.dg11!.toBytes().hex()));
}
if (_mrtdData!.dg12 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG12',
collapsedText: '',
dataText: _mrtdData!.dg12!.toBytes().hex()));
}
if (_mrtdData!.dg13 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG13',
collapsedText: '',
dataText: _mrtdData!.dg13!.toBytes().hex()));
}
if (_mrtdData!.dg14 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG14',
collapsedText: '',
dataText: _mrtdData!.dg14!.toBytes().hex()));
}
if (_mrtdData!.dg15 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG15',
collapsedText: '',
dataText: _mrtdData!.dg15!.toBytes().hex()));
}
if (_mrtdData!.aaSig != null) {
list.add(_makeMrtdDataWidget(
header: 'Active Authentication signature',
collapsedText: '',
dataText: _mrtdData!.aaSig!.hex()));
}
if (_mrtdData!.dg16 != null) {
list.add(_makeMrtdDataWidget(
header: 'EF.DG16',
collapsedText: '',
dataText: _mrtdData!.dg16!.toBytes().hex()));
}
return list;
}
@OverRide
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
/*iosContentPadding: false,
iosContentBottomPadding: false,*/
body: Material(
child: SafeArea(
child: Padding(
padding: EdgeInsets.all(8.0),
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
Row(children: <Widget>[
Text('NFC available:',
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold)),
SizedBox(width: 4),
Text(_isNfcAvailable ? "Yes" : "No",
style: TextStyle(fontSize: 18.0))
]),
SizedBox(height: 40),
Padding(
padding: EdgeInsets.symmetric(
vertical: 8.0, horizontal: 30.0),
child: Form(
key: _mrzData,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
enabled: !_disabledInput(),
controller: _docNumber,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Passport number',
fillColor: Colors.white),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r'[A-Z0-9]+')),
LengthLimitingTextInputFormatter(14)
],
textInputAction: TextInputAction.done,
textCapitalization:
TextCapitalization.characters,
autofocus: true,
validator: (value) {
if (value?.isEmpty ?? false) {
return 'Please enter passport number';
}
return null;
},
),
SizedBox(height: 12),
TextFormField(
enabled: !_disabledInput(),
controller: _dob,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Date of Birth',
fillColor: Colors.white),
autofocus: false,
validator: (value) {
if (value?.isEmpty ?? false) {
return 'Please select Date of Birth';
}
return null;
},
onTap: () async {
FocusScope.of(context)
.requestFocus(FocusNode());
// Can pick date which dates 15 years back or more
final now = DateTime.now();
final firstDate = DateTime(
now.year - 90,
now.month,
now.day);
final lastDate = DateTime(
now.year - 15,
now.month,
now.day);
final initDate = _getDOBDate();
final date = await _pickDate(
context,
firstDate,
initDate ?? lastDate,
lastDate);
FocusScope.of(context)
.requestFocus(FocusNode());
if (date != null) {
_dob.text = date;
}
}),
SizedBox(height: 12),
TextFormField(
enabled: !_disabledInput(),
controller: _doe,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Date of Expiry',
fillColor: Colors.white),
autofocus: false,
validator: (value) {
if (value?.isEmpty ?? false) {
return 'Please select Date of Expiry';
}
return null;
},
onTap: () async {
FocusScope.of(context)
.requestFocus(FocusNode());
// Can pick date from tomorrow and up to 10 years
final now = DateTime.now();
final firstDate = DateTime(
now.year,
now.month,
now.day + 1);
final lastDate = DateTime(
now.year + 10,
now.month + 6,
now.day);
final initDate = _getDOEDate();
final date = await _pickDate(
context,
firstDate,
initDate ?? firstDate,
lastDate);
FocusScope.of(context)
.requestFocus(FocusNode());
if (date != null) {
_doe.text = date;
}
})
],
),
)),
SizedBox(height: 20),
TextButton(
// btn Read MRTD
onPressed: _disabledInput() ||
!_mrzData.currentState!.validate()
? null
: _readMRTD,
child: Text(_isReading
? 'Reading ...'
: 'Read Passport'),
),
SizedBox(height: 4),
Text(_alertMessage,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold)),
SizedBox(height: 15),
Padding(
padding: EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
_mrtdData != null
? "Passport Data:"
: "",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold)),
Padding(
padding: EdgeInsets.only(
left: 16.0,
top: 8.0,
bottom: 8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: _mrtdDataWidgets()))
]),
),
]))))));
Padding _buildForm(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 30.0),
child: Form(
key: _mrzData,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
enabled: !_disabledInput(),
controller: _docNumber,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Passport number',
fillColor: Colors.white),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'[A-Z0-9]+')),
LengthLimitingTextInputFormatter(14)
],
textInputAction: TextInputAction.done,
textCapitalization: TextCapitalization.characters,
autofocus: true,
validator: (value) {
if (value?.isEmpty ?? false) {
return 'Please enter passport number';
}
return null;
},
),
SizedBox(height: 12),
TextFormField(
enabled: !_disabledInput(),
controller: _dob,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Date of Birth',
fillColor: Colors.white),
autofocus: false,
validator: (value) {
if (value?.isEmpty ?? false) {
return 'Please select Date of Birth';
}
return null;
},
onTap: () async {
FocusScope.of(context).requestFocus(FocusNode());
// Can pick date which dates 15 years back or more
final now = DateTime.now();
final firstDate =
DateTime(now.year - 90, now.month, now.day);
final lastDate =
DateTime(now.year - 15, now.month, now.day);
final initDate = _getDOBDate();
final date = await _pickDate(
context, firstDate, initDate ?? lastDate, lastDate);
FocusScope.of(context).requestFocus(FocusNode());
if (date != null) {
_dob.text = date;
}
}),
SizedBox(height: 12),
TextFormField(
enabled: !_disabledInput(),
controller: _doe,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Date of Expiry',
fillColor: Colors.white),
autofocus: false,
validator: (value) {
if (value?.isEmpty ?? false) {
return 'Please select Date of Expiry';
}
return null;
},
onTap: () async {
FocusScope.of(context).requestFocus(FocusNode());
// Can pick date from tomorrow and up to 10 years
final now = DateTime.now();
final firstDate =
DateTime(now.year, now.month, now.day + 1);
final lastDate =
DateTime(now.year + 10, now.month + 6, now.day);
final initDate = _getDOEDate();
final date = await _pickDate(
context, firstDate, initDate ?? firstDate, lastDate);
FocusScope.of(context).requestFocus(FocusNode());
if (date != null) {
_doe.text = date;
}
})
],
),
));
}
}
}
I really appreciate your help !!