убрал скрол к непрочитанныm GET OUT, статус отправки реакций, более заметная кнопка сохранения фото, возможность выбрать деррикторию для сохранения файлов на пк и телiфоне, совместил отправку файлов и отправку медиав одну кнопку, добавил функционал кнопкам 'добавить в контакты' и написать сообщение который там в этом ну этом ну вы поняли, возможность написать человеку прям с чата(добавил кнопки в то меню где можно редактировать локально контакт), выход не чекал

This commit is contained in:
jganenok
2025-12-04 20:17:22 +07:00
parent 61f0eb349a
commit d344adf035
7 changed files with 533 additions and 253 deletions

View File

@@ -379,6 +379,8 @@ class ChatMessageBubble extends StatelessWidget {
final int? chatId;
final bool isEncryptionPasswordSet;
final String? decryptedText;
// Идёт ли сейчас отправка/удаление реакции для этого сообщения
final bool isReactionSending;
const ChatMessageBubble({
super.key,
@@ -414,6 +416,7 @@ class ChatMessageBubble extends StatelessWidget {
this.chatId,
this.isEncryptionPasswordSet = false,
this.decryptedText,
this.isReactionSending = false,
});
String _formatMessageTime(BuildContext context, int timestamp) {
@@ -1166,17 +1169,32 @@ class ChatMessageBubble extends StatelessWidget {
: textColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'$emoji $count',
style: TextStyle(
fontSize: 12,
fontWeight: isUserReaction
? FontWeight.w600
: FontWeight.w500,
color: isUserReaction
? Theme.of(context).colorScheme.primary
: textColor.withOpacity(0.9),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'$emoji $count',
style: TextStyle(
fontSize: 12,
fontWeight: isUserReaction
? FontWeight.w600
: FontWeight.w500,
color: isUserReaction
? Theme.of(context).colorScheme.primary
: textColor.withOpacity(0.9),
),
),
if (isUserReaction && isReactionSending) ...[
const SizedBox(width: 4),
_RotatingIcon(
icon: Icons.watch_later_outlined,
size: 12,
color: Theme.of(context).brightness == Brightness.dark
? const Color(0xFF9bb5c7)
: const Color(0xFF6b7280),
),
],
],
),
),
);
@@ -3340,10 +3358,10 @@ class ChatMessageBubble extends StatelessWidget {
String? token,
int? chatId,
}) async {
// Initialize progress
FileDownloadProgressService().updateProgress(fileId, 0.0);
try {
// Initialize progress
FileDownloadProgressService().updateProgress(fileId, 0.0);
// Get Downloads directory using helper
final downloadDir = await DownloadPathHelper.getDownloadDirectory();
@@ -5634,10 +5652,22 @@ class _FullScreenPhotoViewerState extends State<FullScreenPhotoViewer> {
onPressed: () => Navigator.of(context).pop(),
),
if (widget.attach != null)
IconButton(
icon: const Icon(Icons.download, color: Colors.white),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white,
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
icon: const Icon(Icons.download),
label: const Text(
'Скачать',
style: TextStyle(fontWeight: FontWeight.w600),
),
onPressed: _downloadPhoto,
tooltip: 'Скачать фото',
),
],
),

View File

@@ -41,7 +41,10 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
final ScrollController _nameScrollController = ScrollController();
String? _localDescription;
StreamSubscription? _changesSubscription;
StreamSubscription? _wsSubscription;
bool _isOpeningChat = false;
bool _isInContacts = false;
bool _isAddingToContacts = false;
String get _displayName {
final displayName = getContactDisplayName(
@@ -64,20 +67,50 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
void initState() {
super.initState();
_loadLocalDescription();
_checkIfInContacts();
_changesSubscription = ContactLocalNamesService().changes.listen((
contactId,
) {
if (contactId == widget.userId && mounted) {
_loadLocalDescription();
_checkIfInContacts();
}
});
_wsSubscription = ApiService.instance.messages.listen((msg) {
try {
if (msg['opcode'] == 34 &&
msg['cmd'] == 1 &&
msg['payload'] != null &&
msg['payload']['contact'] != null) {
final contactJson = msg['payload']['contact'] as Map<String, dynamic>;
final id = contactJson['id'] as int?;
if (id == widget.userId && mounted) {
final contact = Contact.fromJson(contactJson);
ApiService.instance.updateContactCache([contact]);
setState(() {
_isInContacts = true;
});
}
}
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkNameLength();
});
}
Future<void> _checkIfInContacts() async {
final cached = ApiService.instance.getCachedContact(widget.userId);
if (mounted) {
setState(() {
_isInContacts = cached != null;
});
}
}
Future<void> _loadLocalDescription() async {
final localData = await ContactLocalNamesService().getContactData(
widget.userId,
@@ -92,6 +125,7 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
@override
void dispose() {
_changesSubscription?.cancel();
_wsSubscription?.cancel();
_nameScrollController.dispose();
super.dispose();
}
@@ -234,14 +268,24 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
_buildActionButton(
icon: Icons.phone,
label: 'Позвонить',
onPressed: null,
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Звонков пока нету'),
behavior: SnackBarBehavior.floating,
),
);
},
colors: colors,
),
_buildActionButton(
icon: Icons.person_add,
label: 'В контакты',
onPressed: null,
label: _isInContacts ? 'В контактах' : 'В контакты',
onPressed: _isInContacts || _isAddingToContacts
? null
: _handleAddToContacts,
colors: colors,
isLoading: _isAddingToContacts,
),
_buildActionButton(
icon: Icons.message,
@@ -386,4 +430,46 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
}
}
}
Future<void> _handleAddToContacts() async {
if (_isAddingToContacts || _isInContacts) return;
setState(() {
_isAddingToContacts = true;
});
try {
// Отправляем opcode=34 с action="ADD"
await ApiService.instance.addContact(widget.userId);
// Пытаемся сразу подтянуть обновлённые данные контакта
await ApiService.instance.requestContactsByIds([widget.userId]);
await _checkIfInContacts();
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,
),
);
}
} finally {
if (mounted) {
setState(() {
_isAddingToContacts = false;
});
}
}
}
}