Chepuhagram/lib/presentation/screens/my_profile_screen.dart

396 lines
14 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<MyProfileScreen> createState() => _MyProfileScreenState();
}
class _MyProfileScreenState extends State<MyProfileScreen> {
final ImagePicker _picker = ImagePicker();
bool _isAvatarExpanded = false;
String? privKey;
double? _stableBaseHeight;
bool _avatarInteracted = false;
final GlobalKey _contentKey = GlobalKey();
Future<void> _pickAvatar() async {
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
final success = await context.read<AuthProvider>().updateAvatar(
image.path,
);
if (!success && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ошибка загрузки аватарки')),
);
}
}
}
@override
void initState() {
super.initState();
_loadPrivKey();
}
Future<void> _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<AuthProvider>();
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),
),
],
);
}
}