yay ASv2 removed
This commit is contained in:
@@ -5,11 +5,18 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/services.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/contact.dart';
|
||||||
import 'package:gwid/models/message.dart';
|
import 'package:gwid/models/message.dart';
|
||||||
import 'package:gwid/models/profile.dart';
|
import 'package:gwid/models/profile.dart';
|
||||||
import 'package:gwid/proxy_service.dart';
|
import 'package:gwid/proxy_service.dart';
|
||||||
import 'package:gwid/services/account_manager.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:gwid/spoofing_service.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
@@ -82,6 +89,18 @@ class ApiService {
|
|||||||
minutes: 5,
|
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 _isLoadingBlockedContacts = false;
|
||||||
|
|
||||||
bool _isSessionReady = false;
|
bool _isSessionReady = false;
|
||||||
@@ -95,6 +114,13 @@ class ApiService {
|
|||||||
final _connectionLogController = StreamController<String>.broadcast();
|
final _connectionLogController = StreamController<String>.broadcast();
|
||||||
Stream<String> get connectionLog => _connectionLogController.stream;
|
Stream<String> get connectionLog => _connectionLogController.stream;
|
||||||
|
|
||||||
|
List<LogEntry> get logs => _connectionLogger.logs;
|
||||||
|
|
||||||
|
Stream<conn_state.ConnectionInfo> get connectionState =>
|
||||||
|
_connectionStateManager.stateStream;
|
||||||
|
|
||||||
|
Stream<HealthMetrics> get healthMetrics => _healthMonitor.metricsStream;
|
||||||
|
|
||||||
final List<String> _connectionLogCache = [];
|
final List<String> _connectionLogCache = [];
|
||||||
List<String> get connectionLogCache => _connectionLogCache;
|
List<String> get connectionLogCache => _connectionLogCache;
|
||||||
|
|
||||||
@@ -133,12 +159,23 @@ class ApiService {
|
|||||||
Timer? _reconnectTimer;
|
Timer? _reconnectTimer;
|
||||||
bool _isReconnecting = false;
|
bool _isReconnecting = false;
|
||||||
|
|
||||||
void _log(String message) {
|
void _log(
|
||||||
|
String message, {
|
||||||
|
LogLevel level = LogLevel.info,
|
||||||
|
String category = 'API',
|
||||||
|
Map<String, dynamic>? data,
|
||||||
|
}) {
|
||||||
print(message);
|
print(message);
|
||||||
_connectionLogCache.add(message);
|
_connectionLogCache.add(message);
|
||||||
if (!_connectionLogController.isClosed) {
|
if (!_connectionLogController.isClosed) {
|
||||||
_connectionLogController.add(message);
|
_connectionLogController.add(message);
|
||||||
}
|
}
|
||||||
|
_connectionLogger.log(
|
||||||
|
message,
|
||||||
|
level: level,
|
||||||
|
category: category,
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _emitLocal(Map<String, dynamic> frame) {
|
void _emitLocal(Map<String, dynamic> frame) {
|
||||||
@@ -204,6 +241,48 @@ class ApiService {
|
|||||||
_isAppInForeground = isForeground;
|
_isAppInForeground = isForeground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateConnectionState(
|
||||||
|
conn_state.ConnectionState state, {
|
||||||
|
String? message,
|
||||||
|
int? attemptNumber,
|
||||||
|
Duration? reconnectDelay,
|
||||||
|
int? latency,
|
||||||
|
Map<String, dynamic>? 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<void> initialize() async {
|
||||||
|
await _ensureCacheServicesInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _ensureCacheServicesInitialized() async {
|
||||||
|
if (_cacheServicesInitialized) return;
|
||||||
|
await Future.wait([
|
||||||
|
_cacheService.initialize(),
|
||||||
|
_avatarCacheService.initialize(),
|
||||||
|
_chatCacheService.initialize(),
|
||||||
|
ImageCacheService.instance.initialize(),
|
||||||
|
]);
|
||||||
|
_cacheServicesInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> getClipboardData() async {
|
Future<String?> getClipboardData() async {
|
||||||
final data = await Clipboard.getData(Clipboard.kTextPlain);
|
final data = await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
return data?.text;
|
return data?.text;
|
||||||
|
|||||||
@@ -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 {
|
try {
|
||||||
final payload = {"chatsCount": 100};
|
final payload = {"chatsCount": 100};
|
||||||
|
|
||||||
@@ -152,6 +174,13 @@ extension ApiServiceChats on ApiService {
|
|||||||
contactListJson.map((json) => Contact.fromJson(json)).toList();
|
contactListJson.map((json) => Contact.fromJson(json)).toList();
|
||||||
updateContactCache(contacts);
|
updateContactCache(contacts);
|
||||||
_lastChatsAt = DateTime.now();
|
_lastChatsAt = DateTime.now();
|
||||||
|
_preloadContactAvatars(contacts);
|
||||||
|
unawaited(
|
||||||
|
_chatCacheService.cacheChats(
|
||||||
|
chatListJson.cast<Map<String, dynamic>>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unawaited(_chatCacheService.cacheContacts(contacts));
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Ошибка получения чатов: $e');
|
print('Ошибка получения чатов: $e');
|
||||||
@@ -167,12 +196,36 @@ extension ApiServiceChats on ApiService {
|
|||||||
throw Exception("Auth token not found - please re-authenticate");
|
throw Exception("Auth token not found - please re-authenticate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _ensureCacheServicesInitialized();
|
||||||
|
|
||||||
if (!force && _lastChatsPayload != null && _lastChatsAt != null) {
|
if (!force && _lastChatsPayload != null && _lastChatsAt != null) {
|
||||||
if (DateTime.now().difference(_lastChatsAt!) < _chatsCacheTtl) {
|
if (DateTime.now().difference(_lastChatsAt!) < _chatsCacheTtl) {
|
||||||
return _lastChatsPayload!;
|
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) {
|
if (_chatsFetchedInThisSession && _lastChatsPayload != null && !force) {
|
||||||
return _lastChatsPayload!;
|
return _lastChatsPayload!;
|
||||||
}
|
}
|
||||||
@@ -232,6 +285,10 @@ extension ApiServiceChats on ApiService {
|
|||||||
_isSessionReady = true;
|
_isSessionReady = true;
|
||||||
|
|
||||||
_connectionStatusController.add("ready");
|
_connectionStatusController.add("ready");
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.ready,
|
||||||
|
message: 'Авторизация успешна',
|
||||||
|
);
|
||||||
|
|
||||||
final profile = chatResponse['payload']?['profile'];
|
final profile = chatResponse['payload']?['profile'];
|
||||||
final contactProfile = profile?['contact'];
|
final contactProfile = profile?['contact'];
|
||||||
@@ -340,6 +397,13 @@ extension ApiServiceChats on ApiService {
|
|||||||
contactListJson.map((json) => Contact.fromJson(json)).toList();
|
contactListJson.map((json) => Contact.fromJson(json)).toList();
|
||||||
updateContactCache(contacts);
|
updateContactCache(contacts);
|
||||||
_lastChatsAt = DateTime.now();
|
_lastChatsAt = DateTime.now();
|
||||||
|
_preloadContactAvatars(contacts);
|
||||||
|
unawaited(
|
||||||
|
_chatCacheService.cacheChats(
|
||||||
|
chatListJson.cast<Map<String, dynamic>>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unawaited(_chatCacheService.cacheContacts(contacts));
|
||||||
_chatsFetchedInThisSession = true;
|
_chatsFetchedInThisSession = true;
|
||||||
_inflightChatsCompleter!.complete(result);
|
_inflightChatsCompleter!.complete(result);
|
||||||
_inflightChatsCompleter = null;
|
_inflightChatsCompleter = null;
|
||||||
@@ -389,11 +453,23 @@ extension ApiServiceChats on ApiService {
|
|||||||
int chatId, {
|
int chatId, {
|
||||||
bool force = false,
|
bool force = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
await _ensureCacheServicesInitialized();
|
||||||
|
|
||||||
if (!force && _messageCache.containsKey(chatId)) {
|
if (!force && _messageCache.containsKey(chatId)) {
|
||||||
print("Загружаем сообщения для чата $chatId из кэша.");
|
print("Загружаем сообщения для чата $chatId из кэша.");
|
||||||
return _messageCache[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 с сервера.");
|
print("Запрашиваем историю для чата $chatId с сервера.");
|
||||||
final payload = {
|
final payload = {
|
||||||
"chatId": chatId,
|
"chatId": chatId,
|
||||||
@@ -433,6 +509,8 @@ extension ApiServiceChats on ApiService {
|
|||||||
..sort((a, b) => a.time.compareTo(b.time));
|
..sort((a, b) => a.time.compareTo(b.time));
|
||||||
|
|
||||||
_messageCache[chatId] = messagesList;
|
_messageCache[chatId] = messagesList;
|
||||||
|
_preloadMessageImages(messagesList);
|
||||||
|
unawaited(_chatCacheService.cacheChatMessages(chatId, messagesList));
|
||||||
|
|
||||||
return messagesList;
|
return messagesList;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -567,6 +645,9 @@ extension ApiServiceChats on ApiService {
|
|||||||
|
|
||||||
void clearCacheForChat(int chatId) {
|
void clearCacheForChat(int chatId) {
|
||||||
_messageCache.remove(chatId);
|
_messageCache.remove(chatId);
|
||||||
|
if (_cacheServicesInitialized) {
|
||||||
|
unawaited(_chatCacheService.clearChatCache(chatId));
|
||||||
|
}
|
||||||
print("Кэш для чата $chatId очищен.");
|
print("Кэш для чата $chatId очищен.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,9 +732,85 @@ extension ApiServiceChats on ApiService {
|
|||||||
clearChatsCache();
|
clearChatsCache();
|
||||||
_messageCache.clear();
|
_messageCache.clear();
|
||||||
clearPasswordAuthData();
|
clearPasswordAuthData();
|
||||||
|
if (_cacheServicesInitialized) {
|
||||||
|
unawaited(_cacheService.clear());
|
||||||
|
unawaited(_chatCacheService.clearAllChatCache());
|
||||||
|
unawaited(_avatarCacheService.clearAvatarCache());
|
||||||
|
unawaited(ImageCacheService.instance.clearCache());
|
||||||
|
}
|
||||||
print("Все кэши очищены из-за ошибки подключения.");
|
print("Все кэши очищены из-за ошибки подключения.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> 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<Contact> 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<Message> messages) {
|
||||||
|
if (!_cacheServicesInitialized || messages.isEmpty) return;
|
||||||
|
final urls = <String>{};
|
||||||
|
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<String, dynamic> _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(
|
void sendMessage(
|
||||||
int chatId,
|
int chatId,
|
||||||
String text, {
|
String text, {
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ part of 'api_service.dart';
|
|||||||
extension ApiServiceConnection on ApiService {
|
extension ApiServiceConnection on ApiService {
|
||||||
Future<void> _connectWithFallback() async {
|
Future<void> _connectWithFallback() async {
|
||||||
_log('Начало подключения...');
|
_log('Начало подключения...');
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.connecting,
|
||||||
|
message: 'Поиск доступного сервера',
|
||||||
|
);
|
||||||
|
|
||||||
while (_currentUrlIndex < _wsUrls.length) {
|
while (_currentUrlIndex < _wsUrls.length) {
|
||||||
final currentUrl = _wsUrls[_currentUrlIndex];
|
final currentUrl = _wsUrls[_currentUrlIndex];
|
||||||
@@ -17,6 +21,11 @@ extension ApiServiceConnection on ApiService {
|
|||||||
? 'Подключено к основному серверу'
|
? 'Подключено к основному серверу'
|
||||||
: 'Подключено через резервный сервер';
|
: 'Подключено через резервный сервер';
|
||||||
_connectionLogController.add('✅ $successMessage');
|
_connectionLogController.add('✅ $successMessage');
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.connecting,
|
||||||
|
message: 'Соединение установлено, ожидание handshake',
|
||||||
|
metadata: {'server': currentUrl},
|
||||||
|
);
|
||||||
if (_currentUrlIndex > 0) {
|
if (_currentUrlIndex > 0) {
|
||||||
_connectionStatusController.add('Подключено через резервный сервер');
|
_connectionStatusController.add('Подключено через резервный сервер');
|
||||||
}
|
}
|
||||||
@@ -25,6 +34,7 @@ extension ApiServiceConnection on ApiService {
|
|||||||
final errorMessage = '❌ Ошибка: ${e.toString().split(':').first}';
|
final errorMessage = '❌ Ошибка: ${e.toString().split(':').first}';
|
||||||
print('Ошибка подключения к $currentUrl: $e');
|
print('Ошибка подключения к $currentUrl: $e');
|
||||||
_connectionLogController.add(errorMessage);
|
_connectionLogController.add(errorMessage);
|
||||||
|
_healthMonitor.onError(errorMessage);
|
||||||
_currentUrlIndex++;
|
_currentUrlIndex++;
|
||||||
|
|
||||||
if (_currentUrlIndex < _wsUrls.length) {
|
if (_currentUrlIndex < _wsUrls.length) {
|
||||||
@@ -35,12 +45,18 @@ extension ApiServiceConnection on ApiService {
|
|||||||
|
|
||||||
_log('❌ Все серверы недоступны');
|
_log('❌ Все серверы недоступны');
|
||||||
_connectionStatusController.add('Все серверы недоступны');
|
_connectionStatusController.add('Все серверы недоступны');
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.error,
|
||||||
|
message: 'Все серверы недоступны',
|
||||||
|
);
|
||||||
|
_stopHealthMonitoring();
|
||||||
throw Exception('Не удалось подключиться ни к одному серверу');
|
throw Exception('Не удалось подключиться ни к одному серверу');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _connectToUrl(String url) async {
|
Future<void> _connectToUrl(String url) async {
|
||||||
_isSessionOnline = false;
|
_isSessionOnline = false;
|
||||||
_onlineCompleter = Completer<void>();
|
_onlineCompleter = Completer<void>();
|
||||||
|
_currentServerUrl = url;
|
||||||
final bool hadChatsFetched = _chatsFetchedInThisSession;
|
final bool hadChatsFetched = _chatsFetchedInThisSession;
|
||||||
final bool hasValidToken = authToken != null;
|
final bool hasValidToken = authToken != null;
|
||||||
|
|
||||||
@@ -96,6 +112,11 @@ extension ApiServiceConnection on ApiService {
|
|||||||
print("Сессия была завершена сервером");
|
print("Сессия была завершена сервером");
|
||||||
_isSessionOnline = false;
|
_isSessionOnline = false;
|
||||||
_isSessionReady = false;
|
_isSessionReady = false;
|
||||||
|
_stopHealthMonitoring();
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.disconnected,
|
||||||
|
message: 'Сессия завершена сервером',
|
||||||
|
);
|
||||||
|
|
||||||
authToken = null;
|
authToken = null;
|
||||||
|
|
||||||
@@ -111,6 +132,12 @@ extension ApiServiceConnection on ApiService {
|
|||||||
print("Обработка недействительного токена");
|
print("Обработка недействительного токена");
|
||||||
_isSessionOnline = false;
|
_isSessionOnline = false;
|
||||||
_isSessionReady = false;
|
_isSessionReady = false;
|
||||||
|
_stopHealthMonitoring();
|
||||||
|
_healthMonitor.onError('invalid_token');
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.error,
|
||||||
|
message: 'Недействительный токен',
|
||||||
|
);
|
||||||
|
|
||||||
authToken = null;
|
authToken = null;
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
@@ -177,6 +204,10 @@ extension ApiServiceConnection on ApiService {
|
|||||||
_isSessionReady = false;
|
_isSessionReady = false;
|
||||||
|
|
||||||
_connectionStatusController.add("connecting");
|
_connectionStatusController.add("connecting");
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.connecting,
|
||||||
|
message: 'Инициализация подключения',
|
||||||
|
);
|
||||||
await _connectWithFallback();
|
await _connectWithFallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +304,7 @@ extension ApiServiceConnection on ApiService {
|
|||||||
try {
|
try {
|
||||||
final decoded = jsonDecode(message) as Map<String, dynamic>;
|
final decoded = jsonDecode(message) as Map<String, dynamic>;
|
||||||
if (decoded['opcode'] == 2) {
|
if (decoded['opcode'] == 2) {
|
||||||
|
_healthMonitor.onPongReceived();
|
||||||
loggableMessage = '⬅️ RECV (pong) seq: ${decoded['seq']}';
|
loggableMessage = '⬅️ RECV (pong) seq: ${decoded['seq']}';
|
||||||
} else {
|
} else {
|
||||||
Map<String, dynamic> loggableDecoded = Map.from(decoded);
|
Map<String, dynamic> loggableDecoded = Map.from(decoded);
|
||||||
@@ -323,6 +355,11 @@ extension ApiServiceConnection on ApiService {
|
|||||||
_isSessionReady = false;
|
_isSessionReady = false;
|
||||||
_reconnectDelaySeconds = 2;
|
_reconnectDelaySeconds = 2;
|
||||||
_connectionStatusController.add("authorizing");
|
_connectionStatusController.add("authorizing");
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.connected,
|
||||||
|
message: 'Handshake успешен',
|
||||||
|
);
|
||||||
|
_startHealthMonitoring();
|
||||||
|
|
||||||
if (_onlineCompleter != null && !_onlineCompleter!.isCompleted) {
|
if (_onlineCompleter != null && !_onlineCompleter!.isCompleted) {
|
||||||
_onlineCompleter!.complete();
|
_onlineCompleter!.complete();
|
||||||
@@ -334,6 +371,11 @@ extension ApiServiceConnection on ApiService {
|
|||||||
if (decodedMessage is Map && decodedMessage['cmd'] == 3) {
|
if (decodedMessage is Map && decodedMessage['cmd'] == 3) {
|
||||||
final error = decodedMessage['payload'];
|
final error = decodedMessage['payload'];
|
||||||
print('Ошибка сервера: $error');
|
print('Ошибка сервера: $error');
|
||||||
|
_healthMonitor.onError(error?['message'] ?? 'server_error');
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.error,
|
||||||
|
message: error?['message'],
|
||||||
|
);
|
||||||
|
|
||||||
if (error != null && error['localizedMessage'] != null) {
|
if (error != null && error['localizedMessage'] != null) {
|
||||||
_errorController.add(error['localizedMessage']);
|
_errorController.add(error['localizedMessage']);
|
||||||
@@ -557,12 +599,22 @@ extension ApiServiceConnection on ApiService {
|
|||||||
print('Ошибка WebSocket: $error');
|
print('Ошибка WebSocket: $error');
|
||||||
_isSessionOnline = false;
|
_isSessionOnline = false;
|
||||||
_isSessionReady = false;
|
_isSessionReady = false;
|
||||||
|
_healthMonitor.onError(error.toString());
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.error,
|
||||||
|
message: error.toString(),
|
||||||
|
);
|
||||||
_reconnect();
|
_reconnect();
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
print('WebSocket соединение закрыто. Попытка переподключения...');
|
print('WebSocket соединение закрыто. Попытка переподключения...');
|
||||||
_isSessionOnline = false;
|
_isSessionOnline = false;
|
||||||
_isSessionReady = false;
|
_isSessionReady = false;
|
||||||
|
_stopHealthMonitoring();
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.disconnected,
|
||||||
|
message: 'Соединение закрыто',
|
||||||
|
);
|
||||||
|
|
||||||
if (!_isSessionReady) {
|
if (!_isSessionReady) {
|
||||||
_reconnect();
|
_reconnect();
|
||||||
@@ -577,6 +629,7 @@ extension ApiServiceConnection on ApiService {
|
|||||||
|
|
||||||
_isReconnecting = true;
|
_isReconnecting = true;
|
||||||
_reconnectAttempts++;
|
_reconnectAttempts++;
|
||||||
|
_healthMonitor.onReconnect();
|
||||||
|
|
||||||
if (_reconnectAttempts > ApiService._maxReconnectAttempts) {
|
if (_reconnectAttempts > ApiService._maxReconnectAttempts) {
|
||||||
print(
|
print(
|
||||||
@@ -584,6 +637,10 @@ extension ApiServiceConnection on ApiService {
|
|||||||
);
|
);
|
||||||
_connectionStatusController.add("disconnected");
|
_connectionStatusController.add("disconnected");
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.error,
|
||||||
|
message: 'Превышено число попыток переподключения',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,6 +664,11 @@ extension ApiServiceConnection on ApiService {
|
|||||||
"Переподключаемся после ${delay.inSeconds}s... (попытка $_reconnectAttempts/${ApiService._maxReconnectAttempts})",
|
"Переподключаемся после ${delay.inSeconds}s... (попытка $_reconnectAttempts/${ApiService._maxReconnectAttempts})",
|
||||||
);
|
);
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.reconnecting,
|
||||||
|
attemptNumber: _reconnectAttempts,
|
||||||
|
reconnectDelay: delay,
|
||||||
|
);
|
||||||
_connectWithFallback();
|
_connectWithFallback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -708,6 +770,11 @@ extension ApiServiceConnection on ApiService {
|
|||||||
_handshakeSent = false;
|
_handshakeSent = false;
|
||||||
_onlineCompleter = Completer<void>();
|
_onlineCompleter = Completer<void>();
|
||||||
_chatsFetchedInThisSession = false;
|
_chatsFetchedInThisSession = false;
|
||||||
|
_stopHealthMonitoring();
|
||||||
|
_updateConnectionState(
|
||||||
|
conn_state.ConnectionState.disconnected,
|
||||||
|
message: 'Отключено пользователем',
|
||||||
|
);
|
||||||
|
|
||||||
_channel?.sink.close(status.goingAway);
|
_channel?.sink.close(status.goingAway);
|
||||||
_channel = null;
|
_channel = null;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import 'dart:async';
|
|||||||
import '../connection/connection_logger.dart';
|
import '../connection/connection_logger.dart';
|
||||||
import '../connection/connection_state.dart' as conn_state;
|
import '../connection/connection_state.dart' as conn_state;
|
||||||
import '../connection/health_monitor.dart';
|
import '../connection/health_monitor.dart';
|
||||||
import '../api_service_v2.dart';
|
import 'package:gwid/api/api_service.dart';
|
||||||
|
|
||||||
|
|
||||||
class ConnectionDebugPanel extends StatefulWidget {
|
class ConnectionDebugPanel extends StatefulWidget {
|
||||||
@@ -39,7 +39,7 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
void _setupSubscriptions() {
|
void _setupSubscriptions() {
|
||||||
|
|
||||||
_logsSubscription = Stream.periodic(const Duration(seconds: 1))
|
_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) {
|
.listen((logs) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -49,7 +49,7 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
_stateSubscription = ApiServiceV2.instance.connectionState.listen((state) {
|
_stateSubscription = ApiService.instance.connectionState.listen((state) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_stateHistory.add(state);
|
_stateHistory.add(state);
|
||||||
@@ -61,7 +61,7 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
_healthSubscription = ApiServiceV2.instance.healthMetrics.listen((health) {
|
_healthSubscription = ApiService.instance.healthMetrics.listen((health) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_healthMetrics.add(health);
|
_healthMetrics.add(health);
|
||||||
@@ -93,7 +93,7 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: Colors.black.withValues(alpha: 0.1),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, -5),
|
offset: const Offset(0, -5),
|
||||||
),
|
),
|
||||||
@@ -113,7 +113,7 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
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)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -223,10 +223,10 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getLogColor(log.level).withOpacity(0.1),
|
color: _getLogColor(log.level).withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _getLogColor(log.level).withOpacity(0.3),
|
color: _getLogColor(log.level).withValues(alpha: 0.3),
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -297,10 +297,10 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getStateColor(state.state).withOpacity(0.1),
|
color: _getStateColor(state.state).withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _getStateColor(state.state).withOpacity(0.3),
|
color: _getStateColor(state.state).withValues(alpha: 0.3),
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -372,10 +372,10 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
margin: const EdgeInsets.all(8),
|
margin: const EdgeInsets.all(8),
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getHealthColor(health.quality).withOpacity(0.1),
|
color: _getHealthColor(health.quality).withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _getHealthColor(health.quality).withOpacity(0.3),
|
color: _getHealthColor(health.quality).withValues(alpha: 0.3),
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -444,7 +444,7 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
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(
|
child: const Center(
|
||||||
@@ -455,7 +455,7 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
|
|
||||||
Widget _buildStatsTab() {
|
Widget _buildStatsTab() {
|
||||||
return FutureBuilder<Map<String, dynamic>>(
|
return FutureBuilder<Map<String, dynamic>>(
|
||||||
future: ApiServiceV2.instance
|
future: ApiService.instance
|
||||||
.getStatistics(), // Указываем Future, который нужно ожидать
|
.getStatistics(), // Указываем Future, который нужно ожидать
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|
||||||
@@ -667,7 +667,12 @@ class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _clearLogs() {
|
void _clearLogs() {
|
||||||
|
ConnectionLogger().clearLogs();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_logs = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _exportLogs() {
|
void _exportLogs() {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import 'dart:async';
|
|||||||
|
|
||||||
import '../connection/connection_state.dart' as conn_state;
|
import '../connection/connection_state.dart' as conn_state;
|
||||||
import '../connection/health_monitor.dart';
|
import '../connection/health_monitor.dart';
|
||||||
import '../api_service_v2.dart';
|
import 'package:gwid/api/api_service.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionStatusWidget extends StatefulWidget {
|
class ConnectionStatusWidget extends StatefulWidget {
|
||||||
@@ -37,7 +39,7 @@ class _ConnectionStatusWidgetState extends State<ConnectionStatusWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _setupSubscriptions() {
|
void _setupSubscriptions() {
|
||||||
_stateSubscription = ApiServiceV2.instance.connectionState.listen((state) {
|
_stateSubscription = ApiService.instance.connectionState.listen((state) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentState = state;
|
_currentState = state;
|
||||||
@@ -46,7 +48,7 @@ class _ConnectionStatusWidgetState extends State<ConnectionStatusWidget> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (widget.showHealthMetrics) {
|
if (widget.showHealthMetrics) {
|
||||||
_healthSubscription = ApiServiceV2.instance.healthMetrics.listen((
|
_healthSubscription = ApiService.instance.healthMetrics.listen((
|
||||||
health,
|
health,
|
||||||
) {
|
) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -77,10 +79,10 @@ class _ConnectionStatusWidgetState extends State<ConnectionStatusWidget> {
|
|||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getStatusColor().withOpacity(0.1),
|
color: _getStatusColor().withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _getStatusColor().withOpacity(0.3),
|
color: _getStatusColor().withValues(alpha: 0.3),
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -125,7 +127,7 @@ class _ConnectionStatusWidgetState extends State<ConnectionStatusWidget> {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: _getStatusColor().withOpacity(0.5),
|
color: _getStatusColor().withValues(alpha: 0.5),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
spreadRadius: 1,
|
spreadRadius: 1,
|
||||||
),
|
),
|
||||||
@@ -177,7 +179,7 @@ class _ConnectionStatusWidgetState extends State<ConnectionStatusWidget> {
|
|||||||
Text(
|
Text(
|
||||||
'$label: ',
|
'$label: ',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: _getStatusColor().withOpacity(0.7),
|
color: _getStatusColor().withValues(alpha: 0.7),
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -200,9 +202,9 @@ class _ConnectionStatusWidgetState extends State<ConnectionStatusWidget> {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getHealthColor().withOpacity(0.1),
|
color: _getHealthColor().withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -233,7 +235,7 @@ class _ConnectionStatusWidgetState extends State<ConnectionStatusWidget> {
|
|||||||
return Container(
|
return Container(
|
||||||
height: 4,
|
height: 4,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey.withOpacity(0.2),
|
color: Colors.grey.withValues(alpha: 0.2),
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(2),
|
||||||
),
|
),
|
||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
@@ -348,7 +350,7 @@ class _ConnectionIndicatorState extends State<ConnectionIndicator> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StreamBuilder<conn_state.ConnectionInfo>(
|
return StreamBuilder<conn_state.ConnectionInfo>(
|
||||||
stream: ApiServiceV2.instance.connectionState,
|
stream: ApiService.instance.connectionState,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@@ -382,11 +384,11 @@ class _ConnectionIndicatorState extends State<ConnectionIndicator> {
|
|||||||
width: widget.size,
|
width: widget.size,
|
||||||
height: widget.size,
|
height: widget.size,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withOpacity(0.3 + (0.7 * value)),
|
color: color.withValues(alpha: 0.3 + (0.7 * value)),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: color.withOpacity(0.5 * value),
|
color: color.withValues(alpha: 0.5 * value),
|
||||||
blurRadius: 8 * value,
|
blurRadius: 8 * value,
|
||||||
spreadRadius: 2 * value,
|
spreadRadius: 2 * value,
|
||||||
),
|
),
|
||||||
@@ -412,7 +414,7 @@ class _ConnectionIndicatorState extends State<ConnectionIndicator> {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: color.withOpacity(0.5),
|
color: color.withValues(alpha: 0.5),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
spreadRadius: 1,
|
spreadRadius: 1,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user