import 'package:flutter/material.dart'; import 'package:nfc_manager/nfc_manager.dart'; import 'dart:typed_data'; import 'google_sheets_api.dart'; class InscriptionPage extends StatefulWidget { final String? prefilledMac; const InscriptionPage({super.key, this.prefilledMac}); @override State createState() => _InscriptionPageState(); } class _InscriptionPageState extends State { final _formKey = GlobalKey(); final TextEditingController _numController = TextEditingController(); final TextEditingController _firstController = TextEditingController(); final TextEditingController _lastController = TextEditingController(); final TextEditingController _macController = TextEditingController(); final TextEditingController _qrController = TextEditingController(); bool _hasNfc = false; bool _isScanning = false; // Couleurs final Color _primaryColor = const Color(0xFF4361EE); final Color _successColor = const Color(0xFF06D6A0); @override void initState() { super.initState(); if (widget.prefilledMac != null) { _macController.text = widget.prefilledMac!; _hasNfc = true; } } @override void dispose() { _numController.dispose(); _firstController.dispose(); _lastController.dispose(); _macController.dispose(); _qrController.dispose(); super.dispose(); } 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(); } } Future _scanNfcAndFill() async { setState(() => _isScanning = true); bool available = await NfcManager.instance.isAvailable(); if (!available) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('NFC non disponible'), backgroundColor: Colors.orange, behavior: SnackBarBehavior.floating, ), ); setState(() => _isScanning = false); return; } NfcManager.instance.startSession( pollingOptions: {NfcPollingOption.iso14443, NfcPollingOption.iso15693}, onDiscovered: (NfcTag tag) async { try { dynamic data; try { data = (tag as dynamic).data; } catch (_) { data = null; } String uid = ''; try { final id = (data is Map && data.containsKey('id')) ? data['id'] : (tag as dynamic).id; uid = _bytesToUid(id); } catch (_) { try { uid = _bytesToUid((tag as dynamic).id); } catch (_) { uid = ''; } } if (uid.isNotEmpty) { _macController.text = uid; _hasNfc = true; } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur lecture NFC: $e'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } await NfcManager.instance.stopSession(); setState(() => _isScanning = false); }, ); } Future _submit() async { if (!_formKey.currentState!.validate()) return; final num = _numController.text.trim(); final first = _firstController.text.trim(); final last = _lastController.text.trim(); final mac = _macController.text.trim(); final qr = _qrController.text.trim(); final nfc = _hasNfc ? 'oui' : 'non'; final date = DateTime.now().toIso8601String(); final row = [num, first, last, mac, nfc, qr, date]; final api = GoogleSheetsApi(); final ok = await api.appendData(row); if (ok) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('✅ Étudiant enregistré avec succès'), backgroundColor: _successColor, behavior: SnackBarBehavior.floating, ), ); Navigator.of(context).pop(true); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('❌ Échec de l\'enregistrement'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF8F9FA), appBar: AppBar( title: const Text( 'Inscription Étudiant', style: TextStyle(fontWeight: FontWeight.bold), ), backgroundColor: _primaryColor, foregroundColor: Colors.white, elevation: 0, ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Padding( padding: const EdgeInsets.all(24), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // En-tête du formulaire Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: _primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Icon(Icons.person_add_alt_1_rounded, color: _primaryColor), const SizedBox(width: 12), const Expanded( child: Text( 'Informations étudiant', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ], ), ), const SizedBox(height: 24), // Champs du formulaire _buildTextField( controller: _numController, label: "N° Étudiant", icon: Icons.badge_rounded, validator: (v) => (v == null || v.trim().isEmpty) ? 'Veuillez saisir le numéro' : null, ), const SizedBox(height: 16), _buildTextField( controller: _firstController, label: 'Prénom', icon: Icons.person_rounded, validator: (v) => (v == null || v.trim().isEmpty) ? 'Veuillez saisir le prénom' : null, ), const SizedBox(height: 16), _buildTextField( controller: _lastController, label: 'Nom', icon: Icons.person_outline_rounded, validator: (v) => (v == null || v.trim().isEmpty) ? 'Veuillez saisir le nom' : null, ), const SizedBox(height: 24), // Section NFC Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.withOpacity(0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'IDENTIFICATION NFC', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.grey.shade600, letterSpacing: 1, ), ), const SizedBox(height: 12), _buildTextField( controller: _macController, label: 'MAC (UID NFC)', icon: Icons.nfc_rounded, ), const SizedBox(height: 12), Row( children: [ Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: _hasNfc ? _successColor.withOpacity(0.1) : Colors.grey.withOpacity(0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: _hasNfc ? _successColor : Colors.grey, ), ), child: Row( children: [ Checkbox( value: _hasNfc, onChanged: (v) => setState(() => _hasNfc = v ?? false), activeColor: _successColor, ), Text( 'NFC détecté', style: TextStyle( color: _hasNfc ? _successColor : Colors.grey, fontWeight: FontWeight.w500, ), ), ], ), ), const Spacer(), ElevatedButton.icon( onPressed: _isScanning ? null : _scanNfcAndFill, icon: _isScanning ? SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Icon(Icons.nfc_rounded, size: 18), label: Text(_isScanning ? 'Scan...' : 'Scanner'), // ← TEXTE RACCOURCI style: ElevatedButton.styleFrom( backgroundColor: _primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), // ← PADDING RÉDUIT ), ), ], ), ], ), ), const SizedBox(height: 16), _buildTextField( controller: _qrController, label: 'QR Code (optionnel)', icon: Icons.qr_code_rounded, ), const SizedBox(height: 32), // Bouton de soumission Container( height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: _primaryColor.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: ElevatedButton.icon( onPressed: _submit, icon: const Icon(Icons.save_rounded, size: 24), label: const Text( 'ENREGISTRER L\'ÉTUDIANT', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), style: ElevatedButton.styleFrom( backgroundColor: _primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), ], ), ), ), ), ), ); } Widget _buildTextField({ required TextEditingController controller, required String label, required IconData icon, String? Function(String?)? validator, }) { return TextFormField( controller: controller, decoration: InputDecoration( labelText: label, prefixIcon: Icon(icon, color: _primaryColor), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: _primaryColor, width: 2), ), filled: true, fillColor: Colors.white, ), validator: validator, ); } }