Ответ на файлы отображается не как фото а как файл, список чатов теперь не дохлая хуйня, динамично изменяется. Написал левый хуй? Появляется чат сразу, создал группу? Создалось, вышел? Вышел.(+ баг что на desktop режимах отображения при выходах чернеет экран)
This commit is contained in:
@@ -190,6 +190,42 @@ extension ApiServiceChats on ApiService {
|
|||||||
print('Переименовываем группу $chatId в: $newName');
|
print('Переименовываем группу $chatId в: $newName');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Обновляет/добавляет чат в локный кэш `_lastChatsPayload['chats']`,
|
||||||
|
/// чтобы остальные экраны (настройки группы, экран чата и т.п.) сразу
|
||||||
|
/// видели новые поля (admins, options, participants и т.д.).
|
||||||
|
void updateChatInCacheFromJson(Map<String, dynamic> chatJson) {
|
||||||
|
try {
|
||||||
|
// Если кэш ещё не инициализирован (например, сразу после запуска),
|
||||||
|
// создаём минимальную структуру, чтобы новый чат тоже оказался в ней.
|
||||||
|
if (_lastChatsPayload == null) {
|
||||||
|
_lastChatsPayload = {
|
||||||
|
'chats': <dynamic>[],
|
||||||
|
'contacts': <dynamic>[],
|
||||||
|
'profile': null,
|
||||||
|
'presence': null,
|
||||||
|
'config': null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final chats = _lastChatsPayload!['chats'] as List<dynamic>;
|
||||||
|
|
||||||
|
final chatId = chatJson['id'];
|
||||||
|
if (chatId == null) return;
|
||||||
|
|
||||||
|
final existingIndex = chats.indexWhere(
|
||||||
|
(c) => c is Map && c['id'] == chatId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingIndex != -1) {
|
||||||
|
chats[existingIndex] = chatJson;
|
||||||
|
} else {
|
||||||
|
chats.insert(0, chatJson);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Не удалось обновить кэш чатов из chatJson: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Создает/перегенерирует пригласительную ссылку для группы.
|
/// Создает/перегенерирует пригласительную ссылку для группы.
|
||||||
/// Сервер ожидает payload вида:
|
/// Сервер ожидает payload вида:
|
||||||
/// {"chatId": -69330645868731, "revokePrivateLink": true}
|
/// {"chatId": -69330645868731, "revokePrivateLink": true}
|
||||||
@@ -198,10 +234,7 @@ extension ApiServiceChats on ApiService {
|
|||||||
int chatId, {
|
int chatId, {
|
||||||
bool revokePrivateLink = true,
|
bool revokePrivateLink = true,
|
||||||
}) async {
|
}) async {
|
||||||
final payload = {
|
final payload = {"chatId": chatId, "revokePrivateLink": revokePrivateLink};
|
||||||
"chatId": chatId,
|
|
||||||
"revokePrivateLink": revokePrivateLink,
|
|
||||||
};
|
|
||||||
|
|
||||||
print('Создаем пригласительную ссылку для группы $chatId: $payload');
|
print('Создаем пригласительную ссылку для группы $chatId: $payload');
|
||||||
|
|
||||||
@@ -216,7 +249,9 @@ extension ApiServiceChats on ApiService {
|
|||||||
final error = response['payload'];
|
final error = response['payload'];
|
||||||
print('Ошибка создания пригласительной ссылки: $error');
|
print('Ошибка создания пригласительной ссылки: $error');
|
||||||
final message =
|
final message =
|
||||||
error?['localizedMessage'] ?? error?['message'] ?? 'Неизвестная ошибка';
|
error?['localizedMessage'] ??
|
||||||
|
error?['message'] ??
|
||||||
|
'Неизвестная ошибка';
|
||||||
throw Exception(message);
|
throw Exception(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,20 +265,8 @@ extension ApiServiceChats on ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Обновим кэш чатов, если сервер вернул полный объект чата
|
// Обновим кэш чатов, если сервер вернул полный объект чата
|
||||||
try {
|
if (chat != null) {
|
||||||
if (chat != null) {
|
updateChatInCacheFromJson(chat);
|
||||||
final chats = _lastChatsPayload?['chats'] as List<dynamic>?;
|
|
||||||
if (chats != null) {
|
|
||||||
final index = chats.indexWhere(
|
|
||||||
(c) => c is Map && c['id'] == chat['id'],
|
|
||||||
);
|
|
||||||
if (index >= 0) {
|
|
||||||
chats[index] = chat;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Не удалось обновить кэш чатов после создания ссылки: $e');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return link;
|
return link;
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ class Message {
|
|||||||
bool get isDeleted => status == 'DELETED';
|
bool get isDeleted => status == 'DELETED';
|
||||||
bool get isReply => link != null && link!['type'] == 'REPLY';
|
bool get isReply => link != null && link!['type'] == 'REPLY';
|
||||||
bool get isForwarded => link != null && link!['type'] == 'FORWARD';
|
bool get isForwarded => link != null && link!['type'] == 'FORWARD';
|
||||||
|
bool get hasFileAttach =>
|
||||||
|
attaches.any((a) => (a['_type'] ?? a['type']) == 'FILE');
|
||||||
|
|
||||||
bool canEdit(int currentUserId) {
|
bool canEdit(int currentUserId) {
|
||||||
if (isDeleted) return false;
|
if (isDeleted) return false;
|
||||||
|
|||||||
@@ -1693,52 +1693,80 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pageBuilder: (context, animation, secondaryAnimation) => AlertDialog(
|
pageBuilder: (context, animation, secondaryAnimation) {
|
||||||
title: const Text('Очистить историю чата'),
|
bool forAll = false;
|
||||||
content: Text(
|
return StatefulBuilder(
|
||||||
'Вы уверены, что хотите очистить историю чата с ${_currentContact.name}? Это действие нельзя отменить.',
|
builder: (context, setStateDialog) {
|
||||||
),
|
return AlertDialog(
|
||||||
actions: [
|
title: const Text('Очистить историю чата'),
|
||||||
TextButton(
|
content: Column(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: const Text('Отмена'),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
FilledButton(
|
Text(
|
||||||
onPressed: () async {
|
'Вы уверены, что хотите очистить историю чата с ${_currentContact.name}? Это действие нельзя отменить.',
|
||||||
Navigator.of(context).pop();
|
),
|
||||||
try {
|
const SizedBox(height: 12),
|
||||||
await ApiService.instance.clearChatHistory(widget.chatId);
|
CheckboxListTile(
|
||||||
if (mounted) {
|
contentPadding: EdgeInsets.zero,
|
||||||
setState(() {
|
value: forAll,
|
||||||
_messages.clear();
|
onChanged: (value) {
|
||||||
_chatItems.clear();
|
setStateDialog(() {
|
||||||
});
|
forAll = value ?? false;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
});
|
||||||
const SnackBar(
|
},
|
||||||
content: Text('История чата очищена'),
|
title: const Text('Удалить сообщения для всех'),
|
||||||
backgroundColor: Colors.green,
|
),
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
}
|
actions: [
|
||||||
} catch (e) {
|
TextButton(
|
||||||
if (mounted) {
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
child: const Text('Отмена'),
|
||||||
SnackBar(
|
),
|
||||||
content: Text('Ошибка очистки истории: $e'),
|
FilledButton(
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
onPressed: () async {
|
||||||
),
|
Navigator.of(context).pop();
|
||||||
);
|
try {
|
||||||
}
|
await ApiService.instance.clearChatHistory(
|
||||||
}
|
widget.chatId,
|
||||||
},
|
forAll: forAll,
|
||||||
style: FilledButton.styleFrom(
|
);
|
||||||
backgroundColor: Colors.red,
|
if (mounted) {
|
||||||
foregroundColor: Colors.white,
|
setState(() {
|
||||||
),
|
_messages.clear();
|
||||||
child: const Text('Очистить'),
|
_chatItems.clear();
|
||||||
),
|
});
|
||||||
],
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
),
|
const SnackBar(
|
||||||
|
content: Text('История чата очищена'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Ошибка очистки истории: $e'),
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: const Text('Очистить'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1757,54 +1785,88 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pageBuilder: (context, animation, secondaryAnimation) => AlertDialog(
|
pageBuilder: (context, animation, secondaryAnimation) {
|
||||||
title: const Text('Удалить чат'),
|
bool forAll = false;
|
||||||
content: Text(
|
return StatefulBuilder(
|
||||||
'Вы уверены, что хотите удалить чат с ${_currentContact.name}? Это действие нельзя отменить.', //1231231233
|
builder: (context, setStateDialog) {
|
||||||
),
|
return AlertDialog(
|
||||||
actions: [
|
title: const Text('Удалить чат'),
|
||||||
TextButton(
|
content: Column(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: const Text('Отмена'),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
FilledButton(
|
Text(
|
||||||
onPressed: () async {
|
'Вы уверены, что хотите удалить чат с ${_currentContact.name}? Это действие нельзя отменить.',
|
||||||
Navigator.of(context).pop();
|
),
|
||||||
try {
|
const SizedBox(height: 12),
|
||||||
print('Имитация удаления чата ID: ${widget.chatId}');
|
CheckboxListTile(
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
contentPadding: EdgeInsets.zero,
|
||||||
|
value: forAll,
|
||||||
|
onChanged: (value) {
|
||||||
|
setStateDialog(() {
|
||||||
|
forAll = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
title: const Text('Удалить сообщения для всех'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Отмена'),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
try {
|
||||||
|
// Удаляем историю чата (opcode 54)
|
||||||
|
await ApiService.instance.clearChatHistory(
|
||||||
|
widget.chatId,
|
||||||
|
forAll: forAll,
|
||||||
|
);
|
||||||
|
|
||||||
if (mounted) {
|
// Отписываемся от чата (opcode 75)
|
||||||
Navigator.of(context).pop();
|
await ApiService.instance.subscribeToChat(
|
||||||
|
widget.chatId,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
widget.onChatUpdated?.call();
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
widget.onChatUpdated?.call();
|
||||||
const SnackBar(
|
|
||||||
content: Text('Чат удален'),
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
backgroundColor: Colors.green,
|
const SnackBar(
|
||||||
),
|
content: Text('Чат удален'),
|
||||||
);
|
backgroundColor: Colors.green,
|
||||||
}
|
),
|
||||||
} catch (e) {
|
);
|
||||||
if (mounted) {
|
}
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
} catch (e) {
|
||||||
SnackBar(
|
if (mounted) {
|
||||||
content: Text('Ошибка удаления чата: $e'),
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
SnackBar(
|
||||||
),
|
content: Text('Ошибка удаления чата: $e'),
|
||||||
);
|
backgroundColor:
|
||||||
}
|
Theme.of(context).colorScheme.error,
|
||||||
}
|
),
|
||||||
},
|
);
|
||||||
style: FilledButton.styleFrom(
|
}
|
||||||
backgroundColor: Colors.red,
|
}
|
||||||
foregroundColor: Colors.white,
|
},
|
||||||
),
|
style: FilledButton.styleFrom(
|
||||||
child: const Text('Удалить'),
|
backgroundColor: Colors.red,
|
||||||
),
|
foregroundColor: Colors.white,
|
||||||
],
|
),
|
||||||
),
|
child: const Text('Удалить'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3036,7 +3098,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
Text(
|
Text(
|
||||||
_replyingToMessage!.text.isNotEmpty
|
_replyingToMessage!.text.isNotEmpty
|
||||||
? _replyingToMessage!.text
|
? _replyingToMessage!.text
|
||||||
: 'Фото',
|
: (_replyingToMessage!.hasFileAttach
|
||||||
|
? 'Файл'
|
||||||
|
: 'Фото'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
@@ -3402,7 +3466,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
Text(
|
Text(
|
||||||
_replyingToMessage!.text.isNotEmpty
|
_replyingToMessage!.text.isNotEmpty
|
||||||
? _replyingToMessage!.text
|
? _replyingToMessage!.text
|
||||||
: 'Фото',
|
: (_replyingToMessage!.hasFileAttach
|
||||||
|
? 'Файл'
|
||||||
|
: 'Фото'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
|
|||||||
@@ -332,7 +332,10 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
final chatIdValue = payload['chatId'];
|
final chatIdValue = payload['chatId'];
|
||||||
final int? chatId = chatIdValue != null ? chatIdValue as int? : null;
|
final int? chatId = chatIdValue != null ? chatIdValue as int? : null;
|
||||||
|
|
||||||
if (opcode == 272 || opcode == 274) {
|
// Для части опкодов (48, 55, 135, 272, 274) нам не нужен явный chatId в корне
|
||||||
|
// payload, поэтому не отбрасываем их, даже если chatId == null.
|
||||||
|
if (opcode == 272 || opcode == 274 || opcode == 48 || opcode == 55 || opcode == 135) {
|
||||||
|
// продолжаем обработку ниже
|
||||||
} else if (chatId == null) {
|
} else if (chatId == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -341,11 +344,44 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
_setTypingForChat(chatId);
|
_setTypingForChat(chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ответ на отправку сообщения (opcode 64).
|
||||||
|
// Если сервер прислал в payload полный объект chat (как при создании группы
|
||||||
|
// через CONTROL new), добавляем/обновляем чат локально.
|
||||||
|
if (opcode == 64 && cmd == 1 && payload['chat'] is Map<String, dynamic>) {
|
||||||
|
final chatJson = payload['chat'] as Map<String, dynamic>;
|
||||||
|
final newChat = Chat.fromJson(chatJson);
|
||||||
|
|
||||||
|
// Обновляем также глобальный кэш ApiService, чтобы настройки группы и
|
||||||
|
// другие экраны сразу видели корректные права/админов/опции.
|
||||||
|
ApiService.instance.updateChatInCacheFromJson(chatJson);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
final existingIndex =
|
||||||
|
_allChats.indexWhere((chat) => chat.id == newChat.id);
|
||||||
|
|
||||||
|
if (existingIndex != -1) {
|
||||||
|
_allChats[existingIndex] = newChat;
|
||||||
|
} else {
|
||||||
|
final savedIndex = _allChats.indexWhere(_isSavedMessages);
|
||||||
|
final insertIndex = savedIndex >= 0 ? savedIndex + 1 : 0;
|
||||||
|
_allChats.insert(insertIndex, newChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
_filterChats();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Новый входящий месседж (opcode 128) — обновляем последний месседж и
|
||||||
|
// двигаем чат вверх БЕЗ полного рефреша с сервера. Если такого чата ещё нет
|
||||||
|
// (нам написал новый пользователь), создаём его на основе payload.chat.
|
||||||
if (opcode == 128 && chatId != null) {
|
if (opcode == 128 && chatId != null) {
|
||||||
final newMessage = Message.fromJson(payload['message']);
|
final newMessage = Message.fromJson(payload['message']);
|
||||||
ApiService.instance.clearCacheForChat(chatId);
|
ApiService.instance.clearCacheForChat(chatId);
|
||||||
|
|
||||||
final int chatIndex = _allChats.indexWhere((chat) => chat.id == chatId);
|
final int chatIndex = _allChats.indexWhere((chat) => chat.id == chatId);
|
||||||
|
|
||||||
if (chatIndex != -1) {
|
if (chatIndex != -1) {
|
||||||
final oldChat = _allChats[chatIndex];
|
final oldChat = _allChats[chatIndex];
|
||||||
final updatedChat = oldChat.copyWith(
|
final updatedChat = oldChat.copyWith(
|
||||||
@@ -377,6 +413,21 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
}
|
}
|
||||||
_filterChats();
|
_filterChats();
|
||||||
});
|
});
|
||||||
|
} else if (payload['chat'] is Map<String, dynamic>) {
|
||||||
|
// Чат ещё не известен клиенту — создаём его на основе payload.chat.
|
||||||
|
final chatJson = payload['chat'] as Map<String, dynamic>;
|
||||||
|
final newChat = Chat.fromJson(chatJson);
|
||||||
|
|
||||||
|
// Обновляем глобальный кэш ApiService, чтобы дальше во всех экранах
|
||||||
|
// был один и тот же объект чата.
|
||||||
|
ApiService.instance.updateChatInCacheFromJson(chatJson);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
final savedIndex = _allChats.indexWhere(_isSavedMessages);
|
||||||
|
final insertIndex = savedIndex >= 0 ? savedIndex + 1 : 0;
|
||||||
|
_allChats.insert(insertIndex, newChat);
|
||||||
|
_filterChats();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (opcode == 67 && chatId != null) {
|
} else if (opcode == 67 && chatId != null) {
|
||||||
final editedMessage = Message.fromJson(payload['message']);
|
final editedMessage = Message.fromJson(payload['message']);
|
||||||
@@ -446,10 +497,6 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opcode == 129 && chatId != null) {
|
|
||||||
_setTypingForChat(chatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opcode == 132) {
|
if (opcode == 132) {
|
||||||
final bool isOnline = payload['online'] == true;
|
final bool isOnline = payload['online'] == true;
|
||||||
|
|
||||||
@@ -536,10 +583,96 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Создание/обновление группы (opcode 48) — стараемся обновить список чатов
|
||||||
|
// локально по объекту chat из payload, без повторного getChatsAndContacts.
|
||||||
if (opcode == 48) {
|
if (opcode == 48) {
|
||||||
print('Получен ответ на создание группы: $payload');
|
print('Получен ответ на создание/обновление группы: $payload');
|
||||||
|
|
||||||
_refreshChats();
|
final chatJson = payload['chat'] as Map<String, dynamic>?;
|
||||||
|
final chatsJson = payload['chats'] as List<dynamic>?;
|
||||||
|
|
||||||
|
// Приоритет: одиночный chat, дальше — первый из списка chats.
|
||||||
|
Map<String, dynamic>? effectiveChatJson = chatJson;
|
||||||
|
if (effectiveChatJson == null && chatsJson != null && chatsJson.isNotEmpty) {
|
||||||
|
final first = chatsJson.first;
|
||||||
|
if (first is Map<String, dynamic>) {
|
||||||
|
effectiveChatJson = first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectiveChatJson != null) {
|
||||||
|
final newChat = Chat.fromJson(effectiveChatJson);
|
||||||
|
|
||||||
|
// Синхронизируем объект чата и в глобальном кэше ApiService.
|
||||||
|
ApiService.instance.updateChatInCacheFromJson(effectiveChatJson);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
final existingIndex =
|
||||||
|
_allChats.indexWhere((chat) => chat.id == newChat.id);
|
||||||
|
|
||||||
|
if (existingIndex != -1) {
|
||||||
|
_allChats[existingIndex] = newChat;
|
||||||
|
} else {
|
||||||
|
// Вставляем новый чат сразу после "Избранного", если оно есть.
|
||||||
|
final savedIndex = _allChats.indexWhere(_isSavedMessages);
|
||||||
|
final insertIndex = savedIndex >= 0 ? savedIndex + 1 : 0;
|
||||||
|
_allChats.insert(insertIndex, newChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
_filterChats();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: если сервер не прислал chat, обновляемся старым способом.
|
||||||
|
_refreshChats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Изменение параметров чата (rename, invite‑link и т.п.) приходит с opcode 55.
|
||||||
|
// В payload обычно лежит обновленный объект chat.
|
||||||
|
if (opcode == 55 && cmd == 1) {
|
||||||
|
final chatJson = payload['chat'] as Map<String, dynamic>?;
|
||||||
|
if (chatJson != null) {
|
||||||
|
final updatedChat = Chat.fromJson(chatJson);
|
||||||
|
|
||||||
|
// Обновляем глобальный кэш ApiService, чтобы настройки группы и др.
|
||||||
|
// сразу видели новые права/линки/название.
|
||||||
|
ApiService.instance.updateChatInCacheFromJson(chatJson);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
final existingIndex =
|
||||||
|
_allChats.indexWhere((chat) => chat.id == updatedChat.id);
|
||||||
|
|
||||||
|
if (existingIndex != -1) {
|
||||||
|
_allChats[existingIndex] = updatedChat;
|
||||||
|
} else {
|
||||||
|
final savedIndex = _allChats.indexWhere(_isSavedMessages);
|
||||||
|
final insertIndex = savedIndex >= 0 ? savedIndex + 1 : 0;
|
||||||
|
_allChats.insert(insertIndex, updatedChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
_filterChats();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выход из группы: сервер сначала шлёт opcode 135 с chat.status = REMOVED,
|
||||||
|
// а уже потом opcode 58 с CONTROL-сообщением "leave". Для обновления списка
|
||||||
|
// чатов нам важен именно 135.
|
||||||
|
if (opcode == 135 && payload['chat'] is Map<String, dynamic>) {
|
||||||
|
final removedChat = payload['chat'] as Map<String, dynamic>;
|
||||||
|
final int? removedChatId = removedChat['id'] as int?;
|
||||||
|
final String? status = removedChat['status'] as String?;
|
||||||
|
|
||||||
|
if (removedChatId != null && status == 'REMOVED') {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_allChats.removeWhere((chat) => chat.id == removedChatId);
|
||||||
|
_filteredChats.removeWhere((chat) => chat.id == removedChatId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opcode == 272) {
|
if (opcode == 272) {
|
||||||
@@ -672,6 +805,14 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _removeChatLocally(int chatId) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_allChats.removeWhere((c) => c.id == chatId);
|
||||||
|
_filteredChats.removeWhere((c) => c.id == chatId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
final Map<int, Timer> _typingDecayTimers = {};
|
final Map<int, Timer> _typingDecayTimers = {};
|
||||||
final Set<int> _typingChats = {};
|
final Set<int> _typingChats = {};
|
||||||
final Set<int> _onlineChats = {};
|
final Set<int> _onlineChats = {};
|
||||||
@@ -2367,7 +2508,7 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
isChannel: isChannel,
|
isChannel: isChannel,
|
||||||
participantCount: participantCount,
|
participantCount: participantCount,
|
||||||
onChatUpdated: () {
|
onChatUpdated: () {
|
||||||
print('Chat updated, но не обновляем список чатов...');
|
_removeChatLocally(chat.id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2513,7 +2654,9 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
} else if (chat.title?.isNotEmpty == true) {
|
} else if (chat.title?.isNotEmpty == true) {
|
||||||
title = chat.title!;
|
title = chat.title!;
|
||||||
} else {
|
} else {
|
||||||
title = "ID ${otherParticipantId ?? 0}";
|
// Контакт ещё не загружен — показываем плейсхолдер и
|
||||||
|
// параллельно запускаем загрузку.
|
||||||
|
title = "Данные загружаются...";
|
||||||
if (otherParticipantId != null && otherParticipantId != 0) {
|
if (otherParticipantId != null && otherParticipantId != 0) {
|
||||||
_loadMissingContact(otherParticipantId);
|
_loadMissingContact(otherParticipantId);
|
||||||
}
|
}
|
||||||
@@ -4001,7 +4144,8 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
} else if (chat.title?.isNotEmpty == true) {
|
} else if (chat.title?.isNotEmpty == true) {
|
||||||
title = chat.title!;
|
title = chat.title!;
|
||||||
} else {
|
} else {
|
||||||
title = "ID $otherParticipantId";
|
// Контакт ещё не загружен — плейсхолдер и асинхронная подзагрузка.
|
||||||
|
title = "Данные загружаются...";
|
||||||
_loadMissingContact(otherParticipantId);
|
_loadMissingContact(otherParticipantId);
|
||||||
}
|
}
|
||||||
avatarUrl = contact?.photoBaseUrl;
|
avatarUrl = contact?.photoBaseUrl;
|
||||||
@@ -4075,7 +4219,7 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
isChannel: isChannel,
|
isChannel: isChannel,
|
||||||
participantCount: participantCount,
|
participantCount: participantCount,
|
||||||
onChatUpdated: () {
|
onChatUpdated: () {
|
||||||
print('Chat updated, но не обновляем список чатов...');
|
_removeChatLocally(chat.id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -4751,7 +4895,15 @@ class _AddChatsToFolderDialogState extends State<_AddChatsToFolderDialog> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
title: Text(title, style: const TextStyle(fontSize: 16)),
|
title: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontStyle: title == 'Данные загружаются...'
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
subtitle: isGroupChat && chat.participantIds.length > 2
|
subtitle: isGroupChat && chat.participantIds.length > 2
|
||||||
? Text(
|
? Text(
|
||||||
'${chat.participantIds.length} участников',
|
'${chat.participantIds.length} участников',
|
||||||
|
|||||||
Reference in New Issue
Block a user