diff --git a/lib/api/api_service_chats.dart b/lib/api/api_service_chats.dart index cd6383a..edd7534 100644 --- a/lib/api/api_service_chats.dart +++ b/lib/api/api_service_chats.dart @@ -326,102 +326,21 @@ extension ApiServiceChats on ApiService { } Future> getChatsOnly({bool force = false}) async { - if (authToken == null) { - await _loadTokenFromAccountManager(); - } - if (authToken == null) throw Exception("Auth token not found"); + // Эта функция теперь НЕ делает специальных запросов к серверу + // (вроде opcode 48 с chatIds:[0]) и не "ломает" глобальный кэш чатов. + // + // Использование: + // - без force: просто возвращаем последний снапшот, который уже + // был получен через getChatsAndContacts / кэш; + // - с force: пробрасываем запрос в getChatsAndContacts(force: true), + // чтобы получить полный список чатов. - if (!force && _lastChatsPayload != null && _lastChatsAt != null) { - if (DateTime.now().difference(_lastChatsAt!) < _chatsCacheTtl) { - return _lastChatsPayload!; - } + if (!force && _lastChatsPayload != null) { + return _lastChatsPayload!; } - await _ensureCacheServicesInitialized(); - - 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 chatListJson = - chatResponse['payload']?['chats'] ?? []; - - if (chatListJson.isEmpty) { - final result = {'chats': [], 'contacts': [], 'profile': null}; - _lastChatsPayload = result; - _lastChatsAt = DateTime.now(); - return result; - } - - final contactIds = {}; - for (var chatJson in chatListJson) { - final participants = - chatJson['participants'] as Map? ?? {}; - contactIds.addAll(participants.keys.map((id) => int.parse(id))); - } - - List 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 contacts = contactListJson - .map((json) => Contact.fromJson(json as Map)) - .toList(); - updateContactCache(contacts); - _lastChatsAt = DateTime.now(); - _preloadContactAvatars(contacts); - unawaited( - _chatCacheService.cacheChats(chatListJson.cast>()), - ); - unawaited(_chatCacheService.cacheContacts(contacts)); - return result; - } catch (e) { - print('Ошибка получения чатов через opcode 48: $e'); - rethrow; - } + // Если нужно именно "обновить" — вызываем полноценную синхронизацию. + return getChatsAndContacts(force: true); } Future> getChatsAndContacts({bool force = false}) async { diff --git a/lib/api/api_service_connection.dart b/lib/api/api_service_connection.dart index a22d4cf..086916c 100644 --- a/lib/api/api_service_connection.dart +++ b/lib/api/api_service_connection.dart @@ -224,7 +224,14 @@ extension ApiServiceConnection on ApiService { if (_channel == null) { throw Exception('WebSocket is not connected. Connect first.'); } - _log('➡️ SEND (raw): $jsonString'); + try { + final decoded = jsonDecode(jsonString) as Map; + final opcode = decoded['opcode']; + final payload = decoded['payload']; + _log('➡️ SEND: opcode=$opcode, payload=$payload'); + } catch (_) { + _log('➡️ SEND (raw): $jsonString'); + } _channel!.sink.add(jsonString); } @@ -250,7 +257,7 @@ extension ApiServiceConnection on ApiService { final encodedMessage = jsonEncode(message); - _log('➡️ SEND (custom): $encodedMessage'); + _log('➡️ SEND: opcode=${message['opcode']}, payload=${message['payload']}'); print('Отправляем кастомное сообщение (seq: $currentSeq): $encodedMessage'); _channel!.sink.add(encodedMessage); @@ -273,19 +280,8 @@ extension ApiServiceConnection on ApiService { final encodedMessage = jsonEncode(message); if (opcode == 1) { _log('➡️ SEND (ping) seq: $_seq'); - } else if (opcode == 18 || opcode == 19) { - Map 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 { - _log('➡️ SEND: $encodedMessage'); + _log('➡️ SEND: opcode=$opcode, payload=$payload'); } print('Отправляем сообщение (seq: $_seq): $encodedMessage'); _channel!.sink.add(encodedMessage); @@ -293,47 +289,35 @@ extension ApiServiceConnection on ApiService { } void _listen() async { - _streamSubscription?.cancel(); - _streamSubscription = _channel?.stream.listen( + if (_channel == null) { + return; + } + + // Если уже есть активная подписка на текущий канал, не создаём вторую. + if (_streamSubscription != null) { + return; + } + + _streamSubscription = _channel!.stream.listen( (message) { if (message == null) return; if (message is String && message.trim().isEmpty) { return; } - String loggableMessage = message; try { final decoded = jsonDecode(message) as Map; - if (decoded['opcode'] == 2) { + final opcode = decoded['opcode']; + if (opcode == 2) { _healthMonitor.onPongReceived(); - loggableMessage = '⬅️ RECV (pong) seq: ${decoded['seq']}'; + _log('⬅️ RECV (pong) seq: ${decoded['seq']}'); } else { - Map loggableDecoded = Map.from(decoded); - bool wasModified = false; - if (loggableDecoded.containsKey('payload') && - loggableDecoded['payload'] is Map) { - Map 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'; - } + final payload = decoded['payload']; + _log('⬅️ RECV: opcode=$opcode, payload=$payload'); } } catch (_) { - loggableMessage = '⬅️ RECV (raw): $message'; + _log('⬅️ RECV (raw): $message'); } - _log(loggableMessage); try { final decodedMessage = message is String diff --git a/lib/screens/chats_screen.dart b/lib/screens/chats_screen.dart index 9f9a941..67c8e9c 100644 --- a/lib/screens/chats_screen.dart +++ b/lib/screens/chats_screen.dart @@ -99,11 +99,18 @@ class _ChatsScreenState extends State StreamSubscription? _connectionStateSubscription; bool _isAccountsExpanded = false; - late SharedPreferences prefs; + SharedPreferences? _prefs; - Future _initializePrefs() async { - prefs = await SharedPreferences.getInstance(); + Future _initializePrefs() async { + final p = await SharedPreferences.getInstance(); + if (mounted) { + setState(() { + _prefs = p; + }); + } else { + _prefs = p; } + } @override void initState() { @@ -126,8 +133,6 @@ class _ChatsScreenState extends State } })(); - - _listenForUpdates(); _searchAnimationController = AnimationController( @@ -165,7 +170,6 @@ class _ChatsScreenState extends State _loadChannels(); } }); - final prefs = SharedPreferences.getInstance(); } @override @@ -334,7 +338,11 @@ class _ChatsScreenState extends State // Для части опкодов (48, 55, 135, 272, 274) нам не нужен явный chatId в корне // 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) { return; @@ -357,8 +365,9 @@ class _ChatsScreenState extends State if (mounted) { setState(() { - final existingIndex = - _allChats.indexWhere((chat) => chat.id == newChat.id); + final existingIndex = _allChats.indexWhere( + (chat) => chat.id == newChat.id, + ); if (existingIndex != -1) { _allChats[existingIndex] = newChat; @@ -593,7 +602,9 @@ class _ChatsScreenState extends State // Приоритет: одиночный chat, дальше — первый из списка chats. Map? effectiveChatJson = chatJson; - if (effectiveChatJson == null && chatsJson != null && chatsJson.isNotEmpty) { + if (effectiveChatJson == null && + chatsJson != null && + chatsJson.isNotEmpty) { final first = chatsJson.first; if (first is Map) { effectiveChatJson = first; @@ -607,8 +618,9 @@ class _ChatsScreenState extends State ApiService.instance.updateChatInCacheFromJson(effectiveChatJson); if (mounted) { setState(() { - final existingIndex = - _allChats.indexWhere((chat) => chat.id == newChat.id); + final existingIndex = _allChats.indexWhere( + (chat) => chat.id == newChat.id, + ); if (existingIndex != -1) { _allChats[existingIndex] = newChat; @@ -640,8 +652,9 @@ class _ChatsScreenState extends State ApiService.instance.updateChatInCacheFromJson(chatJson); if (mounted) { setState(() { - final existingIndex = - _allChats.indexWhere((chat) => chat.id == updatedChat.id); + final existingIndex = _allChats.indexWhere( + (chat) => chat.id == updatedChat.id, + ); if (existingIndex != -1) { _allChats[existingIndex] = updatedChat; @@ -1167,7 +1180,6 @@ class _ChatsScreenState extends State ); }, ),*/ - ListTile( leading: CircleAvatar( backgroundColor: Theme.of( @@ -1673,16 +1685,22 @@ class _ChatsScreenState extends State } void _loadChatsAndContacts() { + // Берём актуальный снапшот чатов, если он уже есть (_lastChatsPayload), + // а если нет — ApiService сам дёрнет opcode 19. + final future = ApiService.instance.getChatsOnly(); + setState(() { - _chatsFuture = ApiService.instance.getChatsAndContacts(force: true); + _chatsFuture = future; }); - _chatsFuture.then((data) { - if (mounted) { - final chats = data['chats'] as List; - final contacts = data['contacts'] as List; - final profileData = data['profile']; + future.then((data) { + if (!mounted) return; + final chats = (data['chats'] as List?) ?? const []; + final contacts = (data['contacts'] as List?) ?? const []; + final profileData = data['profile']; + + setState(() { _allChats = chats .where((json) => json != null) .map((json) => Chat.fromJson(json)) @@ -1695,14 +1713,12 @@ class _ChatsScreenState extends State } if (profileData != null) { - setState(() { - _myProfile = Profile.fromJson(profileData); - _isProfileLoading = false; - }); + _myProfile = Profile.fromJson(profileData); + _isProfileLoading = false; } + }); - _filterChats(); - } + _filterChats(); }); } @@ -1944,6 +1960,12 @@ class _ChatsScreenState extends State }); }); } + // Если чаты есть, но текущий фильтр/папка не даёт ни одного результата + // (например, после переподключения или некорректного фильтра), + // то по умолчанию показываем все чаты, а не пустой экран. + if (_filteredChats.isEmpty && _allChats.isNotEmpty) { + _filteredChats = List.from(_allChats); + } if (_filteredChats.isEmpty && _allChats.isEmpty) { return const Center(child: CircularProgressIndicator()); } @@ -3736,16 +3758,16 @@ class _ChatsScreenState extends State ), ] : [ - if (prefs.getBool('show_sferum_button') ?? true) + if ((_prefs?.getBool('show_sferum_button') ?? true)) IconButton( - icon: Image.asset( - 'assets/images/spermum.png', - width: 28, - height: 28, + icon: Image.asset( + 'assets/images/spermum.png', + width: 28, + height: 28, + ), + onPressed: _openSferum, + tooltip: 'Сферум', ), - onPressed: _openSferum, - tooltip: 'Сферум', - ), IconButton( icon: const Icon(Icons.download), onPressed: () { @@ -3882,7 +3904,7 @@ class _ChatsScreenState extends State } } - void _showDeleteAccountDialog( + void _showDeleteAccountDialog( BuildContext context, Account account, AccountManager accountManager,