import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter/services.dart'; import '../../core/theme/theme_provider.dart'; import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import '../../core/constants/app_constants.dart'; import '../../core/theme/app_theme.dart'; import '../../services/wallet_identity_service.dart'; import '../garage/weapon_list_screen.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')).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: (context) => AlertDialog( title: const Text('Apparence'), content: Column( mainAxisSize: MainAxisSize.min, children: [ _buildThemeOption(ThemeMode.system, 'Automatique', Icons.brightness_auto), _buildThemeOption(ThemeMode.light, 'Clair', Icons.light_mode), _buildThemeOption(ThemeMode.dark, 'Sombre', Icons.dark_mode), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Fermer'), ), ], ), ); } Widget _buildThemeOption(ThemeMode mode, String label, IconData icon) { final themeProvider = context.watch(); final isSelected = themeProvider.themeMode == mode; return ListTile( leading: Icon(icon, color: isSelected ? AppTheme.primaryColor : null), title: Text(label, style: TextStyle( fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? AppTheme.primaryColor : null, )), trailing: isSelected ? const Icon(Icons.check, color: AppTheme.primaryColor) : null, onTap: () { themeProvider.setThemeMode(mode); Navigator.pop(context); }, ); } void _showIdentityDialog() { if (_identityPhrase == null) return; showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Votre Identité Unique', textAlign: TextAlign.center), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.security, size: 48, color: AppTheme.primaryColor), const SizedBox(height: 16), const 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: 14, color: Colors.grey), ), const SizedBox(height: 24), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.withAlpha(20), borderRadius: BorderRadius.circular(8), border: Border.all(color: AppTheme.primaryColor.withAlpha(100)), ), child: Text( _identityPhrase!, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, letterSpacing: 0.5, height: 1.5, ), textAlign: TextAlign.center, ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Fermer'), ), ElevatedButton.icon( icon: const Icon(Icons.copy, size: 18), label: const Text('Copier'), onPressed: () { _copyToClipboard(); Navigator.pop(context); }, ), ], ), ); } void _showOptInDisclaimer(bool value) { if (_isBanned) { showDialog( context: context, builder: (context) => AlertDialog( 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( title: const Text('Participer à l\'entraînement IA', textAlign: TextAlign.center), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Center( child: Icon(Icons.psychology, size: 48, color: AppTheme.primaryColor), ), 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( '🔒 Anonymat Garanti : L\'envoi est totalement anonymisé. Votre identité est remplacée par un hash cryptographique.', 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: AppTheme.primaryColor), 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', style: TextStyle(color: Colors.white)), ), ], ), ); } void _showEditServerUrlDialog() { final urlController = TextEditingController(text: _serverUrl); showDialog( context: context, builder: (context) => AlertDialog( 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: AppTheme.primaryColor), 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', style: TextStyle(color: Colors.white)), ), ], ), ); } // Rapport de bug autonome : on collecte une description + les infos // techniques, et on copie un rapport prêt à coller dans un email de support. void _showReportBugDialog() { final descController = TextEditingController(); const supportEmail = 'monadressemaildesupport@nomdelapplication.com'; const appVersion = '1.0.0'; final platform = Theme.of(context).platform.name; showDialog( context: context, builder: (context) => AlertDialog( 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'), 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), ), ); }, ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Paramètres'), ), body: ListView( padding: const EdgeInsets.all(AppConstants.defaultPadding), children: [ _buildSectionHeader('Identité'), _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, ), Consumer( builder: (context, themeProvider, child) { return _buildSettingsTile( context: context, icon: Icons.color_lens_outlined, title: 'Apparence', subtitle: themeProvider.themeModeName, onTap: _showThemeDialog, ); }, ), const SizedBox(height: 24), _buildSectionHeader('Gestion'), _buildSettingsTile( context: context, icon: Icons.shield_outlined, title: 'Mon Armurerie', subtitle: 'Gérer mes armes et équipements', onTap: () async { await Navigator.push( context, MaterialPageRoute(builder: (_) => const WeaponListScreen()), ); }, ), const SizedBox(height: 24), _buildSectionHeader('Programme d\'Entraînement IA'), if (_isBanned) ...[ Card( elevation: 0, color: AppTheme.errorColor.withValues(alpha: 0.12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.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(8), ), ), onPressed: _isCheckingStatus ? null : _refreshAccountStatus, ), ), ], ), ), ), ] else ...[ Card( elevation: 0, color: Colors.transparent, margin: const EdgeInsets.only(bottom: 8.0), child: SwitchListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), title: const Text('Participer à l\'entraînement IA', style: TextStyle(fontWeight: FontWeight.w500)), subtitle: const Text('Aidez-nous à améliorer la détection (soumis aux règles strictes)', style: TextStyle(fontSize: 12)), secondary: const Icon(Icons.psychology, color: AppTheme.textPrimary), value: _isUploadEnabled, activeThumbColor: AppTheme.primaryColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), side: BorderSide(color: Colors.grey.withAlpha(50), width: 1), ), 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: 24), _buildSectionHeader('À propos'), _buildSettingsTile( context: context, icon: Icons.info_outline, title: 'Version de l\'application', subtitle: '1.0.0', onTap: () {}, ), _buildSettingsTile( context: context, icon: Icons.bug_report_outlined, title: 'Signaler un bug', subtitle: 'Aidez-nous à corriger les problèmes', onTap: _showReportBugDialog, ), _buildSettingsTile( context: context, icon: Icons.privacy_tip_outlined, title: 'Politique de confidentialité', onTap: () {}, ), ], ), ); } Widget _buildSectionHeader(String title) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0), child: Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: AppTheme.primaryColor, ), ), ); } Widget _buildSettingsTile({ required BuildContext context, required IconData icon, required String title, String? subtitle, required VoidCallback onTap, }) { return Card( elevation: 0, color: Colors.transparent, margin: const EdgeInsets.only(bottom: 8.0), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), leading: Icon(icon, color: AppTheme.textPrimary), title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)), subtitle: subtitle != null ? Text(subtitle, style: const TextStyle(fontSize: 12)) : null, trailing: const Icon(Icons.chevron_right, color: Colors.grey), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), side: BorderSide(color: Colors.grey.withAlpha(50), width: 1), ), onTap: onTap, ), ); } }