300 lines
11 KiB
Dart
300 lines
11 KiB
Dart
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),
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}(),
|
||
);
|
||
}
|
||
} |