inscription_page.dart 13,7 ko
Newer Older
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<InscriptionPage> createState() => _InscriptionPageState();
}

class _InscriptionPageState extends State<InscriptionPage> {
  final _formKey = GlobalKey<FormState>();
  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;

mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé
  // 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<int>.from(raw));
      return bytes.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase()).join('');
  Future<void> _scanNfcAndFill() async {
    setState(() => _isScanning = true);
    bool available = await NfcManager.instance.isAvailable();
    if (!available) {
mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé
      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) {
mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé
          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<void> _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) {
mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: const Text('✅ Étudiant enregistré avec succès'),
          backgroundColor: _successColor,
          behavior: SnackBarBehavior.floating,
        ),
      );
mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('❌ Échec de l\'enregistrement'),
          backgroundColor: Colors.red,
          behavior: SnackBarBehavior.floating,
        ),
      );
mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé
      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,
mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé
                  // 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),
                        ),
                      ),
                    ),
                  ),
mouhamed lamine kebe's avatar
mouhamed lamine kebe a validé

  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,
    );
  }
}