Files
forkmessager/client-mobile/chats/presentation/chat_list/ChatListViewModel.kt
T

190 lines
7.5 KiB
Kotlin

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<Chat> = 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<ChatListState> = _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<Chat>): List<Chat> {
return chats.sortedWith(compareByDescending<Chat> {
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
}
}
}
}