main.dart 10,1 ko
Newer Older
Papa THIAM's avatar
Papa THIAM a validé
import 'package:flutter/material.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'dart:typed_data';
import 'inscription_page.dart';
import 'sheet_view_page.dart';
Papa THIAM's avatar
Papa THIAM a validé

void main() {
  runApp(const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: SimpleNfcReader(),
  ));
Papa THIAM's avatar
Papa THIAM a validé
}

class SimpleNfcReader extends StatefulWidget {
  const SimpleNfcReader({super.key});
Papa THIAM's avatar
Papa THIAM a validé

  State<SimpleNfcReader> createState() => _SimpleNfcReaderState();
Papa THIAM's avatar
Papa THIAM a validé

class _SimpleNfcReaderState extends State<SimpleNfcReader> {
  bool _isReading = false;
  String _status = 'Appuyez sur le bouton pour lire un tag NFC';
  Map<String, String> _info = {};
Papa THIAM's avatar
Papa THIAM a validé

  Future<void> _startNfc() async {
    bool available = await NfcManager.instance.isAvailable();
    if (!available) {
      setState(() => _status = '❌ NFC non disponible sur cet appareil');
      return;
    }
      _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<String,String> avec des champs additionnels si trouvés.
  Future<Map<String, String>> _tryReadPigeonTag(NfcTag tag) async {
    final Map<String, String> out = {};
    dynamic data;
    try {
      data = (tag as dynamic).data;
    } catch (_) {
      data = null;
    }
Papa THIAM's avatar
Papa THIAM a validé

    // 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 = <int>[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 = <String>[];
        for (int page = 4; page <= 7; page++) {
          final cmd = <int>[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;
Papa THIAM's avatar
Papa THIAM a validé

  Map<String, String> _parseTag(NfcTag tag) {
    final Map<String, String> 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<int>.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<int>) 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;
Papa THIAM's avatar
Papa THIAM a validé
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('📡 Lecteur NFC simple'),
        actions: [
          IconButton(
            icon: const Icon(Icons.person_add),
            tooltip: 'Inscription',
            onPressed: () async {
              final prefMac = _info['Numéro de série'] ?? _info['UID brute'];
              await Navigator.of(context).push(MaterialPageRoute(builder: (_) => InscriptionPage(prefilledMac: prefMac)));
            },
          ),
          IconButton(
            icon: const Icon(Icons.table_chart),
            tooltip: 'Voir sheet',
            onPressed: () async {
              await Navigator.of(context).push(MaterialPageRoute(builder: (_) => const SheetViewPage()));
            },
          )
        ],
      ),
      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'),
              ),
            ),
          ],
Papa THIAM's avatar
Papa THIAM a validé
        ),
      ),
    );
  }