import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:chepuhagram/logic/auth_provider.dart'; import 'package:chepuhagram/presentation/screens/contacts_screen.dart'; import 'package:chepuhagram/domain/services/crypto_service.dart'; import 'package:chepuhagram/domain/services/api_service.dart'; import 'package:chepuhagram/presentation/screens/account_setup_screen.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:chepuhagram/core/constants.dart'; import 'package:chepuhagram/core/theme_manager.dart'; import 'dart:io'; class KeyRecoveryScreen extends StatefulWidget { const KeyRecoveryScreen({super.key}); @override State createState() => _KeyRecoveryScreenState(); } class _KeyRecoveryScreenState extends State with SingleTickerProviderStateMixin { bool _isLoading = false; String? _errorMessage; final _passwordController = TextEditingController(); final _formKey = GlobalKey(); late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 900), ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeIn, )); _slideAnimation = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( CurvedAnimation( parent: _animationController, curve: Curves.fastOutSlowIn, )); _animationController.forward(); } Future _startFresh() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final authProvider = context.read(); try { final api = ApiService(); await api.deleteAllMessages(); } catch (e) { print('Ошибка при удалении сообщений: $e'); } await authProvider.resetKeys(); if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const AccountSetupScreen()), ); } } catch (e) { if (mounted) { setState(() { _errorMessage = 'Ошибка: ${e.toString()}'; _isLoading = false; }); } } } Future _recoverKeys() async { if (!_formKey.currentState!.validate()) return; FocusScope.of(context).unfocus(); setState(() { _isLoading = true; _errorMessage = null; }); try { final authProvider = context.read(); final apiService = ApiService(); final cryptoService = CryptoService(); final token = await apiService.getAccessToken(); if (token == null) throw Exception('Не авторизован'); final response = await http.get( Uri.parse('${AppConstants.baseUrl}/users/me'), headers: {'Authorization': 'Bearer $token'}, ); if (response.statusCode != 200) { throw Exception('Не удалось получить данные пользователя'); } final data = jsonDecode(utf8.decode(response.bodyBytes)) as Map; final encryptedPrivateKey = data['encrypted_private_key']; if (encryptedPrivateKey == null || encryptedPrivateKey.isEmpty) { throw Exception('Зашифрованный ключ не найден на сервере'); } final decryptedPrivateKey = await cryptoService.decryptPrivateKey( encryptedPrivateKey, _passwordController.text, ); await cryptoService.savePrivateKey(decryptedPrivateKey); await authProvider.tryAutoLogin(); if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const ContactsScreen()), ); } } catch (e) { if (mounted) { setState(() { _errorMessage = 'Ошибка восстановления: ${e.toString().replaceAll('Exception: ', '')}'; _isLoading = false; }); } } } @override Widget build(BuildContext context) { final themeProv = context.watch(); final colorScheme = Theme.of(context).colorScheme; return Scaffold( body: Stack( children: [ if (themeProv.wallpaperPath != null) Container( decoration: BoxDecoration( image: DecorationImage( image: FileImage(File(themeProv.wallpaperPath!)), fit: BoxFit.cover, ), ), ), if (themeProv.wallpaperPath != null) BackdropFilter( filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15), child: Container( color: Colors.black.withOpacity(0.1), ), ), SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 32), SlideTransition( position: _slideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Column( children: [ Icon( Icons.security_outlined, size: 80, color: colorScheme.primary, ), const SizedBox(height: 24), Text( 'Восстановление ключей', textAlign: TextAlign.center, style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: colorScheme.primary, ), ), const SizedBox(height: 16), Text( 'Вы переустановили приложение или вошли на новом устройстве. Выберите один из вариантов:', textAlign: TextAlign.center, style: TextStyle(fontSize: 16, color: colorScheme.outline), ), ], ), ), ), const SizedBox(height: 32), // Вариант 1: Начать с чистого листа _buildOptionCard( icon: Icons.restart_alt_outlined, title: 'Начать с чистого листа', description: 'Создаются новые ключи шифрования. Старые сообщения не будут расшифрованы.', buttonText: 'Продолжить', onPressed: _startFresh, ), const SizedBox(height: 24), // Вариант 2: Восстановить из облака _buildRecoveryCard(), const SizedBox(height: 24), // Сообщение об ошибке if (_errorMessage != null) Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: colorScheme.errorContainer.withOpacity(0.3), borderRadius: BorderRadius.circular(12), ), child: Text( _errorMessage!, style: TextStyle(color: colorScheme.onErrorContainer, fontWeight: FontWeight.w500), textAlign: TextAlign.center, ), ), ], ), ), ), ], ), ); } Widget _buildOptionCard({ required IconData icon, required String title, required String description, required String buttonText, required VoidCallback onPressed, }) { final colorScheme = Theme.of(context).colorScheme; return ClipRRect( borderRadius: BorderRadius.circular(24), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: colorScheme.surfaceVariant.withOpacity(0.35), borderRadius: BorderRadius.circular(24), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.15), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, color: colorScheme.primary, size: 28), const SizedBox(width: 12), Expanded( child: Text( title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), ], ), const SizedBox(height: 12), Text(description, style: TextStyle(color: colorScheme.outline, fontSize: 14)), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isLoading ? null : onPressed, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, ), child: _isLoading ? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : Text(buttonText, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ), ), ], ), ), ), ); } Widget _buildRecoveryCard() { final colorScheme = Theme.of(context).colorScheme; return ClipRRect( borderRadius: BorderRadius.circular(24), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: colorScheme.surfaceVariant.withOpacity(0.35), borderRadius: BorderRadius.circular(24), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.15), width: 1, ), ), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.cloud_download_outlined, color: colorScheme.primary, size: 28), const SizedBox(width: 12), const Expanded( child: Text( 'Восстановить из облака', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), ], ), const SizedBox(height: 12), Text( 'Введите мастер-пароль для восстановления ключей шифрования', style: TextStyle(color: colorScheme.outline, fontSize: 14), ), const SizedBox(height: 16), _buildTextField( controller: _passwordController, label: "Мастер-пароль", icon: Icons.lock_outline, obscureText: true, validator: (value) => value!.isEmpty ? "Введите мастер-пароль" : null, ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isLoading ? null : _recoverKeys, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, ), child: _isLoading ? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Text('Восстановить', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ), ), ], ), ), ), ), ); } Widget _buildTextField({ required TextEditingController controller, required String label, required IconData icon, bool obscureText = false, String? Function(String?)? validator, }) { final colorScheme = Theme.of(context).colorScheme; return ClipRRect( borderRadius: BorderRadius.circular(16), child: Container( // No blur here, it's inside the card decoration: BoxDecoration( color: colorScheme.surface.withOpacity(0.5), borderRadius: BorderRadius.circular(16), ), child: TextFormField( controller: controller, obscureText: obscureText, validator: validator, decoration: InputDecoration( labelText: label, labelStyle: TextStyle(color: colorScheme.outline), prefixIcon: Icon(icon, color: colorScheme.outline), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), ), ), ), ); } @override void dispose() { _passwordController.dispose(); _animationController.dispose(); super.dispose(); } }