Chepuhagram/lib/presentation/screens/settings_screen.dart

463 lines
16 KiB
Dart
Raw 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:chepuhagram/presentation/screens/account_settings_screen.dart';
import 'package:chepuhagram/presentation/screens/login_screen.dart';
import 'package:chepuhagram/presentation/screens/privacy_settings_menu_screen.dart';
import 'package:chepuhagram/presentation/screens/appearance_settings_screen.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '/logic/auth_provider.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'admin_panel_screen.dart';
import 'package:chepuhagram/presentation/screens/sessions_screen.dart';
import 'package:cached_network_image/cached_network_image.dart';
class SettingsScreen extends StatefulWidget {
final bool isFromList;
final Function(double)? onHeightMeasured;
const SettingsScreen({
super.key,
this.isFromList = true,
this.onHeightMeasured,
});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
String? versionCode;
final ImagePicker _picker = ImagePicker();
bool _isAvatarExpanded = false;
double? _stableBaseHeight;
bool _avatarInteracted = false;
final GlobalKey _contentKey = GlobalKey();
@override
void initState() {
super.initState();
_loadVersion();
}
void _loadVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
if (mounted) {
setState(() {
versionCode = packageInfo.version;
});
}
}
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
Widget build(BuildContext context) {
final authProv = context.watch<AuthProvider>();
final colorScheme = Theme.of(context).colorScheme;
final screenWidth = MediaQuery.of(context).size.width;
String platformName = Platform.isAndroid
? 'Android'
: Platform.isIOS
? 'iOS'
: Platform.isWindows
? 'Windows'
: Platform.isLinux
? 'Linux'
: Platform.isMacOS
? 'macOS'
: 'Unknown';
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 : screenWidth;
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),
),
iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface),
elevation: 0,
backgroundColor: Colors.transparent,
)
: null,
body: ListView(
key: _contentKey,
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
// Анимированный интерактивный аватар как в MyProfileScreen
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(
// Ипользуем BorderRadius вместо BoxShape для плавной анимации формы
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
: (authProv.displayName.isNotEmpty
? authProv.displayName
: 'Имя не указано'),
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,
),
)
else
const SizedBox(height: 24),
// Секция навигации меню (Сгруппированный контейнер)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
decoration: BoxDecoration(
color: colorScheme.surfaceVariant.withOpacity(0.2),
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: colorScheme.outlineVariant.withOpacity(0.1),
),
),
child: Column(
children: [
_buildMenuTile(
context,
Icons.person_outline_rounded,
'Аккаунт',
'Имя, телефон, почта, о себе',
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const AccountSettingsScreen(),
),
),
),
_buildDivider(context),
_buildMenuTile(
context,
Icons.shield_outlined,
'Конфиденциальность',
'Безопасность и видимость данных',
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const PrivacySettingsMenuScreen(),
),
),
),
_buildDivider(context),
_buildMenuTile(
context,
Icons.palette_outlined,
'Оформление',
'Тема, цвета, обои чата',
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const AppearanceSettingsScreen(),
),
),
),
_buildDivider(context),
_buildMenuTile(
context,
Icons.devices_rounded,
'Устройства',
'Управление активными сессиями',
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
const SessionsScreen(), // Переходим на созданный экран
),
),
),
],
),
),
),
if (authProv.currentUserId == 1) ...[
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: colorScheme.primary.withOpacity(0.2),
),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 4,
),
leading: Icon(
Icons.admin_panel_settings_rounded,
color: colorScheme.primary,
),
title: Text(
"Админ-панель",
style: TextStyle(
color: colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
subtitle: const Text("Управление пользователями системы"),
trailing: Icon(
Icons.chevron_right_rounded,
color: colorScheme.primary,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const AdminPanelScreen(),
),
);
},
),
),
),
],
const SizedBox(height: 16),
// Кнопка выхода из аккаунта
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
decoration: BoxDecoration(
color: colorScheme.errorContainer.withOpacity(0.15),
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: colorScheme.errorContainer.withOpacity(0.2),
),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 4,
),
leading: authProv.isLoading
? SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
strokeWidth: 2.5,
valueColor: AlwaysStoppedAnimation<Color>(colorScheme.error),
),
)
: Icon(Icons.logout_rounded, color: colorScheme.error),
title: Text(
authProv.isLoading ? "Выход из аккаунта..." : "Выйти из аккаунта",
style: TextStyle(
color: colorScheme.error,
fontWeight: FontWeight.w600,
),
),
trailing: authProv.isLoading
? null
: Icon(
Icons.chevron_right_rounded,
color: colorScheme.error,
),
onTap: authProv.isLoading
? null
: () async {
await authProv.logout();
},
),
),
),
const SizedBox(height: 40),
Center(
child: Text(
"Chepuhagram for $platformName v${versionCode ?? '1.0.0'}",
style: TextStyle(color: colorScheme.outline, fontSize: 12),
),
),
const SizedBox(height: 4),
Center(
child: Text(
"Made by ArturKarasevich",
style: TextStyle(
color: colorScheme.outline.withOpacity(0.6),
fontSize: 11,
),
),
),
const SizedBox(height: 40),
],
),
);
}
Widget _buildMenuTile(
BuildContext context,
IconData icon,
String title,
String subtitle,
VoidCallback onTap,
) {
final colorScheme = Theme.of(context).colorScheme;
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
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: 22),
),
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
),
subtitle: Text(
subtitle,
style: TextStyle(color: colorScheme.outline, fontSize: 13),
),
trailing: Icon(Icons.chevron_right_rounded, color: colorScheme.outline),
onTap: onTap,
);
}
Widget _buildDivider(BuildContext context) => Divider(
height: 1,
indent: 68,
color: Theme.of(context).colorScheme.outlineVariant.withOpacity(0.15),
);
}