Второй этап выхода из офлайн режима
This commit is contained in:
@@ -29,6 +29,7 @@ interface ChatApi {
|
||||
@Path("chatId") chatId: String,
|
||||
@Query("cursor") cursor: String? = null,
|
||||
@Query("pivot") pivot: Long? = null,
|
||||
@Query("afterSequenceId") afterSequenceId: Long? = null,
|
||||
@Query("limit") limit: Int? = 50
|
||||
): List<MessageDto>
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ enum class ConnectionStatus { CONNECTED, CONNECTING, DISCONNECTED }
|
||||
@Singleton
|
||||
class ChatHubClient @Inject constructor() {
|
||||
private var hubConnection: HubConnection? = null
|
||||
private val _events = MutableSharedFlow<ChatEvent>(extraBufferCapacity = 1024)
|
||||
// extraBufferCapacity=1024 позволяет буферизовать события пока нет подписчиков
|
||||
private val _events = MutableSharedFlow<ChatEvent>(replay = 0, extraBufferCapacity = 1024)
|
||||
val events: SharedFlow<ChatEvent> = _events.asSharedFlow()
|
||||
|
||||
private val _status = MutableStateFlow(ConnectionStatus.DISCONNECTED)
|
||||
@@ -56,32 +57,59 @@ class ChatHubClient @Inject constructor() {
|
||||
private var lastToken: String? = null
|
||||
|
||||
fun connect(baseUrl: String, accessToken: String) {
|
||||
if (hubConnection?.connectionState == HubConnectionState.CONNECTED) return
|
||||
// Проверяем текущее состояние
|
||||
val currentState = hubConnection?.connectionState
|
||||
if (currentState == HubConnectionState.CONNECTED) {
|
||||
Log.d("ChatHubClient", "Already connected, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
// Если соединение в процессе - останавливаем его
|
||||
if (currentState == HubConnectionState.CONNECTING) {
|
||||
Log.d("ChatHubClient", "Connection in progress ($currentState), stopping first...")
|
||||
hubConnection?.stop()
|
||||
}
|
||||
|
||||
// Сохраняем параметры для переподключения
|
||||
lastBaseUrl = baseUrl
|
||||
lastToken = accessToken
|
||||
_status.value = ConnectionStatus.CONNECTING
|
||||
|
||||
hubConnection = HubConnectionBuilder.create("${baseUrl}/hubs/chat")
|
||||
Log.d("ChatHubClient", "Connecting to ${baseUrl}/hubs/chat with token: ${accessToken.take(10)}...")
|
||||
|
||||
// Создаем новое соединение
|
||||
val newHubConnection = HubConnectionBuilder.create("${baseUrl}/hubs/chat")
|
||||
.withAccessTokenProvider(Single.just(accessToken))
|
||||
.build()
|
||||
|
||||
hubConnection = newHubConnection
|
||||
|
||||
setupHandlers()
|
||||
|
||||
hubConnection?.onClosed { exception ->
|
||||
Log.e("ChatHubClient", "Connection closed. Reconnecting...", exception)
|
||||
_status.value = ConnectionStatus.DISCONNECTED
|
||||
scope.launch {
|
||||
delay(5000)
|
||||
connect(baseUrl, accessToken)
|
||||
// Проверяем, есть ли еще актуальные параметры для переподключения
|
||||
val reconnectBaseUrl = lastBaseUrl
|
||||
val reconnectToken = lastToken
|
||||
|
||||
if (reconnectBaseUrl != null && reconnectToken != null) {
|
||||
Log.d("ChatHubClient", "Attempting reconnection with saved parameters...")
|
||||
delay(5000)
|
||||
connect(reconnectBaseUrl, reconnectToken)
|
||||
} else {
|
||||
Log.w("ChatHubClient", "Cannot reconnect: missing baseUrl or token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
try {
|
||||
Log.d("ChatHubClient", "Starting SignalR connection...")
|
||||
hubConnection?.start()?.blockingAwait()
|
||||
_status.value = ConnectionStatus.CONNECTED
|
||||
Log.d("ChatHubClient", "SignalR Connected")
|
||||
Log.d("ChatHubClient", "SignalR Connected successfully!")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ChatHubClient", "SignalR Connection Error", e)
|
||||
_status.value = ConnectionStatus.DISCONNECTED
|
||||
@@ -91,19 +119,25 @@ class ChatHubClient @Inject constructor() {
|
||||
|
||||
private fun setupHandlers() {
|
||||
hubConnection?.let { conn ->
|
||||
Log.d("ChatHubClient", "Setting up SignalR handlers")
|
||||
|
||||
conn.on("new_message", { message: MessageDto ->
|
||||
Log.d("ChatHubClient", ">>> new_message event received: ${message.id} in chat ${message.chatId}")
|
||||
_events.tryEmit(ChatEvent.NewMessage(message))
|
||||
}, MessageDto::class.java)
|
||||
|
||||
conn.on("message_edited", { messageId: String, chatId: String, content: String ->
|
||||
Log.d("ChatHubClient", ">>> message_edited event: $messageId")
|
||||
_events.tryEmit(ChatEvent.MessageEdited(messageId, chatId, content))
|
||||
}, String::class.java, String::class.java, String::class.java)
|
||||
|
||||
conn.on("message_deleted", { messageId: String, chatId: String ->
|
||||
Log.d("ChatHubClient", ">>> message_deleted event: $messageId")
|
||||
_events.tryEmit(ChatEvent.MessageDeleted(messageId, chatId))
|
||||
}, String::class.java, String::class.java)
|
||||
|
||||
conn.on("messages_read", { data: MessagesReadEvent ->
|
||||
Log.d("ChatHubClient", ">>> messages_read event: ${data.effectiveChatId}")
|
||||
_events.tryEmit(ChatEvent.MessagesRead(
|
||||
data.effectiveChatId,
|
||||
data.effectiveUserId,
|
||||
@@ -124,10 +158,12 @@ class ChatHubClient @Inject constructor() {
|
||||
}, String::class.java)
|
||||
|
||||
conn.on("new_chat", { chat: ChatDto ->
|
||||
Log.d("ChatHubClient", ">>> new_chat event: ${chat.id}")
|
||||
_events.tryEmit(ChatEvent.NewChat(chat))
|
||||
}, ChatDto::class.java)
|
||||
|
||||
conn.on("reaction_added", { data: ReactionEvent ->
|
||||
Log.d("ChatHubClient", ">>> reaction_added event: ${data.emoji} on ${data.messageId}")
|
||||
_events.tryEmit(ChatEvent.ReactionUpdated(
|
||||
data.messageId ?: "",
|
||||
data.chatId ?: "",
|
||||
@@ -138,6 +174,7 @@ class ChatHubClient @Inject constructor() {
|
||||
}, ReactionEvent::class.java)
|
||||
|
||||
conn.on("reaction_removed", { data: ReactionEvent ->
|
||||
Log.d("ChatHubClient", ">>> reaction_removed event: ${data.emoji} on ${data.messageId}")
|
||||
_events.tryEmit(ChatEvent.ReactionUpdated(
|
||||
data.messageId ?: "",
|
||||
data.chatId ?: "",
|
||||
@@ -185,6 +222,26 @@ class ChatHubClient @Inject constructor() {
|
||||
_status.value = ConnectionStatus.DISCONNECTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Принудительное переподключение - останавливает текущее соединение и создает новое
|
||||
*/
|
||||
fun reconnect() {
|
||||
Log.d("ChatHubClient", "Forced reconnect requested")
|
||||
val baseUrl = lastBaseUrl
|
||||
val token = lastToken
|
||||
|
||||
if (baseUrl != null && token != null) {
|
||||
disconnect()
|
||||
// Небольшая задержка перед переподключением
|
||||
scope.launch {
|
||||
delay(1000)
|
||||
connect(baseUrl, token)
|
||||
}
|
||||
} else {
|
||||
Log.w("ChatHubClient", "Cannot reconnect: missing saved credentials")
|
||||
}
|
||||
}
|
||||
|
||||
fun addReaction(messageId: String, chatId: String, emoji: String) {
|
||||
if (hubConnection?.connectionState == HubConnectionState.CONNECTED) {
|
||||
hubConnection?.invoke("add_reaction", mapOf(
|
||||
@@ -250,12 +307,13 @@ class ChatHubClient @Inject constructor() {
|
||||
}
|
||||
|
||||
if (hubConnection?.connectionState == HubConnectionState.CONNECTED) {
|
||||
Log.d("ChatHubClient", "Joining chat room: $chatId")
|
||||
hubConnection?.invoke("join_chat", chatId)
|
||||
?.doOnError { Log.e("ChatHubClient", "join_chat error", it) }
|
||||
?.subscribe()
|
||||
Log.d("ChatHubClient", "Joined chat room: $chatId")
|
||||
} else {
|
||||
Log.e("ChatHubClient", "Failed to join chat room $chatId: Not connected")
|
||||
Log.e("ChatHubClient", "Failed to join chat room $chatId: Not connected (state=${hubConnection?.connectionState})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package chats.data.remote.signalr
|
||||
|
||||
import android.content.Context
|
||||
import core.network.NetworkManager
|
||||
import core.network.ServerConfig
|
||||
import core.notifications.data.ActiveChatTracker
|
||||
import core.notifications.data.NotificationHelper
|
||||
import core.security.TokenManager
|
||||
import chats.data.sync.MessageSyncWorker
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -12,8 +15,15 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import chats.data.remote.signalr.ConnectionStatus
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Singleton
|
||||
class SignalRNotificationObserver @Inject constructor(
|
||||
@@ -21,41 +31,92 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
private val activeChatTracker: ActiveChatTracker,
|
||||
private val tokenManager: TokenManager,
|
||||
private val chatRepository: chats.domain.repository.ChatRepository,
|
||||
private val serverConfig: ServerConfig,
|
||||
private val networkManager: NetworkManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private var isStarted = false
|
||||
private val processedMessageIds = mutableSetOf<String>()
|
||||
|
||||
fun refresh() {
|
||||
scope.launch {
|
||||
try {
|
||||
val chats = chatRepository.getChats()
|
||||
val total = chats.sumOf { it.unreadCount }
|
||||
activeChatTracker.setTotalUnreadCount(total)
|
||||
} catch (e: Exception) {
|
||||
// Ignore load error
|
||||
}
|
||||
}
|
||||
}
|
||||
// OkHttpClient для ping запроса
|
||||
private val pingClient = OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
fun start() {
|
||||
if (isStarted) return
|
||||
isStarted = true
|
||||
|
||||
// Запускаем мониторинг сети
|
||||
networkManager.startMonitoring()
|
||||
|
||||
// Принудительно обновляем состояние сети при старте
|
||||
networkManager.refreshNetworkState()
|
||||
|
||||
// Подключаемся к SignalR при старте приложения
|
||||
connectSignalR()
|
||||
|
||||
// Initial count load
|
||||
refresh()
|
||||
|
||||
|
||||
// Запускаем периодическую проверку подключения SignalR
|
||||
startConnectionHealthCheck()
|
||||
|
||||
// Слушаем восстановление сети и переподключаем SignalR
|
||||
networkManager.isOnline
|
||||
.filter { it } // Только переход в онлайн
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
android.util.Log.d("SignalRNtfObserver", "Network restored! Reconnecting SignalR and syncing...")
|
||||
// Небольшая задержка чтобы сеть стабилизировалась
|
||||
kotlinx.coroutines.delay(1500)
|
||||
reconnectOnNetworkRestored()
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
||||
// Также отслеживаем состояние SignalR для переподключения
|
||||
signalrClient.status
|
||||
.filter { it == ConnectionStatus.DISCONNECTED }
|
||||
.onEach {
|
||||
android.util.Log.d("SignalRNtfObserver", "SignalR disconnected, checking network...")
|
||||
// Если мы offline и SignalR отключен - не делаем ничего
|
||||
// Подключимся когда сеть восстановится
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
||||
// При восстановлении соединения SignalR обновляем список чатов и вступаем в них
|
||||
signalrClient.status
|
||||
.filter { it == ConnectionStatus.CONNECTED }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
android.util.Log.d("SignalRNtfObserver", "SignalR connected, refreshing chats and joining rooms")
|
||||
onSignalRConnected()
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
||||
// Слушаем события SignalR для уведомлений и обновления списка чатов
|
||||
android.util.Log.d("SignalRNtfObserver", "Starting to listen to signalrClient.events")
|
||||
signalrClient.events
|
||||
.onEach { event ->
|
||||
android.util.Log.d("SignalRNtfObserver", ">>> Received event: ${event::class.simpleName}")
|
||||
when (event) {
|
||||
is ChatEvent.NewMessage -> {
|
||||
val currentUserId = tokenManager.getUserId()
|
||||
val message = event.message
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "New message: ${message.id} from ${message.senderId}, current: $currentUserId")
|
||||
|
||||
// Don't show if it's our message or already processed
|
||||
if (message.senderId == currentUserId) return@onEach
|
||||
if (processedMessageIds.contains(message.id)) return@onEach
|
||||
if (message.senderId == currentUserId) {
|
||||
android.util.Log.d("SignalRNtfObserver", "Skipping - own message")
|
||||
return@onEach
|
||||
}
|
||||
if (processedMessageIds.contains(message.id)) {
|
||||
android.util.Log.d("SignalRNtfObserver", "Skipping - already processed")
|
||||
return@onEach
|
||||
}
|
||||
|
||||
// Mark as processed
|
||||
processedMessageIds.add(message.id)
|
||||
@@ -70,8 +131,12 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
refresh()
|
||||
|
||||
// Don't show if this chat is currently open
|
||||
if (activeChatTracker.currentChatId.value == message.chatId) return@onEach
|
||||
if (activeChatTracker.currentChatId.value == message.chatId) {
|
||||
android.util.Log.d("SignalRNtfObserver", "Skipping - chat is open: ${message.chatId}")
|
||||
return@onEach
|
||||
}
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Showing notification for chat: ${message.chatId}")
|
||||
NotificationHelper.showNotification(
|
||||
context = context,
|
||||
title = message.sender?.displayName ?: "Новое сообщение",
|
||||
@@ -83,12 +148,236 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
)
|
||||
}
|
||||
is ChatEvent.MessagesRead -> {
|
||||
android.util.Log.d("SignalRNtfObserver", "Messages read event")
|
||||
// If anyone read messages, sync our total count
|
||||
refresh()
|
||||
}
|
||||
else -> Unit
|
||||
else -> {
|
||||
android.util.Log.d("SignalRNtfObserver", "Unhandled event: ${event::class.simpleName}")
|
||||
Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
private fun startConnectionHealthCheck() {
|
||||
// Каждые 10 секунд проверяем подключение и при необходимости переподключаемся
|
||||
scope.launch {
|
||||
var consecutiveFailures = 0
|
||||
|
||||
while (true) {
|
||||
kotlinx.coroutines.delay(10000)
|
||||
val currentStatus = signalrClient.status.value
|
||||
val isOnline = networkManager.isOnline.value
|
||||
|
||||
if (isOnline && currentStatus == ConnectionStatus.DISCONNECTED) {
|
||||
consecutiveFailures++
|
||||
android.util.Log.d("SignalRNtfObserver", "Health check: Network online but SignalR disconnected (failures: $consecutiveFailures), reconnecting...")
|
||||
signalrClient.reconnect()
|
||||
} else if (isOnline && currentStatus == ConnectionStatus.CONNECTED) {
|
||||
// Сбрасываем счетчик ошибок при успешном подключении
|
||||
consecutiveFailures = 0
|
||||
android.util.Log.d("SignalRNtfObserver", "Health check: Connection healthy")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSignalRConnected() {
|
||||
android.util.Log.d("SignalRNtfObserver", "SignalR connected - syncing missed messages...")
|
||||
refresh()
|
||||
// Вступаем во все чаты для получения событий
|
||||
joinAllChats()
|
||||
// Синхронизируем пропущенные сообщения
|
||||
syncMissedMessages()
|
||||
}
|
||||
|
||||
/**
|
||||
* Синхронизирует пропущенные сообщения после восстановления соединения
|
||||
* Запрашивает только НОВЫЕ сообщения с последнего известного sequenceId
|
||||
*/
|
||||
private fun syncMissedMessages() {
|
||||
scope.launch {
|
||||
try {
|
||||
android.util.Log.d("SignalRNtfObserver", "Starting missed messages sync...")
|
||||
val chats = chatRepository.getChats()
|
||||
android.util.Log.d("SignalRNtfObserver", "Syncing ${chats.size} chats for missed messages")
|
||||
|
||||
// Запрашиваем только новые сообщения для каждого чата
|
||||
chats.forEach { chat ->
|
||||
try {
|
||||
// Получаем последний известный sequenceId из локальной базы
|
||||
val lastSequenceId = chatRepository.getLastKnownSequenceId(chat.id)
|
||||
|
||||
if (lastSequenceId != null) {
|
||||
// Запрашиваем сообщения ПОСЛЕ lastSequenceId (только новые)
|
||||
android.util.Log.d("SignalRNtfObserver", "Chat ${chat.id}: last known seqId=$lastSequenceId, fetching newer...")
|
||||
chatRepository.getMessages(
|
||||
chatId = chat.id,
|
||||
afterSequenceId = lastSequenceId.toLong(),
|
||||
limit = 100
|
||||
)
|
||||
} else {
|
||||
// Нет локальных сообщений - загружаем последние 50
|
||||
android.util.Log.d("SignalRNtfObserver", "Chat ${chat.id}: no local messages, fetching last 50")
|
||||
chatRepository.getMessages(chatId = chat.id, limit = 50)
|
||||
}
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Synced chat ${chat.id}")
|
||||
// Небольшая пауза между чатами чтобы не перегружать сервер
|
||||
kotlinx.coroutines.delay(100)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Failed to sync chat ${chat.id}", e)
|
||||
}
|
||||
}
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Missed messages sync completed")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Sync failed", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает последний известный sequenceId для чата из локальной базы
|
||||
*/
|
||||
private suspend fun getLastKnownSequenceId(chatId: String): Int? {
|
||||
return try {
|
||||
chatRepository.getLastKnownSequenceId(chatId)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Failed to get last sequenceId for $chatId", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun reconnectOnNetworkRestored() {
|
||||
android.util.Log.d("SignalRNtfObserver", "Network restored, starting reconnection sequence...")
|
||||
|
||||
scope.launch {
|
||||
// 1. Сначала делаем HTTP ping запрос чтобы "разбудить" сетевой стек
|
||||
android.util.Log.d("SignalRNtfObserver", "Sending HTTP ping to wake up network...")
|
||||
val pingSuccess = sendHttpPing()
|
||||
android.util.Log.d("SignalRNtfObserver", "HTTP ping result: $pingSuccess")
|
||||
|
||||
// 2. Небольшая пауза для стабилизации
|
||||
kotlinx.coroutines.delay(1000)
|
||||
|
||||
// 3. Принудительное переподключение SignalR
|
||||
android.util.Log.d("SignalRNtfObserver", "Forcing SignalR reconnect...")
|
||||
signalrClient.reconnect()
|
||||
|
||||
// 4. Ждем пока SignalR подключится (максимум 10 секунд)
|
||||
var waitCount = 0
|
||||
while (signalrClient.status.value != ConnectionStatus.CONNECTED && waitCount < 20) {
|
||||
kotlinx.coroutines.delay(500)
|
||||
waitCount++
|
||||
}
|
||||
|
||||
if (signalrClient.status.value == ConnectionStatus.CONNECTED) {
|
||||
android.util.Log.d("SignalRNtfObserver", "SignalR reconnected, syncing missed messages...")
|
||||
// 5. Синхронизируем пропущенные сообщения
|
||||
syncMissedMessages()
|
||||
} else {
|
||||
android.util.Log.w("SignalRNtfObserver", "SignalR failed to reconnect within timeout")
|
||||
}
|
||||
|
||||
// 6. Запускаем синхронизацию отложенных сообщений
|
||||
MessageSyncWorker.scheduleSync(context)
|
||||
android.util.Log.d("SignalRNtfObserver", "Outgoing sync worker scheduled")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляет HTTP ping запрос для активации сетевого соединения
|
||||
*/
|
||||
private suspend fun sendHttpPing(): Boolean {
|
||||
return try {
|
||||
val baseUrl = serverConfig.getBaseUrl()
|
||||
val token = tokenManager.getToken()
|
||||
|
||||
if (baseUrl.isBlank() || token == null) {
|
||||
android.util.Log.w("SignalRNtfObserver", "Cannot ping: missing baseUrl or token")
|
||||
return false
|
||||
}
|
||||
|
||||
val url = "${baseUrl.removeSuffix("/api/")}/api/auth/refresh"
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.post(okhttp3.RequestBody.create(null, "{}"))
|
||||
.addHeader("Authorization", "Bearer $token")
|
||||
.build()
|
||||
|
||||
val response = pingClient.newCall(request).execute()
|
||||
val success = response.isSuccessful || response.code == 401 // 401 OK для refresh
|
||||
android.util.Log.d("SignalRNtfObserver", "HTTP ping to $url: ${response.code}")
|
||||
response.close()
|
||||
success
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "HTTP ping failed", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectSignalR() {
|
||||
val token = tokenManager.getToken()
|
||||
val baseUrl = serverConfig.getBaseUrl()
|
||||
|
||||
if (token == null || baseUrl.isBlank()) {
|
||||
android.util.Log.w("SignalRNtfObserver", "Cannot connect SignalR: token=${token != null}, baseUrl=$baseUrl")
|
||||
return
|
||||
}
|
||||
|
||||
val isOnline = networkManager.isOnline.value
|
||||
if (!isOnline) {
|
||||
android.util.Log.w("SignalRNtfObserver", "Cannot connect SignalR: network is offline")
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем текущее состояние SignalR
|
||||
val currentStatus = signalrClient.status.value
|
||||
if (currentStatus == ConnectionStatus.CONNECTED) {
|
||||
android.util.Log.d("SignalRNtfObserver", "SignalR already connected, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Connecting SignalR with token: ${token.take(10)}..., baseUrl: $baseUrl")
|
||||
signalrClient.connect(baseUrl.removeSuffix("/api/"), token)
|
||||
|
||||
// Если подключение не удалось в течение 5 секунд - пробуем снова
|
||||
scope.launch {
|
||||
kotlinx.coroutines.delay(5000)
|
||||
if (signalrClient.status.value == ConnectionStatus.DISCONNECTED) {
|
||||
android.util.Log.w("SignalRNtfObserver", "SignalR connection timeout, retrying with reconnect()...")
|
||||
signalrClient.reconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun joinAllChats() {
|
||||
scope.launch {
|
||||
try {
|
||||
val chats = chatRepository.getChats()
|
||||
android.util.Log.d("SignalRNtfObserver", "Joining ${chats.size} chat rooms")
|
||||
chats.forEach { chat ->
|
||||
signalrClient.joinChat(chat.id)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Failed to join chats", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
scope.launch {
|
||||
try {
|
||||
val chats = chatRepository.getChats()
|
||||
val total = chats.sumOf { it.unreadCount }
|
||||
activeChatTracker.setTotalUnreadCount(total)
|
||||
android.util.Log.d("SignalRNtfObserver", "Refreshed chats: ${chats.size}, total unread: $total")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Failed to refresh", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user