Chepuhagram/lib/presentation/screens/forward_contact_picker_scre...

300 lines
11 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:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '/core/constants.dart';
import '/data/models/message_model.dart';
import '/data/models/contact_model.dart';
import '/logic/contact_provider.dart';
import '/domain/services/api_service.dart';
class ForwardContactPickerScreen extends StatefulWidget {
final MessageModel message;
const ForwardContactPickerScreen({
super.key,
required this.message,
});
@override
State<ForwardContactPickerScreen> createState() => _ForwardContactPickerScreenState();
}
class _ForwardContactPickerScreenState extends State<ForwardContactPickerScreen> {
Contact? _selectedContact;
bool _isInitLoading = true;
SharedPreferences? _prefs;
String? token;
@override
void initState() {
super.initState();
_loadActiveChats();
}
Future<void> _loadActiveChats() async {
try {
final contactProvider = context.read<ContactProvider>();
await contactProvider.loadContacts();
final apiService = ApiService();
final accessToken = await apiService.getAccessToken();
final shared = await SharedPreferences.getInstance();
if (mounted) {
setState(() {
_prefs = shared;
token = accessToken;
});
}
} catch (e) {
debugPrint("Ошибка при загрузке данных для пересылки: $e");
} finally {
if (mounted) {
setState(() {
_isInitLoading = false;
});
}
}
}
String _getDisplayName(Contact contact) {
if (_prefs == null) return contact.name;
final id = contact.id;
final savedName = _prefs!.getString('firstname_$id');
if (savedName != null && savedName.isNotEmpty) {
return savedName;
}
return contact.name;
}
String _formatTime(DateTime? time) {
if (time == null) return '';
final localTime = time.toLocal();
final hour = localTime.hour.toString().padLeft(2, '0');
final minute = localTime.minute.toString().padLeft(2, '0');
return '$hour:$minute';
}
@override
Widget build(BuildContext context) {
final contactProvider = context.watch<ContactProvider>();
final contacts = contactProvider.contacts;
final isLoading = _isInitLoading || contactProvider.isLoading;
final primaryColor = Theme.of(context).colorScheme.primary;
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_rounded),
onPressed: () => Navigator.of(context).pop(),
),
title: const Text(
'Переслать...',
style: TextStyle(fontWeight: FontWeight.w600),
),
actions: [
AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: _selectedContact != null ? 1.0 : 0.4,
child: TextButton(
onPressed: _selectedContact != null
? () => Navigator.of(context).pop(_selectedContact)
: null,
child: const Text(
'Продолжить',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(width: 8),
],
),
body: () {
if (isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (contactProvider.error != null) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
'Ошибка: ${contactProvider.error}',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.grey),
),
),
);
}
if (contacts.isEmpty) {
return const Center(
child: Text(
'Нет активных чатов для пересылки.',
style: TextStyle(color: Colors.grey, fontSize: 15),
),
);
}
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
final contact = contacts[index];
final isSelected = _selectedContact?.id == contact.id;
// Логика формирования текста сообщения (1-в-1 как в твоем ContactTile)
final bool isDecrypted = contact.isLastMsgDecrypted ?? false;
final String subtitleText = isDecrypted
? (contact.lastMessage == null
? "Нет сообщений"
: "${contact.lastMessageType != null ? MessageModel.getMediaPreview(contact.lastMessageType!) : ''} ${contact.lastMessage}".trim())
: (contact.lastMessage != null
? "Ожидание дешифровки..."
: "Нет сообщений");
// Логика формирования URL аватарки
final avatarUrl = contact.effectiveAvatarUrl;
final bool hasAvatar = avatarUrl != null && avatarUrl.isNotEmpty;
return InkWell(
onTap: () {
setState(() {
if (isSelected) {
_selectedContact = null;
} else {
_selectedContact = contact;
}
});
},
child: Container(
color: isSelected ? primaryColor.withOpacity(0.08) : Colors.transparent,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
// 1. АВАТАРКА
leading: Stack(
children: [
if (hasAvatar)
CircleAvatar(
radius: 24,
backgroundColor: Colors.grey[200],
child: ClipOval(
child: CachedNetworkImage(
imageUrl: avatarUrl,
width: 48,
height: 48,
fit: BoxFit.cover,
httpHeaders: token != null ? {'Authorization': 'Bearer $token'} : null,
placeholder: (context, url) => const CircularProgressIndicator(strokeWidth: 2),
errorWidget: (context, url, error) => CircleAvatar(
radius: 24,
backgroundColor: primaryColor.withOpacity(0.1),
child: Text(
_getDisplayName(contact).isNotEmpty ? _getDisplayName(contact)[0].toUpperCase() : '?',
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
),
),
),
),
)
else
CircleAvatar(
radius: 24,
backgroundColor: primaryColor.withOpacity(0.1),
child: Text(
_getDisplayName(contact).isNotEmpty ? _getDisplayName(contact)[0].toUpperCase() : '?',
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
),
),
if (contact.isOnline == true)
Positioned(
right: 0,
bottom: 0,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
border: Border.all(color: Theme.of(context).scaffoldBackgroundColor, width: 2),
),
),
),
],
),
// 2. ИМЯ
title: Text(
_getDisplayName(contact),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
),
// 3. ПОСЛЕДНЕЕ СООБЩЕНИЕ
subtitle: Text(
subtitleText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.grey),
),
// 4. ПРАВАЯ ЧАСТЬ (Анимация переключения Время <-> Галочка)
trailing: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: isSelected
? Container(
key: const ValueKey('checkmark'),
width: 24,
height: 24,
decoration: BoxDecoration(
color: primaryColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.check_rounded, color: Colors.white, size: 16),
)
: Column(
key: const ValueKey('time_and_badge'),
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Text(
_formatTime(contact.lastMessageTime),
style: const TextStyle(color: Colors.grey, fontSize: 12),
),
if (contact.unreadCount > 0) ...[
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: primaryColor.withAlpha((0.5 * 255).round()),
shape: BoxShape.circle,
),
child: Text(
'${contact.unreadCount}',
style: const TextStyle(color: Colors.white, fontSize: 10),
),
),
],
],
),
),
),
),
);
},
);
}(),
);
}
}