import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../services/reglages.dart'; import '../supabase_config.dart'; import '../theme.dart'; import '../utils/format.dart'; /// Onglet 4 : réglages du programme de fidélité. /// Le réglage clé est le ratio « combien d'euros pour 1 point ». class ReglagesScreen extends StatefulWidget { const ReglagesScreen({super.key}); @override State createState() => _ReglagesScreenState(); } class _ReglagesScreenState extends State { final _reglages = Reglages.instance; late final TextEditingController _ratioCtrl; late final TextEditingController _nomCtrl; @override void initState() { super.initState(); _ratioCtrl = TextEditingController( text: _formaterRatio(_reglages.eurosParPoint), ); _nomCtrl = TextEditingController(text: _reglages.nomMagasin); } @override void dispose() { _ratioCtrl.dispose(); _nomCtrl.dispose(); super.dispose(); } String _formaterRatio(double v) { return (v == v.roundToDouble() ? v.toInt().toString() : v.toString()) .replaceAll('.', ','); } Future _enregistrerRatio() async { final v = double.tryParse(_ratioCtrl.text.replaceAll(',', '.')); if (v == null || v <= 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Valeur invalide (doit être > 0)')), ); _ratioCtrl.text = _formaterRatio(_reglages.eurosParPoint); return; } await _reglages.definirEurosParPoint(v); _ratioCtrl.text = _formaterRatio(v); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Réglage enregistré')), ); } } Future _enregistrerNom() async { await _reglages.definirNomMagasin(_nomCtrl.text); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Réglages')), body: AnimatedBuilder( animation: _reglages, builder: (context, _) { return ListView( padding: const EdgeInsets.all(16), children: [ _titre('Programme de fidélité'), Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Combien d\'euros dépensés pour gagner 1 point ?', style: TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( '1 point =', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _ratioCtrl, textAlign: TextAlign.center, keyboardType: const TextInputType.numberWithOptions( decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'[0-9.,]')), ], onSubmitted: (_) => _enregistrerRatio(), decoration: const InputDecoration( suffixText: '€', hintText: 'ex : 5', ), ), ), ], ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: FilledButton( onPressed: _enregistrerRatio, child: const Text('Enregistrer'), ), ), const SizedBox(height: 16), _apercus(), ], ), ), ), const SizedBox(height: 24), _titre('Magasin'), Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Nom affiché sur le QR code du client', style: TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(height: 12), TextField( controller: _nomCtrl, textCapitalization: TextCapitalization.words, onSubmitted: (_) => _enregistrerNom(), onEditingComplete: _enregistrerNom, decoration: const InputDecoration( hintText: 'Ex : Boulangerie du Coin', prefixIcon: Icon(Icons.storefront_outlined), ), ), ], ), ), ), const SizedBox(height: 24), _titre('À propos'), const Card( child: Padding( padding: EdgeInsets.all(16), child: Text( 'Données partagées via Supabase avec l\'app client. ' 'Le scan se fait avec la caméra ; la scanette externe sera ' 'branchée dans une prochaine version.', style: TextStyle(color: AppTheme.grisTexte, height: 1.4), ), ), ), const SizedBox(height: 24), if (_emailConnecte() != null) Padding( padding: const EdgeInsets.only(bottom: 10), child: Text( 'Connecté en tant que ${_emailConnecte()}', textAlign: TextAlign.center, style: const TextStyle( fontSize: 13, color: AppTheme.grisTexte), ), ), OutlinedButton.icon( onPressed: _deconnexion, style: OutlinedButton.styleFrom( foregroundColor: Colors.red.shade600, minimumSize: const Size.fromHeight(52), side: BorderSide(color: Colors.red.shade200), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), ), icon: const Icon(Icons.logout), label: const Text('Se déconnecter'), ), ], ); }, ), ); } String? _emailConnecte() => supabase.auth.currentUser?.email; Future _deconnexion() async { final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Se déconnecter ?'), content: const Text('Vous devrez ressaisir vos identifiants staff.'), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Annuler'), ), FilledButton( onPressed: () => Navigator.of(ctx).pop(true), child: const Text('Déconnexion'), ), ], ), ); if (ok == true) await supabase.auth.signOut(); } Widget _apercus() { const exemples = [10.0, 20.0, 25.0, 50.0]; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.accent.withValues(alpha: 0.07), borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Aperçu', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppTheme.grisTexte)), const SizedBox(height: 8), for (final e in exemples) Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Facture de ${euro(e)}'), Text('→ ${points(_reglages.pointsPour(e))}', style: const TextStyle( fontWeight: FontWeight.w700, color: AppTheme.accent)), ], ), ), ], ), ); } Widget _titre(String texte) { return Padding( padding: const EdgeInsets.fromLTRB(4, 0, 4, 10), child: Text(texte, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: AppTheme.grisTexte)), ); } }