|
|
|
@ -3,6 +3,8 @@ import 'dart:io';
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:nfc_manager/nfc_manager.dart';
|
|
|
|
import 'package:nfc_manager/nfc_manager.dart';
|
|
|
|
|
|
|
|
import 'package:nfc_manager/nfc_manager_android.dart' show NfcAAndroid, NfcBAndroid;
|
|
|
|
|
|
|
|
import 'package:nfc_manager/nfc_manager_ios.dart' show MiFareIos, Iso15693Ios;
|
|
|
|
|
|
|
|
|
|
|
|
void showNfcReader(BuildContext context, {required Function(String? nfcId) onNcfScan}) {
|
|
|
|
void showNfcReader(BuildContext context, {required Function(String? nfcId) onNcfScan}) {
|
|
|
|
showModalBottomSheet(
|
|
|
|
showModalBottomSheet(
|
|
|
|
@ -38,60 +40,55 @@ class _NfcLayoutState extends State<NfcLayout> {
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
super.initState();
|
|
|
|
|
|
|
|
_startNfcSession();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
void dispose() {
|
|
|
|
|
|
|
|
// Ensure session is stopped when widget is disposed
|
|
|
|
|
|
|
|
NfcManager.instance.stopSession().catchError((e) {
|
|
|
|
|
|
|
|
print('Error stopping NFC session in dispose: $e');
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
super.dispose();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void _startNfcSession() async {
|
|
|
|
|
|
|
|
// Stop any existing session before starting a new one
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await NfcManager.instance.stopSession();
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
print('No active session to stop: $e');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NfcManager.instance.startSession(
|
|
|
|
NfcManager.instance.startSession(
|
|
|
|
onDiscovered: (NfcTag tag) async {
|
|
|
|
onDiscovered: (NfcTag tag) async {
|
|
|
|
String? identifier;
|
|
|
|
String? identifier;
|
|
|
|
|
|
|
|
|
|
|
|
// Access tag identifier from the tag data
|
|
|
|
|
|
|
|
// In nfc_manager 4.1.1, access data via dynamic cast to bypass protected member
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
final dynamic tagDynamic = tag;
|
|
|
|
|
|
|
|
final Map<String, dynamic> tagData = Map<String, dynamic>.from(tagDynamic.data as Map);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
// For Android, try NfcA technology first
|
|
|
|
// Try NfcA first (most common)
|
|
|
|
if (tagData.containsKey('nfca')) {
|
|
|
|
final nfcA = NfcAAndroid.from(tag);
|
|
|
|
final nfcaData = tagData['nfca'] as Map<dynamic, dynamic>;
|
|
|
|
if (nfcA != null) {
|
|
|
|
if (nfcaData.containsKey('identifier')) {
|
|
|
|
identifier = nfcA.tag.id.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
final List<int> idBytes = List<int>.from(nfcaData['identifier'] as List);
|
|
|
|
} else {
|
|
|
|
identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
// Fallback to NfcB
|
|
|
|
}
|
|
|
|
final nfcB = NfcBAndroid.from(tag);
|
|
|
|
}
|
|
|
|
if (nfcB != null) {
|
|
|
|
// Fallback to other technologies if NfcA is not available
|
|
|
|
identifier = nfcB.tag.id.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
if (identifier == null && tagData.containsKey('nfcb')) {
|
|
|
|
|
|
|
|
final nfcbData = tagData['nfcb'] as Map<dynamic, dynamic>;
|
|
|
|
|
|
|
|
if (nfcbData.containsKey('identifier')) {
|
|
|
|
|
|
|
|
final List<int> idBytes = List<int>.from(nfcbData['identifier'] as List);
|
|
|
|
|
|
|
|
identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// For iOS, try MiFare technology
|
|
|
|
// For iOS, try MiFare first
|
|
|
|
if (tagData.containsKey('mifare')) {
|
|
|
|
final mifare = MiFareIos.from(tag);
|
|
|
|
final mifareData = tagData['mifare'] as Map<dynamic, dynamic>;
|
|
|
|
if (mifare != null) {
|
|
|
|
if (mifareData.containsKey('identifier')) {
|
|
|
|
identifier = mifare.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
final List<int> idBytes = List<int>.from(mifareData['identifier'] as List);
|
|
|
|
} else {
|
|
|
|
identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
// Fallback to Iso15693 for iOS
|
|
|
|
|
|
|
|
final iso15693 = Iso15693Ios.from(tag);
|
|
|
|
|
|
|
|
if (iso15693 != null) {
|
|
|
|
|
|
|
|
identifier = iso15693.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Fallback to iso15693 for iOS
|
|
|
|
|
|
|
|
if (identifier == null && tagData.containsKey('iso15693')) {
|
|
|
|
|
|
|
|
final iso15693Data = tagData['iso15693'] as Map<dynamic, dynamic>;
|
|
|
|
|
|
|
|
if (iso15693Data.containsKey('identifier')) {
|
|
|
|
|
|
|
|
final List<int> idBytes = List<int>.from(iso15693Data['identifier'] as List);
|
|
|
|
|
|
|
|
identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Universal fallback: try Ndef if available
|
|
|
|
|
|
|
|
if (identifier == null && tagData.containsKey('ndef')) {
|
|
|
|
|
|
|
|
final ndefData = tagData['ndef'] as Map<dynamic, dynamic>;
|
|
|
|
|
|
|
|
if (ndefData.containsKey('identifier')) {
|
|
|
|
|
|
|
|
final List<int> idBytes = List<int>.from(ndefData['identifier'] as List);
|
|
|
|
|
|
|
|
identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
print('Error reading NFC tag: $e');
|
|
|
|
print('Error reading NFC tag: $e');
|
|
|
|
@ -99,20 +96,32 @@ class _NfcLayoutState extends State<NfcLayout> {
|
|
|
|
|
|
|
|
|
|
|
|
nfcId = identifier;
|
|
|
|
nfcId = identifier;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
setState(() {
|
|
|
|
_reading = true;
|
|
|
|
_reading = true;
|
|
|
|
mainWidget = doneNfc();
|
|
|
|
mainWidget = doneNfc();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
Future.delayed(const Duration(seconds: 1), () {
|
|
|
|
Future.delayed(const Duration(seconds: 1), () async {
|
|
|
|
NfcManager.instance.stopSession();
|
|
|
|
try {
|
|
|
|
Navigator.pop(context);
|
|
|
|
await NfcManager.instance.stopSession();
|
|
|
|
widget.onNcfScan(nfcId);
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
print('Error stopping session: $e');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
Navigator.pop(context);
|
|
|
|
|
|
|
|
widget.onNcfScan(nfcId);
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pollingOptions: {NfcPollingOption.iso14443},
|
|
|
|
pollingOptions: {NfcPollingOption.iso14443},
|
|
|
|
).catchError((err) {
|
|
|
|
).catchError((err) {
|
|
|
|
print(err);
|
|
|
|
print('NFC session error: $err');
|
|
|
|
|
|
|
|
// Stop session on error
|
|
|
|
|
|
|
|
NfcManager.instance.stopSession().catchError((e) {
|
|
|
|
|
|
|
|
print('Error stopping session after error: $e');
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -166,9 +175,15 @@ class _NfcLayoutState extends State<NfcLayout> {
|
|
|
|
borderRadius: BorderRadius.circular(6),
|
|
|
|
borderRadius: BorderRadius.circular(6),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
child: TextButton(
|
|
|
|
child: TextButton(
|
|
|
|
onPressed: () {
|
|
|
|
onPressed: () async {
|
|
|
|
NfcManager.instance.stopSession();
|
|
|
|
try {
|
|
|
|
Navigator.pop(context);
|
|
|
|
await NfcManager.instance.stopSession();
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
print('Error stopping NFC session: $e');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
Navigator.pop(context);
|
|
|
|
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// elevation: 0,
|
|
|
|
// elevation: 0,
|
|
|
|
child: Text("CANCEL"),
|
|
|
|
child: Text("CANCEL"),
|
|
|
|
|