327 lines
15 KiB
Dart
327 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'dart:ui';
|
|
import 'dart:io';
|
|
import '/data/models/message_model.dart';
|
|
import '/data/models/contact_model.dart';
|
|
import '/logic/contact_provider.dart';
|
|
import '/domain/services/api_service.dart';
|
|
import '/core/theme_manager.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 != 'Unknown' ? contact.name : ''} ${contact.surname != 'Unknown' ? contact.surname : ''}'.trim();
|
|
final id = contact.id;
|
|
final savedName = _prefs!.getString('firstname_$id');
|
|
final savedSurname = _prefs!.getString('lastname_$id');
|
|
String? displayName;
|
|
if (savedName != null && savedName.isNotEmpty) {
|
|
displayName = savedName;
|
|
}
|
|
if (savedSurname != null && savedSurname.isNotEmpty) {
|
|
(displayName == null || displayName.isEmpty) ? displayName = savedSurname : displayName += " $savedSurname";
|
|
}
|
|
return displayName ?? '${contact.name != 'Unknown' ? contact.name : ''} ${contact.surname != 'Unknown' ? contact.surname : ''}'.trim();
|
|
}
|
|
|
|
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';
|
|
}
|
|
|
|
String _getInitials(String name) {
|
|
if (name.isEmpty) return '?';
|
|
final names = name.trim().split(RegExp(r'\s+')).where((s) => s.isNotEmpty).toList();
|
|
if (names.length > 1) {
|
|
return (names[0][0] + names[1][0]).toUpperCase();
|
|
} else if (names.isNotEmpty) {
|
|
return names[0][0].toUpperCase();
|
|
}
|
|
return '?';
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final contactProvider = context.watch<ContactProvider>();
|
|
final contacts = contactProvider.contacts;
|
|
final isLoading = _isInitLoading || contactProvider.isLoading;
|
|
final themeProv = context.watch<ThemeProvider>();
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Scaffold(
|
|
extendBodyBehindAppBar: true,
|
|
appBar: AppBar(
|
|
backgroundColor: colorScheme.surface.withOpacity(0.85),
|
|
elevation: 0,
|
|
scrolledUnderElevation: 0,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
|
|
),
|
|
flexibleSpace: ClipRRect(
|
|
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)),
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
|
child: Container(color: Colors.transparent),
|
|
),
|
|
),
|
|
leading: IconButton(
|
|
icon: Icon(Icons.arrow_back_rounded, color: Theme.of(context).colorScheme.onSurface),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
title: Text(
|
|
'Переслать...',
|
|
style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface),
|
|
actions: [
|
|
AnimatedOpacity(
|
|
duration: const Duration(milliseconds: 200),
|
|
opacity: _selectedContact != null ? 1.0 : 0.5,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(right: 8.0),
|
|
child: ElevatedButton(
|
|
onPressed: _selectedContact != null
|
|
? () => Navigator.of(context).pop(_selectedContact)
|
|
: null,
|
|
style: ElevatedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
backgroundColor: colorScheme.primary,
|
|
foregroundColor: colorScheme.onPrimary,
|
|
),
|
|
child: const Text('Далее'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
body: Stack(
|
|
children: [
|
|
SafeArea(
|
|
child: () {
|
|
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: TextStyle(color: colorScheme.outline),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (contacts.isEmpty) {
|
|
return Center(
|
|
child: Text(
|
|
'Нет активных чатов для пересылки.',
|
|
style: TextStyle(color: colorScheme.outline, fontSize: 15),
|
|
),
|
|
);
|
|
}
|
|
|
|
return ListView.builder(
|
|
padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
|
|
itemCount: contacts.length,
|
|
itemBuilder: (context, index) {
|
|
final contact = contacts[index];
|
|
final isSelected = _selectedContact?.id == contact.id;
|
|
|
|
final bool isDecrypted = contact.isLastMsgDecrypted;
|
|
final String subtitleText = isDecrypted
|
|
? (contact.lastMessage == null
|
|
? "Нет сообщений"
|
|
: "${contact.lastMessageType != null ? MessageModel.getMediaPreview(contact.lastMessageType!) : ''} ${contact.lastMessage}".trim())
|
|
: (contact.lastMessage != null
|
|
? "Ожидание дешифровки..."
|
|
: "Нет сообщений");
|
|
|
|
final avatarUrl = contact.effectiveAvatarUrl;
|
|
final bool hasAvatar = avatarUrl != null && avatarUrl.isNotEmpty;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(20),
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 250),
|
|
decoration: BoxDecoration(
|
|
color: isSelected ? colorScheme.primary.withOpacity(0.2) : colorScheme.surfaceVariant.withOpacity(0.4),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color: isSelected ? colorScheme.primary : colorScheme.outlineVariant.withOpacity(0.2),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: () {
|
|
setState(() {
|
|
_selectedContact = isSelected ? null : contact;
|
|
});
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
child: Row(
|
|
children: [
|
|
// Avatar
|
|
CircleAvatar(
|
|
radius: 26,
|
|
backgroundColor: colorScheme.primaryContainer,
|
|
child: hasAvatar
|
|
? ClipOval(
|
|
child: Image.network(
|
|
avatarUrl,
|
|
fit: BoxFit.cover,
|
|
width: 52,
|
|
height: 52,
|
|
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
|
if (loadingProgress == null) return child;
|
|
return Center(
|
|
child: Text(
|
|
_getInitials(_getDisplayName(contact)),
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: colorScheme.onPrimaryContainer),
|
|
),
|
|
);
|
|
},
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Center(
|
|
child: Text(
|
|
_getInitials(_getDisplayName(contact)),
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: colorScheme.onPrimaryContainer),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
)
|
|
: Center(
|
|
child: Text(
|
|
_getInitials(_getDisplayName(contact)),
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: colorScheme.onPrimaryContainer),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
// Name and Message
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
_getDisplayName(contact),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
subtitleText,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(color: colorScheme.outline),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
// Checkmark
|
|
AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 200),
|
|
transitionBuilder: (child, animation) => ScaleTransition(scale: animation, child: child),
|
|
child: isSelected
|
|
? Container(
|
|
key: const ValueKey('checkmark'),
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(color: colorScheme.primary, shape: BoxShape.circle),
|
|
child: const Icon(Icons.check_rounded, color: Colors.white, size: 18),
|
|
)
|
|
:
|
|
Text(
|
|
_formatTime(contact.lastMessageTime),
|
|
key: ValueKey(_formatTime(contact.lastMessageTime)),
|
|
textAlign: TextAlign.end,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(color: colorScheme.outline),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|