diff --git a/lib/api/api_service.dart b/lib/api/api_service.dart index 9a1676a..17c08e2 100644 --- a/lib/api/api_service.dart +++ b/lib/api/api_service.dart @@ -5,11 +5,18 @@ import 'dart:convert'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; +import 'package:gwid/connection/connection_logger.dart'; +import 'package:gwid/connection/connection_state.dart' as conn_state; +import 'package:gwid/connection/health_monitor.dart'; +import 'package:gwid/image_cache_service.dart'; import 'package:gwid/models/contact.dart'; import 'package:gwid/models/message.dart'; import 'package:gwid/models/profile.dart'; import 'package:gwid/proxy_service.dart'; import 'package:gwid/services/account_manager.dart'; +import 'package:gwid/services/avatar_cache_service.dart'; +import 'package:gwid/services/cache_service.dart'; +import 'package:gwid/services/chat_cache_service.dart'; import 'package:gwid/spoofing_service.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; @@ -82,6 +89,18 @@ class ApiService { minutes: 5, ); + final CacheService _cacheService = CacheService(); + final AvatarCacheService _avatarCacheService = AvatarCacheService(); + final ChatCacheService _chatCacheService = ChatCacheService(); + bool _cacheServicesInitialized = false; + + final ConnectionLogger _connectionLogger = ConnectionLogger(); + final conn_state.ConnectionStateManager _connectionStateManager = + conn_state.ConnectionStateManager(); + final HealthMonitor _healthMonitor = HealthMonitor(); + + String? _currentServerUrl; + bool _isLoadingBlockedContacts = false; bool _isSessionReady = false; @@ -95,6 +114,13 @@ class ApiService { final _connectionLogController = StreamController.broadcast(); Stream get connectionLog => _connectionLogController.stream; + List get logs => _connectionLogger.logs; + + Stream get connectionState => + _connectionStateManager.stateStream; + + Stream get healthMetrics => _healthMonitor.metricsStream; + final List _connectionLogCache = []; List get connectionLogCache => _connectionLogCache; @@ -133,12 +159,23 @@ class ApiService { Timer? _reconnectTimer; bool _isReconnecting = false; - void _log(String message) { + void _log( + String message, { + LogLevel level = LogLevel.info, + String category = 'API', + Map? data, + }) { print(message); _connectionLogCache.add(message); if (!_connectionLogController.isClosed) { _connectionLogController.add(message); } + _connectionLogger.log( + message, + level: level, + category: category, + data: data, + ); } void _emitLocal(Map frame) { @@ -204,6 +241,48 @@ class ApiService { _isAppInForeground = isForeground; } + void _updateConnectionState( + conn_state.ConnectionState state, { + String? message, + int? attemptNumber, + Duration? reconnectDelay, + int? latency, + Map? metadata, + }) { + _connectionStateManager.setState( + state, + message: message, + attemptNumber: attemptNumber, + reconnectDelay: reconnectDelay, + serverUrl: _currentServerUrl, + latency: latency, + metadata: metadata, + ); + } + + void _startHealthMonitoring() { + _healthMonitor.startMonitoring(serverUrl: _currentServerUrl); + } + + void _stopHealthMonitoring() { + _healthMonitor.stopMonitoring(); + } + + Future initialize() async { + await _ensureCacheServicesInitialized(); + } + + Future _ensureCacheServicesInitialized() async { + if (_cacheServicesInitialized) return; + await Future.wait([ + _cacheService.initialize(), + _avatarCacheService.initialize(), + _chatCacheService.initialize(), + ImageCacheService.instance.initialize(), + ]); + _cacheServicesInitialized = true; + } + Future getClipboardData() async { final data = await Clipboard.getData(Clipboard.kTextPlain); return data?.text; diff --git a/lib/api/api_service_chats.dart b/lib/api/api_service_chats.dart index 0d22f8e..a1fcee5 100644 --- a/lib/api/api_service_chats.dart +++ b/lib/api/api_service_chats.dart @@ -105,6 +105,28 @@ extension ApiServiceChats on ApiService { } } + 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 { final payload = {"chatsCount": 100}; @@ -152,6 +174,13 @@ extension ApiServiceChats on ApiService { contactListJson.map((json) => Contact.fromJson(json)).toList(); updateContactCache(contacts); _lastChatsAt = DateTime.now(); + _preloadContactAvatars(contacts); + unawaited( + _chatCacheService.cacheChats( + chatListJson.cast>(), + ), + ); + unawaited(_chatCacheService.cacheContacts(contacts)); return result; } catch (e) { print('Ошибка получения чатов: $e'); @@ -167,12 +196,36 @@ extension ApiServiceChats on ApiService { throw Exception("Auth token not found - please re-authenticate"); } + await _ensureCacheServicesInitialized(); + if (!force && _lastChatsPayload != null && _lastChatsAt != null) { if (DateTime.now().difference(_lastChatsAt!) < _chatsCacheTtl) { return _lastChatsPayload!; } } + if (!force && + !_chatsFetchedInThisSession && + _lastChatsPayload == null) { + final cachedChats = await _chatCacheService.getCachedChats(); + final cachedContacts = await _chatCacheService.getCachedContacts(); + if (cachedChats != null && + cachedContacts != null && + cachedChats.isNotEmpty) { + final cachedResult = { + 'chats': cachedChats, + 'contacts': cachedContacts.map(_contactToMap).toList(), + 'profile': null, + 'presence': null, + }; + _lastChatsPayload = cachedResult; + _lastChatsAt = DateTime.now(); + updateContactCache(cachedContacts); + _preloadContactAvatars(cachedContacts); + return cachedResult; + } + } + if (_chatsFetchedInThisSession && _lastChatsPayload != null && !force) { return _lastChatsPayload!; } @@ -232,6 +285,10 @@ extension ApiServiceChats on ApiService { _isSessionReady = true; _connectionStatusController.add("ready"); + _updateConnectionState( + conn_state.ConnectionState.ready, + message: 'Авторизация успешна', + ); final profile = chatResponse['payload']?['profile']; final contactProfile = profile?['contact']; @@ -340,6 +397,13 @@ extension ApiServiceChats on ApiService { contactListJson.map((json) => Contact.fromJson(json)).toList(); updateContactCache(contacts); _lastChatsAt = DateTime.now(); + _preloadContactAvatars(contacts); + unawaited( + _chatCacheService.cacheChats( + chatListJson.cast>(), + ), + ); + unawaited(_chatCacheService.cacheContacts(contacts)); _chatsFetchedInThisSession = true; _inflightChatsCompleter!.complete(result); _inflightChatsCompleter = null; @@ -389,11 +453,23 @@ extension ApiServiceChats on ApiService { int chatId, { bool force = false, }) async { + await _ensureCacheServicesInitialized(); + if (!force && _messageCache.containsKey(chatId)) { print("Загружаем сообщения для чата $chatId из кэша."); return _messageCache[chatId]!; } + if (!force) { + final cachedMessages = + await _chatCacheService.getCachedChatMessages(chatId); + if (cachedMessages != null && cachedMessages.isNotEmpty) { + print("История сообщений для чата $chatId загружена из ChatCacheService."); + _messageCache[chatId] = cachedMessages; + return cachedMessages; + } + } + print("Запрашиваем историю для чата $chatId с сервера."); final payload = { "chatId": chatId, @@ -433,6 +509,8 @@ extension ApiServiceChats on ApiService { ..sort((a, b) => a.time.compareTo(b.time)); _messageCache[chatId] = messagesList; + _preloadMessageImages(messagesList); + unawaited(_chatCacheService.cacheChatMessages(chatId, messagesList)); return messagesList; } catch (e) { @@ -567,6 +645,9 @@ extension ApiServiceChats on ApiService { void clearCacheForChat(int chatId) { _messageCache.remove(chatId); + if (_cacheServicesInitialized) { + unawaited(_chatCacheService.clearChatCache(chatId)); + } print("Кэш для чата $chatId очищен."); } @@ -651,9 +732,85 @@ extension ApiServiceChats on ApiService { clearChatsCache(); _messageCache.clear(); clearPasswordAuthData(); + if (_cacheServicesInitialized) { + unawaited(_cacheService.clear()); + unawaited(_chatCacheService.clearAllChatCache()); + unawaited(_avatarCacheService.clearAvatarCache()); + unawaited(ImageCacheService.instance.clearCache()); + } print("Все кэши очищены из-за ошибки подключения."); } + Future> getStatistics() async { + await _ensureCacheServicesInitialized(); + + final cacheStats = await _cacheService.getCacheStats(); + final chatCacheStats = await _chatCacheService.getChatCacheStats(); + final avatarStats = await _avatarCacheService.getAvatarCacheStats(); + final imageStats = await ImageCacheService.instance.getCacheStats(); + + return { + 'api_service': { + 'is_online': _isSessionOnline, + 'is_ready': _isSessionReady, + 'cached_chats': (_lastChatsPayload?['chats'] as List?)?.length ?? 0, + 'contacts_in_memory': _contactCache.length, + 'message_cache_entries': _messageCache.length, + 'message_queue_length': _messageQueue.length, + }, + 'connection': { + 'current_url': _currentUrlIndex < _wsUrls.length + ? _wsUrls[_currentUrlIndex] + : null, + 'reconnect_attempts': _reconnectAttempts, + 'last_action_time': _lastActionTime, + }, + 'cache_service': cacheStats, + 'chat_cache': chatCacheStats, + 'avatar_cache': avatarStats, + 'image_cache': imageStats, + }; + } + + void _preloadContactAvatars(List contacts) { + if (!_cacheServicesInitialized || contacts.isEmpty) return; + final photoUrls = contacts.map((c) => c.photoBaseUrl).toList(); + if (photoUrls.isEmpty) return; + unawaited(ImageCacheService.instance.preloadContactAvatars(photoUrls)); + } + + void _preloadMessageImages(List messages) { + if (!_cacheServicesInitialized || messages.isEmpty) return; + final urls = {}; + for (final message in messages) { + for (final attach in message.attaches) { + final url = attach['url'] ?? attach['baseUrl']; + if (url is String && url.isNotEmpty) { + urls.add(url); + } + } + } + for (final url in urls) { + unawaited(ImageCacheService.instance.preloadImage(url)); + } + } + + Map _contactToMap(Contact contact) { + return { + 'id': contact.id, + 'name': contact.name, + 'firstName': contact.firstName, + 'lastName': contact.lastName, + 'description': contact.description, + 'photoBaseUrl': contact.photoBaseUrl, + 'isBlocked': contact.isBlocked, + 'isBlockedByMe': contact.isBlockedByMe, + 'accountStatus': contact.accountStatus, + 'status': contact.status, + 'options': contact.options, + }; + } + void sendMessage( int chatId, String text, { diff --git a/lib/api/api_service_connection.dart b/lib/api/api_service_connection.dart index a9b0609..a3b3225 100644 --- a/lib/api/api_service_connection.dart +++ b/lib/api/api_service_connection.dart @@ -3,6 +3,10 @@ part of 'api_service.dart'; extension ApiServiceConnection on ApiService { Future _connectWithFallback() async { _log('Начало подключения...'); + _updateConnectionState( + conn_state.ConnectionState.connecting, + message: 'Поиск доступного сервера', + ); while (_currentUrlIndex < _wsUrls.length) { final currentUrl = _wsUrls[_currentUrlIndex]; @@ -17,6 +21,11 @@ extension ApiServiceConnection on ApiService { ? 'Подключено к основному серверу' : 'Подключено через резервный сервер'; _connectionLogController.add('✅ $successMessage'); + _updateConnectionState( + conn_state.ConnectionState.connecting, + message: 'Соединение установлено, ожидание handshake', + metadata: {'server': currentUrl}, + ); if (_currentUrlIndex > 0) { _connectionStatusController.add('Подключено через резервный сервер'); } @@ -25,6 +34,7 @@ extension ApiServiceConnection on ApiService { final errorMessage = '❌ Ошибка: ${e.toString().split(':').first}'; print('Ошибка подключения к $currentUrl: $e'); _connectionLogController.add(errorMessage); + _healthMonitor.onError(errorMessage); _currentUrlIndex++; if (_currentUrlIndex < _wsUrls.length) { @@ -35,12 +45,18 @@ extension ApiServiceConnection on ApiService { _log('❌ Все серверы недоступны'); _connectionStatusController.add('Все серверы недоступны'); + _updateConnectionState( + conn_state.ConnectionState.error, + message: 'Все серверы недоступны', + ); + _stopHealthMonitoring(); throw Exception('Не удалось подключиться ни к одному серверу'); } Future _connectToUrl(String url) async { _isSessionOnline = false; _onlineCompleter = Completer(); + _currentServerUrl = url; final bool hadChatsFetched = _chatsFetchedInThisSession; final bool hasValidToken = authToken != null; @@ -96,6 +112,11 @@ extension ApiServiceConnection on ApiService { print("Сессия была завершена сервером"); _isSessionOnline = false; _isSessionReady = false; + _stopHealthMonitoring(); + _updateConnectionState( + conn_state.ConnectionState.disconnected, + message: 'Сессия завершена сервером', + ); authToken = null; @@ -111,6 +132,12 @@ extension ApiServiceConnection on ApiService { print("Обработка недействительного токена"); _isSessionOnline = false; _isSessionReady = false; + _stopHealthMonitoring(); + _healthMonitor.onError('invalid_token'); + _updateConnectionState( + conn_state.ConnectionState.error, + message: 'Недействительный токен', + ); authToken = null; final prefs = await SharedPreferences.getInstance(); @@ -177,6 +204,10 @@ extension ApiServiceConnection on ApiService { _isSessionReady = false; _connectionStatusController.add("connecting"); + _updateConnectionState( + conn_state.ConnectionState.connecting, + message: 'Инициализация подключения', + ); await _connectWithFallback(); } @@ -273,6 +304,7 @@ extension ApiServiceConnection on ApiService { try { final decoded = jsonDecode(message) as Map; if (decoded['opcode'] == 2) { + _healthMonitor.onPongReceived(); loggableMessage = '⬅️ RECV (pong) seq: ${decoded['seq']}'; } else { Map loggableDecoded = Map.from(decoded); @@ -323,6 +355,11 @@ extension ApiServiceConnection on ApiService { _isSessionReady = false; _reconnectDelaySeconds = 2; _connectionStatusController.add("authorizing"); + _updateConnectionState( + conn_state.ConnectionState.connected, + message: 'Handshake успешен', + ); + _startHealthMonitoring(); if (_onlineCompleter != null && !_onlineCompleter!.isCompleted) { _onlineCompleter!.complete(); @@ -334,6 +371,11 @@ extension ApiServiceConnection on ApiService { if (decodedMessage is Map && decodedMessage['cmd'] == 3) { final error = decodedMessage['payload']; print('Ошибка сервера: $error'); + _healthMonitor.onError(error?['message'] ?? 'server_error'); + _updateConnectionState( + conn_state.ConnectionState.error, + message: error?['message'], + ); if (error != null && error['localizedMessage'] != null) { _errorController.add(error['localizedMessage']); @@ -557,12 +599,22 @@ extension ApiServiceConnection on ApiService { print('Ошибка WebSocket: $error'); _isSessionOnline = false; _isSessionReady = false; + _healthMonitor.onError(error.toString()); + _updateConnectionState( + conn_state.ConnectionState.error, + message: error.toString(), + ); _reconnect(); }, onDone: () { print('WebSocket соединение закрыто. Попытка переподключения...'); _isSessionOnline = false; _isSessionReady = false; + _stopHealthMonitoring(); + _updateConnectionState( + conn_state.ConnectionState.disconnected, + message: 'Соединение закрыто', + ); if (!_isSessionReady) { _reconnect(); @@ -577,6 +629,7 @@ extension ApiServiceConnection on ApiService { _isReconnecting = true; _reconnectAttempts++; + _healthMonitor.onReconnect(); if (_reconnectAttempts > ApiService._maxReconnectAttempts) { print( @@ -584,6 +637,10 @@ extension ApiServiceConnection on ApiService { ); _connectionStatusController.add("disconnected"); _isReconnecting = false; + _updateConnectionState( + conn_state.ConnectionState.error, + message: 'Превышено число попыток переподключения', + ); return; } @@ -607,6 +664,11 @@ extension ApiServiceConnection on ApiService { "Переподключаемся после ${delay.inSeconds}s... (попытка $_reconnectAttempts/${ApiService._maxReconnectAttempts})", ); _isReconnecting = false; + _updateConnectionState( + conn_state.ConnectionState.reconnecting, + attemptNumber: _reconnectAttempts, + reconnectDelay: delay, + ); _connectWithFallback(); }); } @@ -708,6 +770,11 @@ extension ApiServiceConnection on ApiService { _handshakeSent = false; _onlineCompleter = Completer(); _chatsFetchedInThisSession = false; + _stopHealthMonitoring(); + _updateConnectionState( + conn_state.ConnectionState.disconnected, + message: 'Отключено пользователем', + ); _channel?.sink.close(status.goingAway); _channel = null; diff --git a/lib/api_service_v2.dart b/lib/api_service_v2.dart deleted file mode 100644 index dc2934a..0000000 --- a/lib/api_service_v2.dart +++ /dev/null @@ -1,1057 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:http/http.dart' as http; -import 'package:image_picker/image_picker.dart'; - -import 'connection/connection_manager.dart'; -import 'connection/connection_logger.dart'; -import 'connection/connection_state.dart'; -import 'connection/health_monitor.dart'; -import 'models/message.dart'; -import 'models/contact.dart'; -import 'image_cache_service.dart'; -import 'services/cache_service.dart'; -import 'services/avatar_cache_service.dart'; -import 'services/chat_cache_service.dart'; - -class ApiServiceV2 { - ApiServiceV2._privateConstructor(); - static final ApiServiceV2 instance = ApiServiceV2._privateConstructor(); - - final ConnectionManager _connectionManager = ConnectionManager(); - - final ConnectionLogger _logger = ConnectionLogger(); - - String? _authToken; - bool _isInitialized = false; - bool _isAuthenticated = false; - - final Map> _messageCache = {}; - final Map _contactCache = {}; - Map? _lastChatsPayload; - DateTime? _lastChatsAt; - final Duration _chatsCacheTtl = const Duration(seconds: 5); - bool _chatsFetchedInThisSession = false; - - final Map _presenceData = {}; - - final StreamController _contactUpdatesController = - StreamController.broadcast(); - final StreamController> _messageController = - StreamController>.broadcast(); - - Stream> get messages => _messageController.stream; - - Stream get contactUpdates => _contactUpdatesController.stream; - - Stream get connectionState => _connectionManager.stateStream; - - Stream get logs => _connectionManager.logStream; - - Stream get healthMetrics => - _connectionManager.healthMetricsStream; - - ConnectionInfo get currentConnectionState => _connectionManager.currentState; - - bool get isOnline => _connectionManager.isConnected; - - bool get canSendMessages => _connectionManager.canSendMessages; - - Future initialize() async { - if (_isInitialized) { - _logger.logConnection('ApiServiceV2 уже инициализирован'); - return; - } - - _logger.logConnection('Инициализация ApiServiceV2'); - - try { - await _connectionManager.initialize(); - _setupMessageHandlers(); - - _isAuthenticated = false; - - _isInitialized = true; - - _logger.logConnection('ApiServiceV2 успешно инициализирован'); - } catch (e) { - _logger.logError('Ошибка инициализации ApiServiceV2', error: e); - rethrow; - } - } - - void _setupMessageHandlers() { - _connectionManager.messageStream.listen((message) { - _handleIncomingMessage(message); - }); - } - - void _handleIncomingMessage(Map message) { - try { - _logger.logMessage('IN', message); - - if (message['opcode'] == 19 && - message['cmd'] == 1 && - message['payload'] != null) { - _isAuthenticated = true; - _logger.logConnection('Аутентификация успешна'); - } - - if (message['opcode'] == 128 && message['payload'] != null) { - _handleContactUpdate(message['payload']); - } - - if (message['opcode'] == 129 && message['payload'] != null) { - _handlePresenceUpdate(message['payload']); - } - - _messageController.add(message); - } catch (e) { - _logger.logError( - 'Ошибка обработки входящего сообщения', - data: {'message': message, 'error': e.toString()}, - ); - } - } - - void _handleContactUpdate(Map payload) { - try { - final contact = Contact.fromJson(payload); - _contactCache[contact.id] = contact; - _contactUpdatesController.add(contact); - - _logger.logConnection( - 'Контакт обновлен', - data: {'contact_id': contact.id, 'contact_name': contact.name}, - ); - } catch (e) { - _logger.logError( - 'Ошибка обработки обновления контакта', - data: {'payload': payload, 'error': e.toString()}, - ); - } - } - - void _handlePresenceUpdate(Map payload) { - try { - _presenceData.addAll(payload); - _logger.logConnection( - 'Presence данные обновлены', - data: {'keys': payload.keys.toList()}, - ); - } catch (e) { - _logger.logError( - 'Ошибка обработки presence данных', - data: {'payload': payload, 'error': e.toString()}, - ); - } - } - - Future connect() async { - _logger.logConnection('Запрос подключения к серверу'); - - try { - await _connectionManager.connect(authToken: _authToken); - _logger.logConnection('Подключение к серверу успешно'); - } catch (e) { - _logger.logError('Ошибка подключения к серверу', error: e); - rethrow; - } - } - - Future reconnect() async { - _logger.logConnection('Запрос переподключения'); - - try { - await _connectionManager.connect(authToken: _authToken); - _logger.logConnection('Переподключение успешно'); - } catch (e) { - _logger.logError('Ошибка переподключения', error: e); - rethrow; - } - } - - Future forceReconnect() async { - _logger.logConnection('Принудительное переподключение'); - - try { - _isAuthenticated = false; - - await _connectionManager.forceReconnect(); - _logger.logConnection('Принудительное переподключение успешно'); - - await _performFullAuthenticationSequence(); - } catch (e) { - _logger.logError('Ошибка принудительного переподключения', error: e); - rethrow; - } - } - - Future _performFullAuthenticationSequence() async { - _logger.logConnection( - 'Выполнение полной последовательности аутентификации', - ); - - try { - await _waitForConnectionReady(); - - await _sendAuthenticationToken(); - - await _waitForAuthenticationConfirmation(); - - await _sendPingToConfirmSession(); - - await _requestChatsAndContacts(); - - _logger.logConnection( - 'Полная последовательность аутентификации завершена', - ); - } catch (e) { - _logger.logError('Ошибка в последовательности аутентификации', error: e); - rethrow; - } - } - - Future _waitForConnectionReady() async { - const maxWaitTime = Duration(seconds: 30); - final startTime = DateTime.now(); - - while (DateTime.now().difference(startTime) < maxWaitTime) { - if (_connectionManager.currentState.isActive) { - await Future.delayed(const Duration(milliseconds: 500)); - return; - } - await Future.delayed(const Duration(milliseconds: 100)); - } - - throw Exception('Таймаут ожидания готовности соединения'); - } - - Future _sendAuthenticationToken() async { - if (_authToken == null) { - _logger.logError('Токен аутентификации отсутствует'); - return; - } - - _logger.logConnection('Отправка токена аутентификации'); - - final payload = { - "interactive": true, - "token": _authToken, - "chatsCount": 100, - "userAgent": { - "deviceType": "DESKTOP", - "locale": "ru", - "deviceLocale": "ru", - "osVersion": "Windows", - "deviceName": "Chrome", - "headerUserAgent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", - "appVersion": "1.0.0", - "screen": "2560x1440 1.0x", - "timezone": "Europe/Moscow", - }, - }; - - _connectionManager.sendMessage(19, payload); - - await _waitForAuthenticationConfirmation(); - } - - Future _waitForAuthenticationConfirmation() async { - const maxWaitTime = Duration(seconds: 10); - final startTime = DateTime.now(); - - while (DateTime.now().difference(startTime) < maxWaitTime) { - if (_connectionManager.currentState.isActive && _isAuthenticated) { - _logger.logConnection('Аутентификация подтверждена'); - return; - } - await Future.delayed(const Duration(milliseconds: 100)); - } - - throw Exception('Таймаут ожидания подтверждения аутентификации'); - } - - Future _sendPingToConfirmSession() async { - _logger.logConnection('Отправка ping для подтверждения готовности сессии'); - - final payload = {"interactive": true}; - _connectionManager.sendMessage(1, payload); - - await Future.delayed(const Duration(milliseconds: 500)); - - _logger.logConnection('Ping отправлен, сессия готова'); - } - - Future _waitForSessionReady() async { - const maxWaitTime = Duration(seconds: 30); - final startTime = DateTime.now(); - - while (DateTime.now().difference(startTime) < maxWaitTime) { - if (canSendMessages && _isAuthenticated) { - _logger.logConnection('Сессия готова для отправки сообщений'); - return; - } - await Future.delayed(const Duration(milliseconds: 100)); - } - - throw Exception('Таймаут ожидания готовности сессии'); - } - - Future _requestChatsAndContacts() async { - _logger.logConnection('Запрос чатов и контактов'); - - final chatsPayload = {"chatsCount": 100}; - - _connectionManager.sendMessage(48, chatsPayload); - - final contactsPayload = {"status": "BLOCKED", "count": 100, "from": 0}; - - _connectionManager.sendMessage(36, contactsPayload); - } - - Future disconnect() async { - _logger.logConnection('Отключение от сервера'); - - try { - await _connectionManager.disconnect(); - _logger.logConnection('Отключение от сервера успешно'); - } catch (e) { - _logger.logError('Ошибка отключения', error: e); - } - } - - int _sendMessage(int opcode, Map payload) { - if (!canSendMessages) { - _logger.logConnection( - 'Сообщение не отправлено - соединение не готово', - data: {'opcode': opcode, 'payload': payload}, - ); - return -1; - } - - if (_requiresAuthentication(opcode) && !_isAuthenticated) { - _logger.logConnection( - 'Сообщение не отправлено - требуется аутентификация', - data: {'opcode': opcode, 'payload': payload}, - ); - return -1; - } - - try { - final seq = _connectionManager.sendMessage(opcode, payload); - _logger.logConnection( - 'Сообщение отправлено', - data: {'opcode': opcode, 'seq': seq, 'payload': payload}, - ); - return seq; - } catch (e) { - _logger.logError( - 'Ошибка отправки сообщения', - data: {'opcode': opcode, 'payload': payload, 'error': e.toString()}, - ); - return -1; - } - } - - bool _requiresAuthentication(int opcode) { - const authRequiredOpcodes = { - 19, // Аутентификация - 32, // Получение контактов - 36, // Получение заблокированных контактов - 48, // Получение чатов - 49, // Получение истории сообщений - 64, // Отправка сообщений - 65, // Статус набора - 66, // Удаление сообщений - 67, // Редактирование сообщений - 77, // Управление участниками группы - 78, // Управление участниками группы - 80, // Загрузка файлов - 178, // Отправка реакций - 179, // Удаление реакций - }; - - return authRequiredOpcodes.contains(opcode); - } - - Future sendHandshake() async { - _logger.logConnection('Отправка handshake'); - - final payload = { - "userAgent": { - "deviceType": "WEB", - "locale": "ru", - "deviceLocale": "ru", - "osVersion": "Windows", - "deviceName": "Chrome", - "headerUserAgent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", - "appVersion": "25.9.15", - "screen": "1920x1080 1.0x", - "timezone": "Europe/Moscow", - }, - "deviceId": _generateDeviceId(), - }; - - _sendMessage(6, payload); - } - - void requestOtp(String phoneNumber) { - _logger.logConnection('Запрос OTP', data: {'phone': phoneNumber}); - - final payload = { - "phone": phoneNumber, - "type": "START_AUTH", - "language": "ru", - }; - _sendMessage(17, payload); - } - - void verifyCode(String token, String code) { - _logger.logConnection( - 'Проверка кода', - data: {'token': token, 'code': code}, - ); - - final payload = { - "token": token, - "verifyCode": code, - "authTokenType": "CHECK_CODE", - }; - _sendMessage(18, payload); - } - - Future> authenticateWithToken(String token) async { - _logger.logConnection('Аутентификация с токеном'); - - _authToken = token; - await saveToken(token); - - final payload = {"interactive": true, "token": token, "chatsCount": 100}; - - final seq = _sendMessage(19, payload); - - try { - final response = await messages - .firstWhere((msg) => msg['seq'] == seq) - .timeout(const Duration(seconds: 30)); - - _logger.logConnection( - 'Аутентификация успешна', - data: {'seq': seq, 'response_cmd': response['cmd']}, - ); - - return response['payload'] ?? {}; - } catch (e) { - _logger.logError( - 'Ошибка аутентификации', - data: {'token': token, 'error': e.toString()}, - ); - rethrow; - } - } - - Future> getChatsAndContacts({bool force = false}) async { - _logger.logConnection('Запрос чатов и контактов', data: {'force': force}); - - if (!force && _lastChatsPayload != null && _lastChatsAt != null) { - if (DateTime.now().difference(_lastChatsAt!) < _chatsCacheTtl) { - _logger.logConnection('Возвращаем данные из локального кэша'); - return _lastChatsPayload!; - } - } - - if (!force) { - final chatService = ChatCacheService(); - final cachedChats = await chatService.getCachedChats(); - final cachedContacts = await chatService.getCachedContacts(); - - if (cachedChats != null && - cachedContacts != null && - cachedChats.isNotEmpty) { - _logger.logConnection('Возвращаем данные из сервиса кэша'); - final result = { - 'chats': cachedChats, - 'contacts': cachedContacts - .map( - (contact) => { - 'id': contact.id, - 'name': contact.name, - 'firstName': contact.firstName, - 'lastName': contact.lastName, - 'photoBaseUrl': contact.photoBaseUrl, - 'isBlocked': contact.isBlocked, - 'isBlockedByMe': contact.isBlockedByMe, - 'accountStatus': contact.accountStatus, - 'status': contact.status, - }, - ) - .toList(), - 'profile': null, - 'presence': null, - }; - - _lastChatsPayload = result; - _lastChatsAt = DateTime.now(); - _chatsFetchedInThisSession = true; - - return result; - } - } - - await _waitForSessionReady(); - - try { - final payload = {"chatsCount": 100}; - final seq = _sendMessage(48, payload); - - final response = await messages - .firstWhere((msg) => msg['seq'] == seq) - .timeout(const Duration(seconds: 30)); - - final List chatListJson = response['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))); - } - - final contactSeq = _sendMessage(32, {"contactIds": contactIds.toList()}); - - final contactResponse = await messages - .firstWhere((msg) => msg['seq'] == contactSeq) - .timeout(const Duration(seconds: 30)); - - final List contactListJson = - contactResponse['payload']?['contacts'] ?? []; - - final result = { - 'chats': chatListJson, - 'contacts': contactListJson, - 'profile': null, - 'presence': null, - }; - - _lastChatsPayload = result; - _lastChatsAt = DateTime.now(); - _chatsFetchedInThisSession = true; - - final contacts = contactListJson - .map((json) => Contact.fromJson(json)) - .toList(); - updateContactCache(contacts); - - final chatService = ChatCacheService(); - await chatService.cacheChats(chatListJson.cast>()); - await chatService.cacheContacts(contacts); - - _preloadContactAvatars(contacts); - - _logger.logConnection( - 'Чаты и контакты получены', - data: { - 'chats_count': chatListJson.length, - 'contacts_count': contactListJson.length, - }, - ); - - return result; - } catch (e) { - _logger.logError('Ошибка получения чатов и контактов', error: e); - rethrow; - } - } - - Future> getMessageHistory( - int chatId, { - bool force = false, - }) async { - _logger.logConnection( - 'Запрос истории сообщений', - data: {'chat_id': chatId, 'force': force}, - ); - - if (!force && _messageCache.containsKey(chatId)) { - _logger.logConnection('История сообщений загружена из локального кэша'); - return _messageCache[chatId]!; - } - - if (!force) { - final chatService = ChatCacheService(); - final cachedMessages = await chatService.getCachedChatMessages(chatId); - - if (cachedMessages != null && cachedMessages.isNotEmpty) { - _logger.logConnection('История сообщений загружена из сервиса кэша'); - _messageCache[chatId] = cachedMessages; - return cachedMessages; - } - } - - await _waitForSessionReady(); - - try { - final payload = { - "chatId": chatId, - "from": DateTime.now() - .add(const Duration(days: 1)) - .millisecondsSinceEpoch, - "forward": 0, - "backward": 1000, - "getMessages": true, - }; - - final seq = _sendMessage(49, payload); - - final response = await messages - .firstWhere((msg) => msg['seq'] == seq) - .timeout(const Duration(seconds: 30)); - - if (response['cmd'] == 3) { - final error = response['payload']; - _logger.logError( - 'Ошибка получения истории сообщений', - data: {'chat_id': chatId, 'error': error}, - ); - throw Exception('Ошибка получения истории: ${error['message']}'); - } - - final List messagesJson = response['payload']?['messages'] ?? []; - final messagesList = - messagesJson.map((json) => Message.fromJson(json)).toList() - ..sort((a, b) => a.time.compareTo(b.time)); - - _messageCache[chatId] = messagesList; - - final chatService = ChatCacheService(); - await chatService.cacheChatMessages(chatId, messagesList); - - _preloadMessageImages(messagesList); - - _logger.logConnection( - 'История сообщений получена', - data: {'chat_id': chatId, 'messages_count': messagesList.length}, - ); - - return messagesList; - } catch (e) { - _logger.logError( - 'Ошибка получения истории сообщений', - data: {'chat_id': chatId, 'error': e.toString()}, - ); - return []; - } - } - - void sendMessage(int chatId, String text, {String? replyToMessageId}) { - _logger.logConnection( - 'Отправка сообщения', - data: { - 'chat_id': chatId, - 'text_length': text.length, - 'reply_to': replyToMessageId, - }, - ); - - final int clientMessageId = DateTime.now().millisecondsSinceEpoch; - final payload = { - "chatId": chatId, - "message": { - "text": text, - "cid": clientMessageId, - "elements": [], - "attaches": [], - if (replyToMessageId != null) - "link": {"type": "REPLY", "messageId": replyToMessageId}, - }, - "notify": true, - }; - - clearChatsCache(); - _sendMessage(64, payload); - } - - void forwardMessage(int targetChatId, String messageId, int sourceChatId) { - _logger.logConnection( - 'Пересылка сообщения', - data: { - 'target_chat_id': targetChatId, - 'message_id': messageId, - 'source_chat_id': sourceChatId, - }, - ); - - final int clientMessageId = DateTime.now().millisecondsSinceEpoch; - final payload = { - "chatId": targetChatId, - "message": { - "cid": clientMessageId, - "link": { - "type": "FORWARD", - "messageId": messageId, - "chatId": sourceChatId, - }, - "attaches": [], - }, - "notify": true, - }; - - _sendMessage(64, payload); - } - - Future sendPhotoMessage( - int chatId, { - String? localPath, - String? caption, - int? cidOverride, - int? senderId, - }) async { - _logger.logConnection( - 'Отправка фото', - data: {'chat_id': chatId, 'local_path': localPath, 'caption': caption}, - ); - - try { - XFile? image; - if (localPath != null) { - image = XFile(localPath); - } else { - final picker = ImagePicker(); - image = await picker.pickImage(source: ImageSource.gallery); - if (image == null) return; - } - - final seq80 = _sendMessage(80, {"count": 1}); - final resp80 = await messages - .firstWhere((m) => m['seq'] == seq80) - .timeout(const Duration(seconds: 30)); - - final String uploadUrl = resp80['payload']['url']; - - var request = http.MultipartRequest('POST', Uri.parse(uploadUrl)); - request.files.add(await http.MultipartFile.fromPath('file', image.path)); - var streamed = await request.send(); - var httpResp = await http.Response.fromStream(streamed); - - if (httpResp.statusCode != 200) { - throw Exception( - 'Ошибка загрузки фото: ${httpResp.statusCode} ${httpResp.body}', - ); - } - - final uploadJson = jsonDecode(httpResp.body) as Map; - final Map photos = uploadJson['photos'] as Map; - if (photos.isEmpty) throw Exception('Не получен токен фото'); - final String photoToken = (photos.values.first as Map)['token']; - - final int cid = cidOverride ?? DateTime.now().millisecondsSinceEpoch; - final payload = { - "chatId": chatId, - "message": { - "text": caption?.trim() ?? "", - "cid": cid, - "elements": [], - "attaches": [ - {"_type": "PHOTO", "photoToken": photoToken}, - ], - }, - "notify": true, - }; - - clearChatsCache(); - _sendMessage(64, payload); - - _logger.logConnection( - 'Фото отправлено', - data: {'chat_id': chatId, 'photo_token': photoToken}, - ); - } catch (e) { - _logger.logError( - 'Ошибка отправки фото', - data: {'chat_id': chatId, 'error': e.toString()}, - ); - } - } - - Future blockContact(int contactId) async { - _logger.logConnection( - 'Блокировка контакта', - data: {'contact_id': contactId}, - ); - _sendMessage(34, {'contactId': contactId, 'action': 'BLOCK'}); - } - - Future unblockContact(int contactId) async { - _logger.logConnection( - 'Разблокировка контакта', - data: {'contact_id': contactId}, - ); - _sendMessage(34, {'contactId': contactId, 'action': 'UNBLOCK'}); - } - - void getBlockedContacts() { - _logger.logConnection('Запрос заблокированных контактов'); - _sendMessage(36, {'status': 'BLOCKED', 'count': 100, 'from': 0}); - } - - void createGroup(String name, List participantIds) { - _logger.logConnection( - 'Создание группы', - data: {'name': name, 'participants': participantIds}, - ); - - final payload = {"name": name, "participantIds": participantIds}; - _sendMessage(48, payload); - } - - void addGroupMember( - int chatId, - List userIds, { - bool showHistory = true, - }) { - _logger.logConnection( - 'Добавление участника в группу', - data: {'chat_id': chatId, 'user_ids': userIds}, - ); - - final payload = { - "chatId": chatId, - "userIds": userIds, - "showHistory": showHistory, - "operation": "add", - }; - _sendMessage(77, payload); - } - - void removeGroupMember( - int chatId, - List userIds, { - int cleanMsgPeriod = 0, - }) { - _logger.logConnection( - 'Удаление участника из группы', - data: {'chat_id': chatId, 'user_ids': userIds}, - ); - - final payload = { - "chatId": chatId, - "userIds": userIds, - "operation": "remove", - "cleanMsgPeriod": cleanMsgPeriod, - }; - _sendMessage(77, payload); - } - - void leaveGroup(int chatId) { - _logger.logConnection('Выход из группы', data: {'chat_id': chatId}); - _sendMessage(58, {"chatId": chatId}); - } - - void sendReaction(int chatId, String messageId, String emoji) { - _logger.logConnection( - 'Отправка реакции', - data: {'chat_id': chatId, 'message_id': messageId, 'emoji': emoji}, - ); - - final payload = { - "chatId": chatId, - "messageId": messageId, - "reaction": {"reactionType": "EMOJI", "id": emoji}, - }; - _sendMessage(178, payload); - } - - void removeReaction(int chatId, String messageId) { - _logger.logConnection( - 'Удаление реакции', - data: {'chat_id': chatId, 'message_id': messageId}, - ); - - final payload = {"chatId": chatId, "messageId": messageId}; - _sendMessage(179, payload); - } - - void sendTyping(int chatId, {String type = "TEXT"}) { - final payload = {"chatId": chatId, "type": type}; - _sendMessage(65, payload); - } - - DateTime? getLastSeen(int userId) { - final userPresence = _presenceData[userId.toString()]; - if (userPresence != null && userPresence['seen'] != null) { - final seenTimestamp = userPresence['seen'] as int; - return DateTime.fromMillisecondsSinceEpoch(seenTimestamp * 1000); - } - return null; - } - - void updateContactCache(List contacts) { - _contactCache.clear(); - for (final contact in contacts) { - _contactCache[contact.id] = contact; - } - _logger.logConnection( - 'Кэш контактов обновлен', - data: {'contacts_count': contacts.length}, - ); - } - - Contact? getCachedContact(int contactId) { - return _contactCache[contactId]; - } - - void clearChatsCache() { - _lastChatsPayload = null; - _lastChatsAt = null; - _chatsFetchedInThisSession = false; - _logger.logConnection('Кэш чатов очищен'); - } - - void clearMessageCache(int chatId) { - _messageCache.remove(chatId); - _logger.logConnection('Кэш сообщений очищен', data: {'chat_id': chatId}); - } - - Future clearAllCaches() async { - _messageCache.clear(); - _contactCache.clear(); - clearChatsCache(); - - try { - await CacheService().clear(); - await AvatarCacheService().clearAvatarCache(); - await ChatCacheService().clearAllChatCache(); - } catch (e) { - _logger.logError('Ошибка очистки сервисов кеширования', error: e); - } - - _logger.logConnection('Все кэши очищены'); - } - - Future saveToken(String token) async { - _authToken = token; - final prefs = await SharedPreferences.getInstance(); - - await prefs.setString('authToken', token); - - _logger.logConnection('Токен сохранен'); - } - - Future hasToken() async { - final prefs = await SharedPreferences.getInstance(); - _authToken = prefs.getString('authToken'); - return _authToken != null; - } - - Future logout() async { - _logger.logConnection('Выход из системы'); - - try { - final prefs = await SharedPreferences.getInstance(); - await prefs.remove('authToken'); - _authToken = null; - clearAllCaches(); - await disconnect(); - _logger.logConnection('Выход из системы выполнен'); - } catch (e) { - _logger.logError('Ошибка при выходе из системы', error: e); - } - } - - Future _preloadContactAvatars(List contacts) async { - try { - final avatarUrls = contacts - .map((contact) => contact.photoBaseUrl) - .where((url) => url != null && url.isNotEmpty) - .toList(); - - if (avatarUrls.isNotEmpty) { - _logger.logConnection( - 'Предзагрузка аватарок контактов', - data: {'count': avatarUrls.length}, - ); - - await ImageCacheService.instance.preloadContactAvatars(avatarUrls); - } - } catch (e) { - _logger.logError('Ошибка предзагрузки аватарок контактов', error: e); - } - } - - Future _preloadMessageImages(List messages) async { - try { - final imageUrls = []; - - for (final message in messages) { - for (final attach in message.attaches) { - if (attach['_type'] == 'PHOTO' || attach['_type'] == 'SHARE') { - final url = attach['url'] ?? attach['baseUrl']; - if (url is String && url.isNotEmpty) { - imageUrls.add(url); - } - } - } - } - - if (imageUrls.isNotEmpty) { - _logger.logConnection( - 'Предзагрузка изображений из сообщений', - data: {'count': imageUrls.length}, - ); - - await ImageCacheService.instance.preloadContactAvatars(imageUrls); - } - } catch (e) { - _logger.logError( - 'Ошибка предзагрузки изображений из сообщений', - error: e, - ); - } - } - - String _generateDeviceId() { - final timestamp = DateTime.now().millisecondsSinceEpoch; - final random = (timestamp % 1000000).toString().padLeft(6, '0'); - return "$timestamp$random"; - } - - Future> getStatistics() async { - final imageCacheStats = await ImageCacheService.instance.getCacheStats(); - final cacheServiceStats = await CacheService().getCacheStats(); - final avatarCacheStats = await AvatarCacheService().getAvatarCacheStats(); - final chatCacheStats = await ChatCacheService().getChatCacheStats(); - - return { - 'api_service': { - 'is_initialized': _isInitialized, - 'has_auth_token': _authToken != null, - 'message_cache_size': _messageCache.length, - 'contact_cache_size': _contactCache.length, - 'chats_fetched_in_session': _chatsFetchedInThisSession, - }, - 'connection': _connectionManager.getStatistics(), - 'cache_service': cacheServiceStats, - 'avatar_cache': avatarCacheStats, - 'chat_cache': chatCacheStats, - 'image_cache': imageCacheStats, - }; - } - - void dispose() { - _logger.logConnection('Освобождение ресурсов ApiServiceV2'); - _connectionManager.dispose(); - _messageController.close(); - _contactUpdatesController.close(); - } -} diff --git a/lib/widgets/connection_debug_panel.dart b/lib/widgets/connection_debug_panel.dart index a2ba1b8..364b197 100644 --- a/lib/widgets/connection_debug_panel.dart +++ b/lib/widgets/connection_debug_panel.dart @@ -4,7 +4,7 @@ import 'dart:async'; import '../connection/connection_logger.dart'; import '../connection/connection_state.dart' as conn_state; import '../connection/health_monitor.dart'; -import '../api_service_v2.dart'; +import 'package:gwid/api/api_service.dart'; class ConnectionDebugPanel extends StatefulWidget { @@ -39,7 +39,7 @@ class _ConnectionDebugPanelState extends State void _setupSubscriptions() { _logsSubscription = Stream.periodic(const Duration(seconds: 1)) - .asyncMap((_) async => ApiServiceV2.instance.logs.take(100).toList()) + .asyncMap((_) async => ApiService.instance.logs.take(100).toList()) .listen((logs) { if (mounted) { setState(() { @@ -49,7 +49,7 @@ class _ConnectionDebugPanelState extends State }); - _stateSubscription = ApiServiceV2.instance.connectionState.listen((state) { + _stateSubscription = ApiService.instance.connectionState.listen((state) { if (mounted) { setState(() { _stateHistory.add(state); @@ -61,7 +61,7 @@ class _ConnectionDebugPanelState extends State }); - _healthSubscription = ApiServiceV2.instance.healthMetrics.listen((health) { + _healthSubscription = ApiService.instance.healthMetrics.listen((health) { if (mounted) { setState(() { _healthMetrics.add(health); @@ -93,7 +93,7 @@ class _ConnectionDebugPanelState extends State borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, -5), ), @@ -113,7 +113,7 @@ class _ConnectionDebugPanelState extends State return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), child: Row( @@ -223,10 +223,10 @@ class _ConnectionDebugPanelState extends State margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: _getLogColor(log.level).withOpacity(0.1), + color: _getLogColor(log.level).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( - color: _getLogColor(log.level).withOpacity(0.3), + color: _getLogColor(log.level).withValues(alpha: 0.3), width: 1, ), ), @@ -297,10 +297,10 @@ class _ConnectionDebugPanelState extends State margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: _getStateColor(state.state).withOpacity(0.1), + color: _getStateColor(state.state).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( - color: _getStateColor(state.state).withOpacity(0.3), + color: _getStateColor(state.state).withValues(alpha: 0.3), width: 1, ), ), @@ -372,10 +372,10 @@ class _ConnectionDebugPanelState extends State margin: const EdgeInsets.all(8), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: _getHealthColor(health.quality).withOpacity(0.1), + color: _getHealthColor(health.quality).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( - color: _getHealthColor(health.quality).withOpacity(0.3), + color: _getHealthColor(health.quality).withValues(alpha: 0.3), width: 1, ), ), @@ -444,7 +444,7 @@ class _ConnectionDebugPanelState extends State color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8), border: Border.all( - color: Theme.of(context).colorScheme.outline.withOpacity(0.2), + color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.2), ), ), child: const Center( @@ -455,7 +455,7 @@ class _ConnectionDebugPanelState extends State Widget _buildStatsTab() { return FutureBuilder>( - future: ApiServiceV2.instance + future: ApiService.instance .getStatistics(), // Указываем Future, который нужно ожидать builder: (context, snapshot) { @@ -667,7 +667,12 @@ class _ConnectionDebugPanelState extends State } void _clearLogs() { - + ConnectionLogger().clearLogs(); + if (mounted) { + setState(() { + _logs = []; + }); + } } void _exportLogs() { diff --git a/lib/widgets/connection_status_widget.dart b/lib/widgets/connection_status_widget.dart index 4a9e5f2..a8eab60 100644 --- a/lib/widgets/connection_status_widget.dart +++ b/lib/widgets/connection_status_widget.dart @@ -3,7 +3,9 @@ import 'dart:async'; import '../connection/connection_state.dart' as conn_state; import '../connection/health_monitor.dart'; -import '../api_service_v2.dart'; +import 'package:gwid/api/api_service.dart'; + + class ConnectionStatusWidget extends StatefulWidget { @@ -37,7 +39,7 @@ class _ConnectionStatusWidgetState extends State { } void _setupSubscriptions() { - _stateSubscription = ApiServiceV2.instance.connectionState.listen((state) { + _stateSubscription = ApiService.instance.connectionState.listen((state) { if (mounted) { setState(() { _currentState = state; @@ -46,7 +48,7 @@ class _ConnectionStatusWidgetState extends State { }); if (widget.showHealthMetrics) { - _healthSubscription = ApiServiceV2.instance.healthMetrics.listen(( + _healthSubscription = ApiService.instance.healthMetrics.listen(( health, ) { if (mounted) { @@ -77,10 +79,10 @@ class _ConnectionStatusWidgetState extends State { duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( - color: _getStatusColor().withOpacity(0.1), + color: _getStatusColor().withValues(alpha: 0.1), borderRadius: BorderRadius.circular(20), border: Border.all( - color: _getStatusColor().withOpacity(0.3), + color: _getStatusColor().withValues(alpha: 0.3), width: 1, ), ), @@ -125,7 +127,7 @@ class _ConnectionStatusWidgetState extends State { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: _getStatusColor().withOpacity(0.5), + color: _getStatusColor().withValues(alpha: 0.5), blurRadius: 4, spreadRadius: 1, ), @@ -177,7 +179,7 @@ class _ConnectionStatusWidgetState extends State { Text( '$label: ', style: TextStyle( - color: _getStatusColor().withOpacity(0.7), + color: _getStatusColor().withValues(alpha: 0.7), fontSize: 10, ), ), @@ -200,9 +202,9 @@ class _ConnectionStatusWidgetState extends State { return Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: _getHealthColor().withOpacity(0.1), + color: _getHealthColor().withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: Border.all(color: _getHealthColor().withOpacity(0.3), width: 1), + border: Border.all(color: _getHealthColor().withValues(alpha: 0.3), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -233,7 +235,7 @@ class _ConnectionStatusWidgetState extends State { return Container( height: 4, decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.2), + color: Colors.grey.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(2), ), child: FractionallySizedBox( @@ -348,7 +350,7 @@ class _ConnectionIndicatorState extends State { @override Widget build(BuildContext context) { return StreamBuilder( - stream: ApiServiceV2.instance.connectionState, + stream: ApiService.instance.connectionState, builder: (context, snapshot) { if (!snapshot.hasData) { return SizedBox( @@ -382,11 +384,11 @@ class _ConnectionIndicatorState extends State { width: widget.size, height: widget.size, decoration: BoxDecoration( - color: color.withOpacity(0.3 + (0.7 * value)), + color: color.withValues(alpha: 0.3 + (0.7 * value)), shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: color.withOpacity(0.5 * value), + color: color.withValues(alpha: 0.5 * value), blurRadius: 8 * value, spreadRadius: 2 * value, ), @@ -412,7 +414,7 @@ class _ConnectionIndicatorState extends State { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: color.withOpacity(0.5), + color: color.withValues(alpha: 0.5), blurRadius: 4, spreadRadius: 1, ),