Files
fuckKomet/lib/screens/manage_account_screen.dart

514 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:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gwid/api/api_service.dart';
import 'package:gwid/models/profile.dart';
import 'package:gwid/screens/phone_entry_screen.dart';
import 'package:gwid/services/profile_cache_service.dart';
import 'package:gwid/services/local_profile_manager.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
class ManageAccountScreen extends StatefulWidget {
final Profile? myProfile;
const ManageAccountScreen({super.key, this.myProfile});
@override
State<ManageAccountScreen> createState() => _ManageAccountScreenState();
}
class _ManageAccountScreenState extends State<ManageAccountScreen> {
late final TextEditingController _firstNameController;
late final TextEditingController _lastNameController;
late final TextEditingController _descriptionController;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final ProfileCacheService _profileCache = ProfileCacheService();
final LocalProfileManager _profileManager = LocalProfileManager();
Profile? _actualProfile;
String? _localAvatarPath;
bool _isLoading = false;
@override
void initState() {
super.initState();
_initializeProfileData();
}
Future<void> _initializeProfileData() async {
await _profileManager.initialize();
_actualProfile = await _profileManager.getActualProfile(widget.myProfile);
_firstNameController = TextEditingController(
text: _actualProfile?.firstName ?? '',
);
_lastNameController = TextEditingController(
text: _actualProfile?.lastName ?? '',
);
_descriptionController = TextEditingController(
text: _actualProfile?.description ?? '',
);
final localPath = await _profileManager.getLocalAvatarPath();
if (mounted) {
setState(() {
_localAvatarPath = localPath;
});
}
}
Future<void> _saveProfile() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
final firstName = _firstNameController.text.trim();
final lastName = _lastNameController.text.trim();
final description = _descriptionController.text.trim();
final userId = _actualProfile?.id ?? widget.myProfile?.id ?? 0;
final photoBaseUrl =
_actualProfile?.photoBaseUrl ?? widget.myProfile?.photoBaseUrl;
final photoId = _actualProfile?.photoId ?? widget.myProfile?.photoId ?? 0;
await _profileCache.saveProfileData(
userId: userId,
firstName: firstName,
lastName: lastName,
description: description.isEmpty ? null : description,
photoBaseUrl: photoBaseUrl,
photoId: photoId,
);
_actualProfile = await _profileManager.getActualProfile(widget.myProfile);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Профиль сохранен локально"),
behavior: SnackBarBehavior.floating,
duration: Duration(seconds: 2),
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Ошибка сохранения: $e"),
behavior: SnackBarBehavior.floating,
backgroundColor: Theme.of(context).colorScheme.error,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
void _logout() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Выйти из аккаунта?'),
content: const Text('Вы уверены, что хотите выйти из аккаунта?'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Отмена'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
style: FilledButton.styleFrom(
backgroundColor: Colors.red.shade400,
foregroundColor: Colors.white,
),
child: const Text('Выйти'),
),
],
),
);
if (confirmed == true && mounted) {
try {
await ApiService.instance.logout();
if (mounted) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const PhoneEntryScreen()),
(route) => false,
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Ошибка выхода: $e'),
backgroundColor: Theme.of(context).colorScheme.error,
behavior: SnackBarBehavior.floating,
),
);
}
}
}
}
Future<void> _pickAndUpdateProfilePhoto() async {
try {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 1024,
maxHeight: 1024,
imageQuality: 85,
);
if (image == null) return;
setState(() {
_isLoading = true;
});
File imageFile = File(image.path);
final userId = _actualProfile?.id ?? widget.myProfile?.id ?? 0;
if (userId != 0) {
final localPath = await _profileCache.saveAvatar(imageFile, userId);
if (localPath != null && mounted) {
setState(() {
_localAvatarPath = localPath;
});
_actualProfile = await _profileManager.getActualProfile(
widget.myProfile,
);
}
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Фотография профиля сохранена"),
behavior: SnackBarBehavior.floating,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Ошибка загрузки фото: $e"),
behavior: SnackBarBehavior.floating,
backgroundColor: Theme.of(context).colorScheme.error,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text("Изменить профиль"),
centerTitle: true,
scrolledUnderElevation: 0,
actions: [
TextButton(
onPressed: _saveProfile,
child: const Text(
"Сохранить",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildAvatarSection(theme),
const SizedBox(height: 32),
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Основная информация",
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
TextFormField(
controller: _firstNameController,
maxLength: 60, // Ограничение по символам
decoration: _buildInputDecoration(
"Имя",
Icons.person_outline,
).copyWith(counterText: ""), // Скрываем счетчик
validator: (value) =>
value!.isEmpty ? 'Введите ваше имя' : null,
),
const SizedBox(height: 16),
TextFormField(
controller: _lastNameController,
maxLength: 60, // Ограничение по символам
decoration: _buildInputDecoration(
"Фамилия",
Icons.person_outline,
).copyWith(counterText: ""), // Скрываем счетчик
),
],
),
),
),
const SizedBox(height: 24),
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Дополнительно",
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
TextFormField(
controller: _descriptionController,
maxLines: 4,
maxLength: 400,
decoration: _buildInputDecoration(
"О себе",
Icons.edit_note_outlined,
alignLabel: true,
),
),
],
),
),
),
const SizedBox(height: 24),
if (widget.myProfile != null)
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: Column(
children: [
_buildInfoTile(
icon: Icons.phone_outlined,
title: "Телефон",
subtitle: widget.myProfile!.formattedPhone,
),
const Divider(height: 1),
_buildTappableInfoTile(
icon: Icons.tag,
title: "Ваш ID",
subtitle: widget.myProfile!.id.toString(),
onTap: () {
Clipboard.setData(
ClipboardData(
text: widget.myProfile!.id.toString(),
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('ID скопирован в буфер обмена'),
behavior: SnackBarBehavior.floating,
),
);
},
),
],
),
),
const SizedBox(height: 32),
_buildLogoutButton(),
],
),
),
),
);
}
Widget _buildAvatarSection(ThemeData theme) {
ImageProvider? avatarImage;
if (_localAvatarPath != null) {
avatarImage = FileImage(File(_localAvatarPath!));
} else if (_actualProfile?.photoBaseUrl != null) {
if (_actualProfile!.photoBaseUrl!.startsWith('file://')) {
final path = _actualProfile!.photoBaseUrl!.replaceFirst('file://', '');
avatarImage = FileImage(File(path));
} else {
avatarImage = NetworkImage(_actualProfile!.photoBaseUrl!);
}
} else if (widget.myProfile?.photoBaseUrl != null) {
avatarImage = NetworkImage(widget.myProfile!.photoBaseUrl!);
}
return Center(
child: GestureDetector(
onTap: _pickAndUpdateProfilePhoto,
child: Stack(
children: [
CircleAvatar(
radius: 60,
backgroundColor: theme.colorScheme.secondaryContainer,
backgroundImage: avatarImage,
child: avatarImage == null
? Icon(
Icons.person,
size: 60,
color: theme.colorScheme.onSecondaryContainer,
)
: null,
),
if (_isLoading)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
color: Colors.black54,
shape: BoxShape.circle,
),
child: const Center(
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 3,
),
),
),
),
Positioned(
bottom: 4,
right: 4,
child: Container(
decoration: BoxDecoration(
color: theme.colorScheme.primary,
shape: BoxShape.circle,
),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.camera_alt, color: Colors.white, size: 20),
),
),
),
],
),
),
);
}
InputDecoration _buildInputDecoration(
String label,
IconData icon, {
bool alignLabel = false,
}) {
final prefixIcon = (label == "О себе")
? Padding(
padding: const EdgeInsets.only(bottom: 60), // Смещаем иконку вверх
child: Icon(icon),
)
: Icon(icon);
return InputDecoration(
labelText: label,
prefixIcon: prefixIcon,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
alignLabelWithHint: alignLabel,
);
}
Widget _buildInfoTile({
required IconData icon,
required String title,
required String subtitle,
}) {
return ListTile(
leading: Icon(icon, color: Theme.of(context).colorScheme.primary),
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(subtitle),
);
}
Widget _buildTappableInfoTile({
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
child: ListTile(
leading: Icon(icon, color: Theme.of(context).colorScheme.primary),
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(subtitle),
trailing: const Icon(Icons.copy_outlined, size: 20),
),
);
}
Widget _buildLogoutButton() {
return OutlinedButton.icon(
icon: const Icon(Icons.logout),
label: const Text('Выйти из аккаунта'),
onPressed: _logout,
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red.shade400,
side: BorderSide(color: Colors.red.shade200),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
);
}
@override
void dispose() {
_firstNameController.dispose();
_lastNameController.dispose();
_descriptionController.dispose();
super.dispose();
}
}