import 'package:flutter/material.dart'; import 'package:nfc_manager/nfc_manager.dart'; import 'dart:typed_data'; void main() { runApp(const MaterialApp( debugShowCheckedModeBanner: false, home: SimpleNfcReader(), )); } class SimpleNfcReader extends StatefulWidget { const SimpleNfcReader({super.key}); @override State createState() => _SimpleNfcReaderState(); } class _SimpleNfcReaderState extends State { bool _isReading = false; String _status = 'Appuyez sur le bouton pour lire un tag NFC'; Map _info = {}; Future _startNfc() async { bool available = await NfcManager.instance.isAvailable(); if (!available) { setState(() => _status = '❌ NFC non disponible sur cet appareil'); return; } setState(() { _isReading = true; _status = '🔄 Approchez un tag NFC...'; _info.clear(); }); NfcManager.instance.startSession( pollingOptions: { NfcPollingOption.iso14443, NfcPollingOption.iso15693, NfcPollingOption.iso18092, }, onDiscovered: (NfcTag tag) async { try { // Tentative spécifique pour bagues/pigeons (ISO15693 / ISO14443A) final pigeonResults = await _tryReadPigeonTag(tag); final details = _parseTag(tag); setState(() { // Merge results: pigeonResults can overwrite or extend details _info = {...details, ...pigeonResults}; _status = '✅ Tag détecté !'; }); } catch (e) { setState(() { _status = 'Erreur: ${e.toString()}'; }); } await NfcManager.instance.stopSession(); setState(() => _isReading = false); }, ); } /// Essaye de lire des informations utiles pour une "bague pigeon". /// Retourne une Map avec des champs additionnels si trouvés. Future> _tryReadPigeonTag(NfcTag tag) async { final Map out = {}; dynamic data; try { data = (tag as dynamic).data; } catch (_) { data = null; } // UID first try { final idv = _safeLookup(data, 'id'); if (idv != null) out['UID brute'] = _bytesToUid(idv); } catch (_) {} // Detect techs String techsStr = ''; try { if (data is Map) techsStr = (_safeLookup(data, 'techList') ?? _safeLookup(data, 'techs') ?? '').toString(); if (techsStr.isEmpty) { try { final t = (tag as dynamic).techs ?? (tag as dynamic).tech; if (t != null) techsStr = t.toString(); } catch (_) {} } } catch (_) {} final techsLower = techsStr.toLowerCase(); // ISO15693 -> try Read Single Block (flag=0x02: address flag 0x00 / 0x20 depends) if (techsLower.contains('nfcv') || techsLower.contains('iso15693')) { try { // Build a Read Single Block command for ISO15693 (example: flags=0x02, cmd=0x20, block=0) // Frame: [flags, cmd, ...UID (if needed), blockNumber] // Many platforms require UID (8 bytes) after cmd when addressing is used; to keep it simple we try without UID first. final cmd = [0x02, 0x20, 0x00]; // flags=0x02, ReadSingleBlock=0x20, block=0 // Try to call transceive/sendCommand dynamically Uint8List? resp; try { resp = await (tag as dynamic).transceive(Uint8List.fromList(cmd)); } catch (_) { try { resp = await (tag as dynamic).sendCommand(Uint8List.fromList(cmd)); } catch (_) { resp = null; } } if (resp != null && resp.isNotEmpty) { out['ISO15693 block0'] = _bytesToHexString(resp); } } catch (_) {} } // ISO14443A / NTAG -> try read pages (MIFARE Ultralight/NTAG: READ 0x30 page) if (techsLower.contains('nfca') || techsLower.contains('iso14443-3a') || techsLower.contains('iso14443')) { try { // Read pages 4..7 (application/user pages) using READ command 0x30 final results = []; for (int page = 4; page <= 7; page++) { final cmd = [0x30, page]; Uint8List? resp; try { resp = await (tag as dynamic).transceive(Uint8List.fromList(cmd)); } catch (_) { try { resp = await (tag as dynamic).sendCommand(Uint8List.fromList(cmd)); } catch (_) { resp = null; } } if (resp != null && resp.isNotEmpty) { results.add('p$page=${_bytesToHexString(resp)}'); } } if (results.isNotEmpty) out['NTAG pages 4-7'] = results.join(', '); } catch (_) {} } return out; } Map _parseTag(NfcTag tag) { final Map result = {}; dynamic data; try { data = (tag as dynamic).data; } catch (_) { data = null; } // Type try { final t = _safeLookup(data, 'type'); result['Type de tag'] = t?.toString() ?? 'Inconnu'; } catch (_) { result['Type de tag'] = 'Inconnu'; } // Techs try { final techs = _safeLookup(data, 'techList') ?? _safeLookup(data, 'techs') ?? ''; result['Technologies disponibles'] = techs?.toString() ?? ''; } catch (_) { result['Technologies disponibles'] = ''; } // UID try { final id = _safeLookup(data, 'id'); if (id != null) result['Numéro de série'] = _bytesToUid(id); } catch (_) {} // ATQA/SAK/ATS/size try { result['ATQA'] = _asHex(_safeLookup(data, 'atqa')); result['SAK'] = _asHex(_safeLookup(data, 'sak')); result['ATS'] = _asHex(_safeLookup(data, 'ats')); final s = _safeLookup(data, 'size') ?? '---'; result['Informations mémoire'] = '${s} bytes'; } catch (_) { result['ATQA'] = '---'; result['SAK'] = '---'; result['ATS'] = '---'; result['Informations mémoire'] = '---'; } return result; } String _bytesToUid(dynamic raw) { try { final bytes = raw is Uint8List ? raw : Uint8List.fromList(List.from(raw)); return bytes.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase()).join(':'); } catch (_) { return raw.toString(); } } String _asHex(dynamic v) { if (v == null) return '---'; if (v is int) return '0x${v.toRadixString(16).toUpperCase()}'; if (v is Uint8List) { return '0x${v.map((b) => b.toRadixString(16).padLeft(2, '0')).join().toUpperCase()}'; } return v.toString(); } String _bytesToHexString(dynamic maybeBytes) { try { Uint8List bytes; if (maybeBytes is Uint8List) bytes = maybeBytes; else if (maybeBytes is List) bytes = Uint8List.fromList(maybeBytes); else return maybeBytes.toString(); final sb = StringBuffer(); for (final b in bytes) sb.write(b.toRadixString(16).padLeft(2, '0')); return sb.toString().toUpperCase(); } catch (_) { return maybeBytes.toString(); } } /// Safe lookup: try Map access, bracket dynamic (caught), or common property getters. dynamic _safeLookup(dynamic obj, String key) { if (obj == null) return null; try { if (obj is Map) { if (obj.containsKey(key)) return obj[key]; // also try case variations final lower = key.toLowerCase(); if (obj.containsKey(lower)) return obj[lower]; final upper = key.toUpperCase(); if (obj.containsKey(upper)) return obj[upper]; } } catch (_) {} // try bracket access dynamically (may throw NoSuchMethodError) try { return (obj as dynamic)[key]; } catch (_) {} // try common property getters by name try { if (key == 'type') return (obj as dynamic).type; } catch (_) {} try { if (key == 'techs') return (obj as dynamic).techs; } catch (_) {} try { if (key == 'techList') return (obj as dynamic).techList; } catch (_) {} try { if (key == 'id') return (obj as dynamic).id; } catch (_) {} try { if (key == 'atqa') return (obj as dynamic).atqa; } catch (_) {} try { if (key == 'sak') return (obj as dynamic).sak; } catch (_) {} try { if (key == 'ats') return (obj as dynamic).ats; } catch (_) {} try { if (key == 'size') return (obj as dynamic).size; } catch (_) {} return null; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('📡 Lecteur NFC simple')), body: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(_status, style: const TextStyle(fontSize: 16)), const SizedBox(height: 20), Expanded( child: ListView( children: _info.entries.map((e) { return ListTile( title: Text(e.key, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text(e.value), ); }).toList(), ), ), const SizedBox(height: 10), Center( child: ElevatedButton.icon( onPressed: _isReading ? null : _startNfc, icon: const Icon(Icons.nfc), label: Text(_isReading ? 'Lecture en cours...' : 'Lire un tag NFC'), ), ), ], ), ), ); } }