339 lines
11 KiB
Dart
339 lines
11 KiB
Dart
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 '/logic/auth_provider.dart';
|
||
import 'account_settings_screen.dart';
|
||
import 'settings_screen.dart';
|
||
|
||
class MyProfileScreen extends StatefulWidget {
|
||
const MyProfileScreen({super.key, required this.isFromList});
|
||
final bool isFromList;
|
||
|
||
@override
|
||
State<MyProfileScreen> createState() => _MyProfileScreenState();
|
||
}
|
||
|
||
class _MyProfileScreenState extends State<MyProfileScreen> {
|
||
final ImagePicker _picker = ImagePicker();
|
||
bool _isAvatarExpanded = false;
|
||
String? privKey;
|
||
|
||
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 = NetworkImage(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();
|
||
|
||
return Scaffold(
|
||
backgroundColor: colorScheme.background,
|
||
appBar: (Platform.isWindows || !widget.isFromList)
|
||
? AppBar(
|
||
title: const Text(
|
||
'Профиль',
|
||
style: TextStyle(fontWeight: FontWeight.bold),
|
||
),
|
||
elevation: 0,
|
||
backgroundColor: Colors.transparent,
|
||
)
|
||
: null,
|
||
body: ListView(
|
||
physics: const BouncingScrollPhysics(),
|
||
padding: EdgeInsets.zero,
|
||
children: [
|
||
// Анимированный интерактивный аватар
|
||
GestureDetector(
|
||
onTap: () => setState(() => _isAvatarExpanded = !_isAvatarExpanded),
|
||
child: AnimatedContainer(
|
||
duration: const Duration(milliseconds: 350),
|
||
curve: Curves.fastOutSlowIn,
|
||
width: _isAvatarExpanded ? screenWidth : 130.0,
|
||
height: _isAvatarExpanded ? screenWidth : 130.0,
|
||
margin: _isAvatarExpanded
|
||
? EdgeInsets.zero
|
||
: const EdgeInsets.only(top: 16, bottom: 8),
|
||
decoration: BoxDecoration(
|
||
shape: _isAvatarExpanded ? BoxShape.rectangle : BoxShape.circle,
|
||
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: 13, fontWeight: FontWeight.w600),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
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),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|