399 lines
13 KiB
Dart
399 lines
13 KiB
Dart
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:gwid/api_service.dart';
|
||
import 'package:gwid/models/profile.dart';
|
||
import 'package:gwid/phone_entry_screen.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>();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_firstNameController = TextEditingController(
|
||
text: widget.myProfile?.firstName ?? '',
|
||
);
|
||
_lastNameController = TextEditingController(
|
||
text: widget.myProfile?.lastName ?? '',
|
||
);
|
||
_descriptionController = TextEditingController(
|
||
text: widget.myProfile?.description ?? '',
|
||
);
|
||
}
|
||
|
||
void _saveProfile() {
|
||
if (!_formKey.currentState!.validate()) {
|
||
return;
|
||
}
|
||
|
||
ApiService.instance.updateProfileText(
|
||
_firstNameController.text.trim(),
|
||
_lastNameController.text.trim(),
|
||
_descriptionController.text.trim(),
|
||
);
|
||
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text("Профиль успешно сохранен"),
|
||
behavior: SnackBarBehavior.floating,
|
||
duration: Duration(seconds: 2),
|
||
),
|
||
);
|
||
}
|
||
|
||
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,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void _pickAndUpdateProfilePhoto() async {
|
||
final ImagePicker picker = ImagePicker();
|
||
|
||
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
|
||
|
||
|
||
if (image != null) {
|
||
|
||
File imageFile = File(image.path);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text("Фотография профиля обновляется..."),
|
||
behavior: SnackBarBehavior.floating,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
@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) {
|
||
return Center(
|
||
child: GestureDetector(
|
||
|
||
onTap: _pickAndUpdateProfilePhoto, // 2. Вызываем метод при нажатии
|
||
child: Stack(
|
||
children: [
|
||
CircleAvatar(
|
||
radius: 60,
|
||
backgroundColor: theme.colorScheme.secondaryContainer,
|
||
backgroundImage: widget.myProfile?.photoBaseUrl != null
|
||
? NetworkImage(widget.myProfile!.photoBaseUrl!)
|
||
: null,
|
||
child: widget.myProfile?.photoBaseUrl == null
|
||
? Icon(
|
||
Icons.person,
|
||
size: 60,
|
||
color: theme.colorScheme.onSecondaryContainer,
|
||
)
|
||
: null,
|
||
),
|
||
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();
|
||
}
|
||
}
|