package chats.presentation.chat_list import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import chats.domain.model.Chat import chats.domain.repository.ChatRepository import chats.data.remote.signalr.ChatHubClient import chats.data.remote.signalr.ConnectionStatus import chats.data.remote.signalr.ChatEvent import core.network.NetworkManager import core.network.ServerConfig import core.security.TokenManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject import chats.data.repository.toDomain private const val TAG = "ChatListViewModel" data class ChatListState( val chats: List = emptyList(), val isLoading: Boolean = false, val error: String? = null, val isStoriesEnabled: Boolean = true ) @HiltViewModel class ChatListViewModel @Inject constructor( private val repository: ChatRepository, private val hubClient: ChatHubClient, private val serverConfig: ServerConfig, private val tokenManager: TokenManager, private val networkManager: NetworkManager ) : ViewModel() { private val _state = MutableStateFlow(ChatListState()) val state: StateFlow = _state.asStateFlow() init { loadChats() observeSignalRStatus() observeSignalREvents() observeNetworkStatus() } private fun observeNetworkStatus() { // При восстановлении сети обновляем чаты networkManager.isOnline .filter { it } // Только переход в онлайн .distinctUntilChanged() .onEach { android.util.Log.d(TAG, "Network restored in chat list, refreshing chats") kotlinx.coroutines.delay(1000) // Дадим сети стабилизироваться repository.getChats() } .launchIn(viewModelScope) } private fun getCurrentUserId(): String = tokenManager.getUserId() ?: "" fun loadChats() { viewModelScope.launch { _state.update { it.copy(isLoading = true) } try { // Пробуем загрузить из сети (это закэширует в Room) repository.getChats() } catch (e: Exception) { android.util.Log.d(TAG, "Initial load failed, will use cache") } // Подписываемся на Flow из Room (всегда работает, даже оффлайн) repository.getChatsFlow() .catch { e -> android.util.Log.e(TAG, "Flow error", e) emit(emptyList()) } .collect { chats -> _state.update { it.copy( chats = sortChats(chats), isLoading = false ) } } } } private fun observeSignalRStatus() { // Наблюдаем за статусом подключения SignalR и обновляем чаты при переподключении hubClient.status .filter { it == ConnectionStatus.CONNECTED } .distinctUntilChanged() .onEach { android.util.Log.d(TAG, "SignalR connected, refreshing chats") // При переподключении обновляем чаты из сети repository.getChats() } .launchIn(viewModelScope) } private fun sortChats(chats: List): List { return chats.sortedWith(compareByDescending { it.name.equals("Избранное", ignoreCase = true) || it.name.equals("Saved Messages", ignoreCase = true) }.thenByDescending { it.lastMessage?.createdAt }) } private fun observeSignalREvents() { val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/") android.util.Log.d(TAG, "Starting to observe SignalR events") hubClient.events .onEach { event -> android.util.Log.d(TAG, ">>> ChatListVM received event: ${event::class.simpleName}") when (event) { is ChatEvent.NewMessage -> { updateChatsWithNewMessage(event) } is ChatEvent.NewChat -> { val currentUserId = getCurrentUserId() _state.update { it.copy(chats = listOf(event.chat.toDomain(currentUserId, baseUrl)) + it.chats) } } is ChatEvent.MessagesRead -> { if (event.userId == getCurrentUserId()) { _state.update { currentState -> val updatedChats = currentState.chats.map { chat -> if (chat.id == event.chatId) { chat.copy(unreadCount = 0) } else chat } currentState.copy(chats = sortChats(updatedChats)) } } } else -> Unit } } .launchIn(viewModelScope) } private fun updateChatsWithNewMessage(event: ChatEvent.NewMessage) { val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/") val currentUserId = getCurrentUserId() _state.update { currentState -> val chatIndex = currentState.chats.indexOfFirst { it.id.equals(event.message.chatId, ignoreCase = true) } if (chatIndex >= 0) { // Чат есть в списке - обновляем его val chat = currentState.chats[chatIndex] val isMyMessage = event.message.senderId == currentUserId val isAlreadySeen = chat.lastMessage?.id == event.message.id val lastMsgDomain = event.message.toDomain(currentUserId, baseUrl) val newCount = if (isMyMessage || isAlreadySeen) { chat.unreadCount } else { chat.unreadCount + 1 } if (!isAlreadySeen) { android.util.Log.d("ChatListVM", "Message ${event.message.id} -> Count ${chat.unreadCount} -> $newCount") } val updatedChat = chat.copy( lastMessage = lastMsgDomain, unreadCount = newCount ) val updatedChats = currentState.chats.toMutableList() updatedChats[chatIndex] = updatedChat currentState.copy(chats = sortChats(updatedChats)) } else { // Чата нет в списке - обновляем весь список из репозитория android.util.Log.d("ChatListVM", "Chat ${event.message.chatId} not found in list, refreshing from repository") viewModelScope.launch { try { repository.getChats() // Это обновит Room и Flow } catch (e: Exception) { android.util.Log.e("ChatListVM", "Failed to refresh chats", e) } } currentState } } } }