import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter/services.dart'; import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import '../../core/theme/app_theme.dart'; import '../../core/theme/theme_provider.dart'; import '../../core/widgets/glass_container.dart'; import '../../services/wallet_identity_service.dart'; import '../garage/weapon_list_screen.dart'; import '../tutorial/tutorial_provider.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { final WalletIdentityService _walletService = WalletIdentityService(); String? _identityPhrase; int? _photoCount; bool _isLoadingStats = false; bool _isCheckingStatus = false; bool _isUploadEnabled = false; bool _isBanned = false; String? _banReason; String _serverUrl = 'https://backendia.kevlar.cloud'; @override void initState() { super.initState(); _loadIdentity(); } Future _loadIdentity() async { final phrase = await _walletService.getIdentityPhrase(); final serverUrl = await _walletService.getServerBaseUrl(); final isBanned = await _walletService.isBanned(); final banReason = await _walletService.getBanReason(); final isEnabled = await _walletService.isUploadEnabled(); if (mounted) { setState(() { _identityPhrase = phrase; _serverUrl = serverUrl; _isBanned = isBanned; _banReason = banReason; _isUploadEnabled = isEnabled && !isBanned; }); _fetchStats(phrase); } } Future _fetchStats(String phrase) async { setState(() => _isLoadingStats = true); try { final phraseBytes = utf8.encode(phrase); final walletHash = sha256.convert(phraseBytes).toString(); final baseUrl = await _walletService.getServerBaseUrl(); final response = await http.get( Uri.parse('$baseUrl/api/stats/$walletHash'), headers: {'X-API-KEY': WalletIdentityService.apiKey}, ).timeout( const Duration(seconds: 4), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (mounted) { setState(() { _photoCount = data['stats']['photo_count'] ?? 0; _serverUrl = baseUrl; _isLoadingStats = false; }); } } else { if (mounted) setState(() => _isLoadingStats = false); } } catch (e) { debugPrint('Erreur lors du chargement des statistiques: $e'); if (mounted) setState(() => _isLoadingStats = false); } } /// Action manuelle de l'utilisateur pour vérifier et synchroniser son statut auprès du serveur Future _refreshAccountStatus() async { setState(() => _isCheckingStatus = true); try { final isBanned = await _walletService.syncBanStatus(); final banReason = await _walletService.getBanReason(); final isEnabled = await _walletService.isUploadEnabled(); if (_identityPhrase != null) { await _fetchStats(_identityPhrase!); } if (!mounted) return; setState(() { _isBanned = isBanned; _banReason = banReason; _isUploadEnabled = isEnabled && !isBanned; _isCheckingStatus = false; }); if (isBanned) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.block, color: Colors.white), const SizedBox(width: 10), Expanded( child: Text('Compte toujours suspendu : ${_banReason ?? "Non-respect des règles"}'), ), ], ), backgroundColor: AppTheme.errorColor, duration: const Duration(seconds: 5), ), ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Row( children: [ Icon(Icons.check_circle, color: Colors.white), SizedBox(width: 10), Expanded( child: Text('Statut actualisé : votre compte est actif et autorisé !'), ), ], ), backgroundColor: AppTheme.successColor, duration: Duration(seconds: 4), ), ); } } catch (e) { if (!mounted) return; setState(() => _isCheckingStatus = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Impossible de joindre le serveur : $e'), backgroundColor: AppTheme.errorColor, ), ); } } void _copyToClipboard() { if (_identityPhrase != null) { Clipboard.setData(ClipboardData(text: _identityPhrase!)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Identité copiée dans le presse-papiers'), backgroundColor: AppTheme.successColor, ), ); } } void _showThemeDialog() { showDialog( context: context, builder: (dialogCtx) => Consumer( builder: (context, themeProvider, child) { final isDark = Theme.of(context).brightness == Brightness.dark; final primary = themeProvider.primaryColor; return AlertDialog( backgroundColor: isDark ? AppTheme.darkSurface : AppTheme.lightSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: Text( 'Personnalisation & Thème', style: TextStyle( fontWeight: FontWeight.w800, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'MODE D\'AFFICHAGE', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: primary, letterSpacing: 1.0, ), ), const SizedBox(height: 8), _buildThemeOption(themeProvider, ThemeMode.system, 'Automatique (Système)', Icons.brightness_auto), _buildThemeOption(themeProvider, ThemeMode.light, 'Clair', Icons.light_mode_outlined), _buildThemeOption(themeProvider, ThemeMode.dark, 'Sombre (Stand de Tir)', Icons.dark_mode_outlined), const Divider(height: 24), Text( 'COULEUR D\'ACCENTUATION', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: primary, letterSpacing: 1.0, ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: AppAccentColor.allAccents.map((accent) { final isSelected = themeProvider.currentAccent.id == accent.id; return GestureDetector( onTap: () { themeProvider.setAccent(accent); }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7), decoration: BoxDecoration( color: accent.color.withValues(alpha: isSelected ? 0.22 : 0.08), borderRadius: BorderRadius.circular(10), border: Border.all( color: isSelected ? accent.color : Colors.transparent, width: 1.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 14, height: 14, decoration: BoxDecoration( color: accent.color, shape: BoxShape.circle, boxShadow: [ if (isSelected) BoxShadow( color: accent.color.withValues(alpha: 0.6), blurRadius: 6, ), ], ), ), const SizedBox(width: 6), Text( accent.name, style: TextStyle( fontSize: 12, fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500, color: isSelected ? accent.color : (isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary), ), ), ], ), ), ); }).toList(), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogCtx), child: Text('Fermer', style: TextStyle(color: primary, fontWeight: FontWeight.bold)), ), ], ); }, ), ); } Widget _buildThemeOption( ThemeProvider themeProvider, ThemeMode mode, String label, IconData icon, ) { final isSelected = themeProvider.themeMode == mode; final primary = themeProvider.primaryColor; final isDark = themeProvider.themeMode == ThemeMode.dark || (themeProvider.themeMode == ThemeMode.system && WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark); return ListTile( dense: true, contentPadding: EdgeInsets.zero, leading: Icon(icon, color: isSelected ? primary : (isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary), size: 20), title: Text( label, style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500, color: isSelected ? primary : (isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary), ), ), trailing: isSelected ? Icon(Icons.check_circle, color: primary, size: 20) : null, onTap: () { themeProvider.setThemeMode(mode); }, ); } void _showIdentityDialog() { if (_identityPhrase == null) return; final isDark = Theme.of(context).brightness == Brightness.dark; final primary = Theme.of(context).colorScheme.primary; showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: isDark ? AppTheme.darkSurface : AppTheme.lightSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: Text( 'Votre Identité Unique', textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.w800, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: primary.withValues(alpha: 0.15), shape: BoxShape.circle, ), child: Icon(Icons.security, size: 36, color: primary), ), const SizedBox(height: 16), Text( 'Cette phrase de 15 mots vous identifie de manière unique. Ne la partagez qu\'en cas de besoin.', textAlign: TextAlign.center, style: TextStyle( fontSize: 13, color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary, ), ), const SizedBox(height: 20), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: primary.withValues(alpha: isDark ? 0.15 : 0.08), borderRadius: BorderRadius.circular(12), border: Border.all(color: primary.withValues(alpha: 0.35)), ), child: Text( _identityPhrase!, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700, letterSpacing: 0.5, height: 1.5, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), textAlign: TextAlign.center, ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Fermer', style: TextStyle(color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary)), ), ElevatedButton.icon( icon: const Icon(Icons.copy, size: 18), label: const Text('Copier'), style: ElevatedButton.styleFrom( backgroundColor: primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), onPressed: () { _copyToClipboard(); Navigator.pop(context); }, ), ], ), ); } void _showOptInDisclaimer(bool value) { final isDark = Theme.of(context).brightness == Brightness.dark; final primary = Theme.of(context).colorScheme.primary; if (_isBanned) { showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: isDark ? AppTheme.darkSurface : AppTheme.lightSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: const Row( children: [ Icon(Icons.block, color: AppTheme.errorColor), SizedBox(width: 10), Expanded(child: Text('Programme IA Suspendu')), ], ), content: Text( 'Votre participation au programme d\'entraînement a été suspendue par la modération pour le motif suivant :\n\n' '« ${_banReason ?? "Envois non conformes ou inappropriés"} »\n\n' 'L\'envoi de photos pour l\'entraînement est définitivement désactivé sur cet appareil.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Compris'), ), ], ), ); return; } if (!value) { _walletService.setUploadEnabled(false); setState(() { _isUploadEnabled = false; }); return; } showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( backgroundColor: isDark ? AppTheme.darkSurface : AppTheme.lightSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: const Text('Participer à l\'entraînement IA', textAlign: TextAlign.center), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: primary.withValues(alpha: 0.15), shape: BoxShape.circle, ), child: Icon(Icons.psychology, size: 40, color: primary), ), ), const SizedBox(height: 16), const Text( 'En activant cette option, vous acceptez d\'envoyer vos photos de cibles au serveur d\'entraînement IA.', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), const SizedBox(height: 14), const Text( '📤 Ce qui est envoyé : la photo de la cible, la position des ' 'impacts que vous avez placés, la distance de tir, le calibre et ' 'le nombre de coups prévus.', style: TextStyle(fontSize: 13), ), const SizedBox(height: 10), const Text( '🚫 Ce qui ne l\'est pas : la géolocalisation de la photo (retirée ' 'avant l\'envoi), le nom de votre arme, et le modèle de votre appareil.', style: TextStyle(fontSize: 13), ), const SizedBox(height: 10), const Text( '🔒 Pseudonymat : votre identité est remplacée par un hash ' 'cryptographique. Il reste le même d\'un envoi à l\'autre, afin de ' 'rattacher vos contributions à votre compte (récompenses, ' 'modération, suppression sur demande).', style: TextStyle(fontSize: 13), ), const SizedBox(height: 10), const Text( '🎁 Récompenses : Vos contributions sont enregistrées pour vous donner accès à des fonctionnalités exclusives.', style: TextStyle(fontSize: 13), ), const SizedBox(height: 14), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.errorColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(10), border: Border.all(color: AppTheme.errorColor.withValues(alpha: 0.4)), ), child: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.warning_amber_rounded, color: AppTheme.errorColor, size: 20), SizedBox(width: 8), Text( 'Règles strictes & Bannissement', style: TextStyle( color: AppTheme.errorColor, fontWeight: FontWeight.bold, fontSize: 13, ), ), ], ), SizedBox(height: 6), Text( 'Vous vous engagez à n\'envoyer que de réelles cibles de tir conformes. ' 'Tout envoi de photos non conformes, fausses cibles, images floues ou contenu inapproprié ' 'entraînera le bannissement immédiat et définitif de votre compte. ' 'L\'application perdra définitivement la possibilité d\'envoyer des photos.', style: TextStyle(fontSize: 12, height: 1.4), ), ], ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Refuser', style: TextStyle(color: Colors.grey)), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), onPressed: () { _walletService.setUploadEnabled(true); setState(() { _isUploadEnabled = true; }); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Merci pour votre contribution !'), backgroundColor: AppTheme.successColor, ), ); }, child: const Text('J\'accepte les règles'), ), ], ), ); } void _showEditServerUrlDialog() { final isDark = Theme.of(context).brightness == Brightness.dark; final primary = Theme.of(context).colorScheme.primary; final urlController = TextEditingController(text: _serverUrl); showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: isDark ? AppTheme.darkSurface : AppTheme.lightSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: const Text('Adresse du Serveur IA'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Indiquez l\'URL du serveur backend IA :', style: TextStyle(fontSize: 13, color: Colors.grey), ), const SizedBox(height: 12), TextField( controller: urlController, decoration: const InputDecoration( hintText: 'https://backendia.kevlar.cloud', border: OutlineInputBorder(), ), ), const SizedBox(height: 8), const Text( '💡 Serveur officiel : https://backendia.kevlar.cloud', style: TextStyle(fontSize: 11, color: Colors.grey), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), onPressed: () async { final newUrl = urlController.text.trim(); if (newUrl.isNotEmpty) { await _walletService.setServerBaseUrl(newUrl); setState(() { _serverUrl = newUrl; }); if (_identityPhrase != null) { _fetchStats(_identityPhrase!); } } if (!context.mounted) return; Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Adresse du serveur IA mise à jour'), backgroundColor: AppTheme.successColor, ), ); }, child: const Text('Enregistrer'), ), ], ), ); } void _showReportBugDialog() { final isDark = Theme.of(context).brightness == Brightness.dark; final primary = Theme.of(context).colorScheme.primary; final descController = TextEditingController(); const supportEmail = 'monadressemaildesupport@nomdelapplication.com'; const appVersion = '1.0.3'; final platform = Theme.of(context).platform.name; showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: isDark ? AppTheme.darkSurface : AppTheme.lightSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: const Text('Signaler un bug'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Décrivez le problème : ce que vous faisiez, ce qui était attendu et ce qui s\'est passé.', style: TextStyle(fontSize: 13, color: Colors.grey), ), const SizedBox(height: 12), TextField( controller: descController, maxLines: 5, decoration: const InputDecoration( hintText: 'Ex: l\'application se ferme quand j\'ouvre une session...', border: OutlineInputBorder(), ), ), const SizedBox(height: 12), Text( 'Infos techniques jointes : version $appVersion • $platform', style: const TextStyle(fontSize: 11, color: Colors.grey), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton.icon( icon: const Icon(Icons.copy, size: 18), label: const Text('Copier le rapport'), style: ElevatedButton.styleFrom( backgroundColor: primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), onPressed: () { final desc = descController.text.trim(); final report = StringBuffer() ..writeln('--- Rapport de bug ---') ..writeln('Version : $appVersion') ..writeln('Plateforme : $platform') ..writeln('Date : ${DateTime.now().toIso8601String()}') ..writeln('') ..writeln('Description :') ..writeln(desc.isEmpty ? '(non renseignée)' : desc); Clipboard.setData(ClipboardData(text: report.toString())); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Rapport copié — collez-le dans un email à $supportEmail'), backgroundColor: AppTheme.successColor, duration: Duration(seconds: 5), ), ); }, ), ], ), ); } /// Réactive le didacticiel : toutes les visites guidées sont remises à zéro /// et celle de l'accueil redémarre dès le retour sur l'écran principal. Future _restartTutorial() async { final tutorial = context.read(); final messenger = ScaffoldMessenger.of(context); final navigator = Navigator.of(context); await tutorial.restart(); if (!mounted) return; navigator.pop(); messenger.showSnackBar( const SnackBar( content: Text('Didacticiel réactivé : la visite guidée redémarre.'), backgroundColor: AppTheme.successColor, duration: Duration(seconds: 3), ), ); } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final primary = Theme.of(context).colorScheme.primary; return Scaffold( appBar: AppBar( title: const Text('Paramètres'), ), body: ListView( padding: const EdgeInsets.fromLTRB(16, 12, 16, 40), children: [ _buildSectionHeader('IDENTITÉ & COMPTE', primary), _buildSettingsTile( context: context, icon: Icons.fingerprint, title: 'Identité Wallet', subtitle: _identityPhrase != null ? 'Phrase de 15 mots générée' : 'Génération en cours...', onTap: _showIdentityDialog, ), const SizedBox(height: 20), _buildSectionHeader('PERSONNALISATION & THÈME', primary), Consumer( builder: (context, themeProvider, child) { return GlassContainer( borderRadius: 16, blur: 12, margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(16), glowColor: primary, borderColor: isDark ? primary.withValues(alpha: 0.2) : primary.withValues(alpha: 0.12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InkWell( onTap: _showThemeDialog, borderRadius: BorderRadius.circular(10), child: Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: primary.withValues(alpha: isDark ? 0.2 : 0.12), borderRadius: BorderRadius.circular(12), ), child: Icon(Icons.palette_outlined, color: primary, size: 22), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Apparence', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), const SizedBox(height: 2), Text( '${themeProvider.themeModeName} • ${themeProvider.currentAccent.name}', style: TextStyle( fontSize: 12, color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary, ), ), ], ), ), Icon( Icons.chevron_right, color: isDark ? AppTheme.darkTextMuted : AppTheme.lightTextMuted, ), ], ), ), const SizedBox(height: 14), const Divider(height: 1), const SizedBox(height: 12), Text( 'Couleur d\'accent active :', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary, ), ), const SizedBox(height: 10), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: AppAccentColor.allAccents.map((accent) { final isSelected = themeProvider.currentAccent.id == accent.id; return Padding( padding: const EdgeInsets.only(right: 10), child: GestureDetector( onTap: () => themeProvider.setAccent(accent), child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 36, height: 36, decoration: BoxDecoration( color: accent.color, shape: BoxShape.circle, border: Border.all( color: isSelected ? Colors.white : Colors.transparent, width: 2.5, ), boxShadow: [ if (isSelected) BoxShadow( color: accent.color.withValues(alpha: 0.65), blurRadius: 10, spreadRadius: 1, ), ], ), child: isSelected ? const Icon(Icons.check, color: Colors.white, size: 18) : null, ), ), ); }).toList(), ), ), ], ), ); }, ), const SizedBox(height: 20), _buildSectionHeader('ARMURERIE & MATÉRIEL', primary), _buildSettingsTile( context: context, icon: Icons.shield_outlined, title: 'Mon Armurerie', subtitle: 'Gérer mes armes, calibres et optiques', onTap: () async { await Navigator.push( context, MaterialPageRoute(builder: (_) => const WeaponListScreen()), ); }, ), const SizedBox(height: 20), _buildSectionHeader('PROGRAMME D\'ENTRAÎNEMENT IA', primary), if (_isBanned) ...[ Card( elevation: 0, color: AppTheme.errorColor.withValues(alpha: 0.12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0), side: BorderSide(color: AppTheme.errorColor.withValues(alpha: 0.6), width: 1.5), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ Icon(Icons.block, color: AppTheme.errorColor, size: 22), SizedBox(width: 10), Expanded( child: Text( 'Participation au programme IA suspendue', style: TextStyle( color: AppTheme.errorColor, fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ], ), const SizedBox(height: 8), Text( 'Motif : ${_banReason ?? "Envois d'images non appropriées ou fausses cibles."}', style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600), ), const SizedBox(height: 6), const Text( 'Suite à des envois non conformes, la possibilité de participer au programme d\'entraînement et d\'envoyer des photos a été révoquée pour cet identifiant.', style: TextStyle(fontSize: 12, color: Colors.grey, height: 1.3), ), const SizedBox(height: 14), SizedBox( width: double.infinity, child: ElevatedButton.icon( icon: _isCheckingStatus ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.refresh, size: 18), label: Text( _isCheckingStatus ? 'Vérification...' : 'ACTUALISER MON STATUT', style: const TextStyle(fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.errorColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: _isCheckingStatus ? null : _refreshAccountStatus, ), ), ], ), ), ), ] else ...[ GlassContainer( borderRadius: 16, blur: 10, margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), child: SwitchListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0), title: Text( 'Participer à l\'entraînement IA', style: TextStyle( fontWeight: FontWeight.w700, fontSize: 14, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), subtitle: Text( 'Aidez-nous à améliorer la détection automatique', style: TextStyle( fontSize: 12, color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary, ), ), secondary: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: primary.withValues(alpha: isDark ? 0.2 : 0.12), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.psychology, color: primary, size: 22), ), value: _isUploadEnabled, activeThumbColor: primary, onChanged: _showOptInDisclaimer, ), ), if (_isUploadEnabled) _buildSettingsTile( context: context, icon: Icons.cloud_outlined, title: 'Adresse du Serveur IA', subtitle: _serverUrl, onTap: _showEditServerUrlDialog, ), if (_isUploadEnabled) _buildSettingsTile( context: context, icon: Icons.sync, title: 'Actualiser mon statut', subtitle: _isCheckingStatus ? 'Vérification en cours...' : 'Vérifier l\'état du compte auprès du serveur', onTap: _isCheckingStatus ? () {} : _refreshAccountStatus, ), if (_isUploadEnabled) _buildSettingsTile( context: context, icon: Icons.data_usage, title: 'Photos Exportées', subtitle: _isLoadingStats ? 'Chargement...' : (_photoCount != null ? '$_photoCount photos envoyées à l\'IA' : 'Non disponible'), onTap: () { if (_identityPhrase != null) { _fetchStats(_identityPhrase!); } }, ), ], const SizedBox(height: 20), _buildSectionHeader('AIDE & DIDACTICIEL', primary), Consumer( builder: (context, tutorial, child) { return _buildSettingsTile( context: context, icon: Icons.school_outlined, title: 'Revoir le didacticiel', subtitle: tutorial.hasSeenIntro ? 'Rejouer la visite guidée de l\'application' : 'Visite guidée en attente sur l\'écran d\'accueil', onTap: _restartTutorial, ); }, ), const SizedBox(height: 20), _buildSectionHeader('À PROPOS', primary), _buildSettingsTile( context: context, icon: Icons.info_outline, title: 'Version de l\'application', subtitle: '1.0.3 (Design Tactical & Precision)', onTap: () {}, ), _buildSettingsTile( context: context, icon: Icons.bug_report_outlined, title: 'Signaler un bug', subtitle: 'Aidez-nous à corriger les anomalies', onTap: _showReportBugDialog, ), _buildSettingsTile( context: context, icon: Icons.privacy_tip_outlined, title: 'Politique de confidentialité', onTap: () {}, ), ], ), ); } Widget _buildSectionHeader(String title, Color primary) { return Padding( padding: const EdgeInsets.fromLTRB(4, 8, 4, 10), child: Text( title, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: primary, letterSpacing: 1.0, ), ), ); } Widget _buildSettingsTile({ required BuildContext context, required IconData icon, required String title, String? subtitle, required VoidCallback onTap, }) { final isDark = Theme.of(context).brightness == Brightness.dark; final primary = Theme.of(context).colorScheme.primary; return GlassContainer( borderRadius: 16, blur: 10, margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), onTap: onTap, child: Row( children: [ Container( padding: const EdgeInsets.all(9), decoration: BoxDecoration( color: primary.withValues(alpha: isDark ? 0.2 : 0.12), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: primary, size: 20), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), if (subtitle != null) ...[ const SizedBox(height: 2), Text( subtitle, style: TextStyle( fontSize: 12, color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary, ), ), ], ], ), ), Icon( Icons.chevron_right, color: isDark ? AppTheme.darkTextMuted : AppTheme.lightTextMuted, size: 20, ), ], ), ); } }