import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:image_picker/image_picker.dart'; import 'dart:io'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'dart:math'; import '/logic/auth_provider.dart'; import 'account_settings_screen.dart'; import 'settings_screen.dart'; import 'package:cached_network_image/cached_network_image.dart'; class MyProfileScreen extends StatefulWidget { const MyProfileScreen({ super.key, required this.isFromList, this.onHeightMeasured, }); final bool isFromList; final Function(double)? onHeightMeasured; @override State createState() => _MyProfileScreenState(); } class _MyProfileScreenState extends State { final ImagePicker _picker = ImagePicker(); bool _isAvatarExpanded = false; String? privKey; double? _stableBaseHeight; bool _avatarInteracted = false; final GlobalKey _contentKey = GlobalKey(); Future _pickAvatar() async { final XFile? image = await _picker.pickImage(source: ImageSource.gallery); if (image != null) { final success = await context.read().updateAvatar( image.path, ); if (!success && mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Ошибка загрузки аватарки')), ); } } } @override void initState() { super.initState(); _loadPrivKey(); } Future _loadPrivKey() async { final storage = const FlutterSecureStorage(); privKey = await storage.read(key: 'private_key'); if (mounted) { setState(() {}); } } @override Widget build(BuildContext context) { final authProv = context.watch(); final colorScheme = Theme.of(context).colorScheme; final screenWidth = MediaQuery.of(context).size.width; final String fullName = '${authProv.firstName ?? ''} ${authProv.lastName ?? ''}'.trim(); final String username = authProv.username ?? ''; ImageProvider? avatarImage; if (authProv.avatarUrl != null) { avatarImage = CachedNetworkImageProvider(authProv.avatarUrl!); } else if (authProv.avatarPath != null) { avatarImage = FileImage(File(authProv.avatarPath!)); } final initials = (authProv.displayName.isNotEmpty ? authProv.displayName : (username.isNotEmpty ? username : 'U')) .trim() .split(RegExp(r'\s+')) .where((p) => p.isNotEmpty) .take(2) .map((p) => p[0].toUpperCase()) .join(); final double expandedAvatarSize = widget.isFromList ? 340.0 : max(screenWidth, 200); if (widget.onHeightMeasured != null) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; final renderBox = _contentKey.currentContext?.findRenderObject() as RenderBox?; if (renderBox != null) { double currentHeight = renderBox.size.height + kToolbarHeight; // Если пользователь ещё не кликал по аватарке, спокойно обновляем идеальную высоту окна if (!_avatarInteracted) { _stableBaseHeight = currentHeight; widget.onHeightMeasured!(currentHeight); } else if (_stableBaseHeight != null) { // Как только пошёл процесс анимации — жёстко держим первоначальную базовую высоту рамки widget.onHeightMeasured!(_stableBaseHeight!); } } }); } return Scaffold( backgroundColor: colorScheme.background, appBar: (!widget.isFromList) ? AppBar( title: Text( 'Профиль', style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface), ), elevation: 0, backgroundColor: Colors.transparent, iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface), ) : null, body: ListView( key: _contentKey, physics: const BouncingScrollPhysics(), padding: EdgeInsets.zero, shrinkWrap: true, // 1. ДОБАВИТЬ ЭТОТ ПАРАМЕТР children: [ // Анимированный интерактивный аватар LayoutBuilder( builder: (context, constraints) { // Получаем доступную ширину именно этого экрана/панели final panelWidth = constraints.maxWidth; return Center( child: GestureDetector( onTap: () { setState(() { _avatarInteracted = true; _isAvatarExpanded = !_isAvatarExpanded; }); }, child: AnimatedContainer( duration: const Duration(milliseconds: 350), curve: Curves.fastOutSlowIn, width: _isAvatarExpanded ? panelWidth : 130.0, height: _isAvatarExpanded ? panelWidth : 130.0, margin: _isAvatarExpanded ? EdgeInsets.zero : const EdgeInsets.only(top: 16, bottom: 8), decoration: BoxDecoration( // 65.0 — идеальный радиус скругления для круга размером 130 borderRadius: _isAvatarExpanded ? BorderRadius.zero : BorderRadius.circular(65.0), color: colorScheme.primaryContainer.withOpacity(0.4), boxShadow: _isAvatarExpanded ? [] : [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 20, offset: const Offset(0, 10), ), ], image: avatarImage != null ? DecorationImage( image: avatarImage, fit: BoxFit.cover, ) : null, ), child: avatarImage == null ? Center( child: Text( initials.isEmpty ? 'U' : initials, style: TextStyle( fontSize: _isAvatarExpanded ? 80 : 38, fontWeight: FontWeight.w700, color: colorScheme.onPrimaryContainer, ), ), ) : null, ), ), ); }, ), // Имя пользователя Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Text( fullName.isNotEmpty ? fullName : 'Имя не указано', style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, letterSpacing: -0.5, ), textAlign: TextAlign.center, ), ), if (username.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 4, bottom: 24), child: Text( '@$username', style: TextStyle( color: colorScheme.primary, fontSize: 16, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ), // Блок кнопок (Выбрать фото, Изменить, Настройки) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Expanded( child: _buildActionButton( icon: Icons.photo_camera_rounded, label: 'Поставить фото', onTap: _pickAvatar, ), ), const SizedBox(width: 8), Expanded( child: _buildActionButton( icon: Icons.edit_note_rounded, label: 'Изменить', onTap: () => Navigator.push( context, MaterialPageRoute( builder: (_) => const AccountSettingsScreen(), ), ), ), ), const SizedBox(width: 8), Expanded( child: _buildActionButton( icon: Icons.settings_suggest_rounded, label: 'Настройки', onTap: () => Navigator.push( context, MaterialPageRoute( builder: (_) => const SettingsScreen(isFromList: false), ), ), ), ), ], ), ), const SizedBox(height: 32), // Блок с личной информацией Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( decoration: BoxDecoration( color: colorScheme.surfaceVariant.withOpacity(0.3), borderRadius: BorderRadius.circular(24), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.2), ), ), child: Column( children: [ _buildInfoRow( context, Icons.fingerprint_rounded, authProv.currentUserId.toString(), 'ID пользователя', true, ), _buildInfoRow( context, Icons.info_outline_rounded, authProv.about, 'О себе', true, ), _buildInfoRow( context, Icons.phone_android_rounded, authProv.phone, 'Номер телефона', true, ), _buildInfoRow( context, Icons.mail_outline_rounded, authProv.email, 'Электронная почта', true, ), _buildInfoRow( context, Icons.key_rounded, privKey ?? 'Отсутствует', 'Публичный E2EE ключ', false, ), ], ), ), ), const SizedBox(height: 20), ], ), ); } Widget _buildActionButton({ required IconData icon, required String label, required VoidCallback onTap, }) { final colorScheme = Theme.of(context).colorScheme; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(16), child: Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: colorScheme.surfaceVariant.withOpacity(0.4), borderRadius: BorderRadius.circular(16), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.1), ), ), child: Column( children: [ Icon(icon, color: colorScheme.primary, size: 22), const SizedBox(height: 4), Text( label, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, overflow: TextOverflow.ellipsis, ), ), ], ), ), ); } Widget _buildInfoRow( BuildContext context, IconData icon, String? value, String label, bool showDivider, ) { final colorScheme = Theme.of(context).colorScheme; return Column( children: [ ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 4, ), leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: colorScheme.primary.withOpacity(0.08), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: colorScheme.primary, size: 20), ), title: Text( value?.isNotEmpty == true ? value! : 'Не указано', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), subtitle: Text( label, style: TextStyle(fontSize: 12, color: colorScheme.outline), ), ), if (showDivider) Divider( height: 1, indent: 70, color: colorScheme.outlineVariant.withOpacity(0.2), ), ], ); } }