фух блять, сделал нормальную загрузку чатов, я в такое говно окунулся это пиздец. НАХУЯ НАМ ЗАПРОС КОТОРЫЙ ДАЕТ ТОЛЬКО ИЗБРАННЫЕ НАХУЯ НАМ 3 РАЗНЫХ КОНФЛИКТУЮЩИХ МЕЖДУ СОБОЙ ФУНКЦИИ ДЕЛАЮЩИХ ОДНО И ТОЖЕ НАХУЯ НАМ ПОВТОРНОЕ ПОЛНОЕ ПЕРЕПОДКЛЮЧЕНИЕ ПО СТРИМУ НАХУЯ НАМ ЖДАТЬ ДРУГИЕ ЗАПРОСЫ ЧТОБЫ ПОКАЗАТЬ ЧАТЫ НАХУЯ НАМ КОМЕТА
This commit is contained in:
@@ -326,102 +326,21 @@ extension ApiServiceChats on ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getChatsOnly({bool force = false}) async {
|
Future<Map<String, dynamic>> getChatsOnly({bool force = false}) async {
|
||||||
if (authToken == null) {
|
// Эта функция теперь НЕ делает специальных запросов к серверу
|
||||||
await _loadTokenFromAccountManager();
|
// (вроде opcode 48 с chatIds:[0]) и не "ломает" глобальный кэш чатов.
|
||||||
}
|
//
|
||||||
if (authToken == null) throw Exception("Auth token not found");
|
// Использование:
|
||||||
|
// - без force: просто возвращаем последний снапшот, который уже
|
||||||
|
// был получен через getChatsAndContacts / кэш;
|
||||||
|
// - с force: пробрасываем запрос в getChatsAndContacts(force: true),
|
||||||
|
// чтобы получить полный список чатов.
|
||||||
|
|
||||||
if (!force && _lastChatsPayload != null && _lastChatsAt != null) {
|
if (!force && _lastChatsPayload != null) {
|
||||||
if (DateTime.now().difference(_lastChatsAt!) < _chatsCacheTtl) {
|
|
||||||
return _lastChatsPayload!;
|
return _lastChatsPayload!;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await _ensureCacheServicesInitialized();
|
// Если нужно именно "обновить" — вызываем полноценную синхронизацию.
|
||||||
|
return getChatsAndContacts(force: true);
|
||||||
if (!force && _lastChatsPayload == null) {
|
|
||||||
final cachedChats = await _chatCacheService.getCachedChats();
|
|
||||||
final cachedContacts = await _chatCacheService.getCachedContacts();
|
|
||||||
if (cachedChats != null &&
|
|
||||||
cachedContacts != null &&
|
|
||||||
cachedChats.isNotEmpty) {
|
|
||||||
final result = {
|
|
||||||
'chats': cachedChats,
|
|
||||||
'contacts': cachedContacts.map(_contactToMap).toList(),
|
|
||||||
'profile': null,
|
|
||||||
'presence': null,
|
|
||||||
};
|
|
||||||
_lastChatsPayload = result;
|
|
||||||
_lastChatsAt = DateTime.now();
|
|
||||||
updateContactCache(cachedContacts);
|
|
||||||
_preloadContactAvatars(cachedContacts);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Используем opcode 48 для запроса конкретных чатов
|
|
||||||
// chatIds:[0] - это "Избранное" (Saved Messages)
|
|
||||||
final payload = {
|
|
||||||
"chatIds": [0],
|
|
||||||
};
|
|
||||||
|
|
||||||
final int chatSeq = _sendMessage(48, payload);
|
|
||||||
final chatResponse = await messages.firstWhere(
|
|
||||||
(msg) => msg['seq'] == chatSeq,
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<dynamic> chatListJson =
|
|
||||||
chatResponse['payload']?['chats'] ?? [];
|
|
||||||
|
|
||||||
if (chatListJson.isEmpty) {
|
|
||||||
final result = {'chats': [], 'contacts': [], 'profile': null};
|
|
||||||
_lastChatsPayload = result;
|
|
||||||
_lastChatsAt = DateTime.now();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
final contactIds = <int>{};
|
|
||||||
for (var chatJson in chatListJson) {
|
|
||||||
final participants =
|
|
||||||
chatJson['participants'] as Map<String, dynamic>? ?? {};
|
|
||||||
contactIds.addAll(participants.keys.map((id) => int.parse(id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<dynamic> contactListJson = [];
|
|
||||||
if (contactIds.isNotEmpty) {
|
|
||||||
final int contactSeq = _sendMessage(32, {
|
|
||||||
"contactIds": contactIds.toList(),
|
|
||||||
});
|
|
||||||
final contactResponse = await messages.firstWhere(
|
|
||||||
(msg) => msg['seq'] == contactSeq,
|
|
||||||
);
|
|
||||||
contactListJson = contactResponse['payload']?['contacts'] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = {
|
|
||||||
'chats': chatListJson,
|
|
||||||
'contacts': contactListJson,
|
|
||||||
'profile': null,
|
|
||||||
'presence': null,
|
|
||||||
};
|
|
||||||
_lastChatsPayload = result;
|
|
||||||
|
|
||||||
final List<Contact> contacts = contactListJson
|
|
||||||
.map((json) => Contact.fromJson(json as Map<String, dynamic>))
|
|
||||||
.toList();
|
|
||||||
updateContactCache(contacts);
|
|
||||||
_lastChatsAt = DateTime.now();
|
|
||||||
_preloadContactAvatars(contacts);
|
|
||||||
unawaited(
|
|
||||||
_chatCacheService.cacheChats(chatListJson.cast<Map<String, dynamic>>()),
|
|
||||||
);
|
|
||||||
unawaited(_chatCacheService.cacheContacts(contacts));
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
print('Ошибка получения чатов через opcode 48: $e');
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getChatsAndContacts({bool force = false}) async {
|
Future<Map<String, dynamic>> getChatsAndContacts({bool force = false}) async {
|
||||||
|
|||||||
@@ -224,7 +224,14 @@ extension ApiServiceConnection on ApiService {
|
|||||||
if (_channel == null) {
|
if (_channel == null) {
|
||||||
throw Exception('WebSocket is not connected. Connect first.');
|
throw Exception('WebSocket is not connected. Connect first.');
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(jsonString) as Map<String, dynamic>;
|
||||||
|
final opcode = decoded['opcode'];
|
||||||
|
final payload = decoded['payload'];
|
||||||
|
_log('➡️ SEND: opcode=$opcode, payload=$payload');
|
||||||
|
} catch (_) {
|
||||||
_log('➡️ SEND (raw): $jsonString');
|
_log('➡️ SEND (raw): $jsonString');
|
||||||
|
}
|
||||||
_channel!.sink.add(jsonString);
|
_channel!.sink.add(jsonString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +257,7 @@ extension ApiServiceConnection on ApiService {
|
|||||||
|
|
||||||
final encodedMessage = jsonEncode(message);
|
final encodedMessage = jsonEncode(message);
|
||||||
|
|
||||||
_log('➡️ SEND (custom): $encodedMessage');
|
_log('➡️ SEND: opcode=${message['opcode']}, payload=${message['payload']}');
|
||||||
print('Отправляем кастомное сообщение (seq: $currentSeq): $encodedMessage');
|
print('Отправляем кастомное сообщение (seq: $currentSeq): $encodedMessage');
|
||||||
|
|
||||||
_channel!.sink.add(encodedMessage);
|
_channel!.sink.add(encodedMessage);
|
||||||
@@ -273,19 +280,8 @@ extension ApiServiceConnection on ApiService {
|
|||||||
final encodedMessage = jsonEncode(message);
|
final encodedMessage = jsonEncode(message);
|
||||||
if (opcode == 1) {
|
if (opcode == 1) {
|
||||||
_log('➡️ SEND (ping) seq: $_seq');
|
_log('➡️ SEND (ping) seq: $_seq');
|
||||||
} else if (opcode == 18 || opcode == 19) {
|
|
||||||
Map<String, dynamic> loggablePayload = Map.from(payload);
|
|
||||||
if (loggablePayload.containsKey('token')) {
|
|
||||||
String token = loggablePayload['token'] as String;
|
|
||||||
|
|
||||||
loggablePayload['token'] = token.length > 8
|
|
||||||
? '${token.substring(0, 4)}...${token.substring(token.length - 4)}'
|
|
||||||
: '***';
|
|
||||||
}
|
|
||||||
final loggableMessage = {...message, 'payload': loggablePayload};
|
|
||||||
_log('➡️ SEND: ${jsonEncode(loggableMessage)}');
|
|
||||||
} else {
|
} else {
|
||||||
_log('➡️ SEND: $encodedMessage');
|
_log('➡️ SEND: opcode=$opcode, payload=$payload');
|
||||||
}
|
}
|
||||||
print('Отправляем сообщение (seq: $_seq): $encodedMessage');
|
print('Отправляем сообщение (seq: $_seq): $encodedMessage');
|
||||||
_channel!.sink.add(encodedMessage);
|
_channel!.sink.add(encodedMessage);
|
||||||
@@ -293,47 +289,35 @@ extension ApiServiceConnection on ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _listen() async {
|
void _listen() async {
|
||||||
_streamSubscription?.cancel();
|
if (_channel == null) {
|
||||||
_streamSubscription = _channel?.stream.listen(
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если уже есть активная подписка на текущий канал, не создаём вторую.
|
||||||
|
if (_streamSubscription != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_streamSubscription = _channel!.stream.listen(
|
||||||
(message) {
|
(message) {
|
||||||
if (message == null) return;
|
if (message == null) return;
|
||||||
if (message is String && message.trim().isEmpty) {
|
if (message is String && message.trim().isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String loggableMessage = message;
|
|
||||||
try {
|
try {
|
||||||
final decoded = jsonDecode(message) as Map<String, dynamic>;
|
final decoded = jsonDecode(message) as Map<String, dynamic>;
|
||||||
if (decoded['opcode'] == 2) {
|
final opcode = decoded['opcode'];
|
||||||
|
if (opcode == 2) {
|
||||||
_healthMonitor.onPongReceived();
|
_healthMonitor.onPongReceived();
|
||||||
loggableMessage = '⬅️ RECV (pong) seq: ${decoded['seq']}';
|
_log('⬅️ RECV (pong) seq: ${decoded['seq']}');
|
||||||
} else {
|
} else {
|
||||||
Map<String, dynamic> loggableDecoded = Map.from(decoded);
|
final payload = decoded['payload'];
|
||||||
bool wasModified = false;
|
_log('⬅️ RECV: opcode=$opcode, payload=$payload');
|
||||||
if (loggableDecoded.containsKey('payload') &&
|
|
||||||
loggableDecoded['payload'] is Map) {
|
|
||||||
Map<String, dynamic> payload = Map.from(
|
|
||||||
loggableDecoded['payload'],
|
|
||||||
);
|
|
||||||
if (payload.containsKey('token')) {
|
|
||||||
String token = payload['token'] as String;
|
|
||||||
payload['token'] = token.length > 8
|
|
||||||
? '${token.substring(0, 4)}...${token.substring(token.length - 4)}'
|
|
||||||
: '***';
|
|
||||||
loggableDecoded['payload'] = payload;
|
|
||||||
wasModified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wasModified) {
|
|
||||||
loggableMessage = '⬅️ RECV: ${jsonEncode(loggableDecoded)}';
|
|
||||||
} else {
|
|
||||||
loggableMessage = '⬅️ RECV: $message';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
loggableMessage = '⬅️ RECV (raw): $message';
|
_log('⬅️ RECV (raw): $message');
|
||||||
}
|
}
|
||||||
_log(loggableMessage);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final decodedMessage = message is String
|
final decodedMessage = message is String
|
||||||
|
|||||||
@@ -99,10 +99,17 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
StreamSubscription<String>? _connectionStateSubscription;
|
StreamSubscription<String>? _connectionStateSubscription;
|
||||||
bool _isAccountsExpanded = false;
|
bool _isAccountsExpanded = false;
|
||||||
|
|
||||||
late SharedPreferences prefs;
|
SharedPreferences? _prefs;
|
||||||
|
|
||||||
Future<void> _initializePrefs() async {
|
Future<void> _initializePrefs() async {
|
||||||
prefs = await SharedPreferences.getInstance();
|
final p = await SharedPreferences.getInstance();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_prefs = p;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_prefs = p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -126,8 +133,6 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_listenForUpdates();
|
_listenForUpdates();
|
||||||
|
|
||||||
_searchAnimationController = AnimationController(
|
_searchAnimationController = AnimationController(
|
||||||
@@ -165,7 +170,6 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
_loadChannels();
|
_loadChannels();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final prefs = SharedPreferences.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -334,7 +338,11 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
|
|
||||||
// Для части опкодов (48, 55, 135, 272, 274) нам не нужен явный chatId в корне
|
// Для части опкодов (48, 55, 135, 272, 274) нам не нужен явный chatId в корне
|
||||||
// payload, поэтому не отбрасываем их, даже если chatId == null.
|
// payload, поэтому не отбрасываем их, даже если chatId == null.
|
||||||
if (opcode == 272 || opcode == 274 || opcode == 48 || opcode == 55 || opcode == 135) {
|
if (opcode == 272 ||
|
||||||
|
opcode == 274 ||
|
||||||
|
opcode == 48 ||
|
||||||
|
opcode == 55 ||
|
||||||
|
opcode == 135) {
|
||||||
// продолжаем обработку ниже
|
// продолжаем обработку ниже
|
||||||
} else if (chatId == null) {
|
} else if (chatId == null) {
|
||||||
return;
|
return;
|
||||||
@@ -357,8 +365,9 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
final existingIndex =
|
final existingIndex = _allChats.indexWhere(
|
||||||
_allChats.indexWhere((chat) => chat.id == newChat.id);
|
(chat) => chat.id == newChat.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (existingIndex != -1) {
|
if (existingIndex != -1) {
|
||||||
_allChats[existingIndex] = newChat;
|
_allChats[existingIndex] = newChat;
|
||||||
@@ -593,7 +602,9 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
|
|
||||||
// Приоритет: одиночный chat, дальше — первый из списка chats.
|
// Приоритет: одиночный chat, дальше — первый из списка chats.
|
||||||
Map<String, dynamic>? effectiveChatJson = chatJson;
|
Map<String, dynamic>? effectiveChatJson = chatJson;
|
||||||
if (effectiveChatJson == null && chatsJson != null && chatsJson.isNotEmpty) {
|
if (effectiveChatJson == null &&
|
||||||
|
chatsJson != null &&
|
||||||
|
chatsJson.isNotEmpty) {
|
||||||
final first = chatsJson.first;
|
final first = chatsJson.first;
|
||||||
if (first is Map<String, dynamic>) {
|
if (first is Map<String, dynamic>) {
|
||||||
effectiveChatJson = first;
|
effectiveChatJson = first;
|
||||||
@@ -607,8 +618,9 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
ApiService.instance.updateChatInCacheFromJson(effectiveChatJson);
|
ApiService.instance.updateChatInCacheFromJson(effectiveChatJson);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
final existingIndex =
|
final existingIndex = _allChats.indexWhere(
|
||||||
_allChats.indexWhere((chat) => chat.id == newChat.id);
|
(chat) => chat.id == newChat.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (existingIndex != -1) {
|
if (existingIndex != -1) {
|
||||||
_allChats[existingIndex] = newChat;
|
_allChats[existingIndex] = newChat;
|
||||||
@@ -640,8 +652,9 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
ApiService.instance.updateChatInCacheFromJson(chatJson);
|
ApiService.instance.updateChatInCacheFromJson(chatJson);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
final existingIndex =
|
final existingIndex = _allChats.indexWhere(
|
||||||
_allChats.indexWhere((chat) => chat.id == updatedChat.id);
|
(chat) => chat.id == updatedChat.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (existingIndex != -1) {
|
if (existingIndex != -1) {
|
||||||
_allChats[existingIndex] = updatedChat;
|
_allChats[existingIndex] = updatedChat;
|
||||||
@@ -1167,7 +1180,6 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),*/
|
),*/
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundColor: Theme.of(
|
backgroundColor: Theme.of(
|
||||||
@@ -1673,16 +1685,22 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _loadChatsAndContacts() {
|
void _loadChatsAndContacts() {
|
||||||
|
// Берём актуальный снапшот чатов, если он уже есть (_lastChatsPayload),
|
||||||
|
// а если нет — ApiService сам дёрнет opcode 19.
|
||||||
|
final future = ApiService.instance.getChatsOnly();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_chatsFuture = ApiService.instance.getChatsAndContacts(force: true);
|
_chatsFuture = future;
|
||||||
});
|
});
|
||||||
|
|
||||||
_chatsFuture.then((data) {
|
future.then((data) {
|
||||||
if (mounted) {
|
if (!mounted) return;
|
||||||
final chats = data['chats'] as List;
|
|
||||||
final contacts = data['contacts'] as List;
|
final chats = (data['chats'] as List?) ?? const [];
|
||||||
|
final contacts = (data['contacts'] as List?) ?? const [];
|
||||||
final profileData = data['profile'];
|
final profileData = data['profile'];
|
||||||
|
|
||||||
|
setState(() {
|
||||||
_allChats = chats
|
_allChats = chats
|
||||||
.where((json) => json != null)
|
.where((json) => json != null)
|
||||||
.map((json) => Chat.fromJson(json))
|
.map((json) => Chat.fromJson(json))
|
||||||
@@ -1695,14 +1713,12 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (profileData != null) {
|
if (profileData != null) {
|
||||||
setState(() {
|
|
||||||
_myProfile = Profile.fromJson(profileData);
|
_myProfile = Profile.fromJson(profileData);
|
||||||
_isProfileLoading = false;
|
_isProfileLoading = false;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_filterChats();
|
_filterChats();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1944,6 +1960,12 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Если чаты есть, но текущий фильтр/папка не даёт ни одного результата
|
||||||
|
// (например, после переподключения или некорректного фильтра),
|
||||||
|
// то по умолчанию показываем все чаты, а не пустой экран.
|
||||||
|
if (_filteredChats.isEmpty && _allChats.isNotEmpty) {
|
||||||
|
_filteredChats = List.from(_allChats);
|
||||||
|
}
|
||||||
if (_filteredChats.isEmpty && _allChats.isEmpty) {
|
if (_filteredChats.isEmpty && _allChats.isEmpty) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
@@ -3736,7 +3758,7 @@ class _ChatsScreenState extends State<ChatsScreen>
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
if (prefs.getBool('show_sferum_button') ?? true)
|
if ((_prefs?.getBool('show_sferum_button') ?? true))
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Image.asset(
|
icon: Image.asset(
|
||||||
'assets/images/spermum.png',
|
'assets/images/spermum.png',
|
||||||
|
|||||||
Reference in New Issue
Block a user