Files
forkmessager/client-mobile/chats/presentation/chat_list/ChatListViewModel.kt
Халимов Рустам 9daa786cfb Заготовка
2026-05-07 23:27:37 +03:00

184 lines
6.8 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.ChatEvent
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
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 authRepository: auth.domain.repository.AuthRepository,
private val signalrClient: ChatHubClient,
private val serverConfig: ServerConfig,
private val tokenManager: TokenManager
) : ViewModel() {
private val _state = MutableStateFlow(ChatListState())
val state: StateFlow<ChatListState> = _state.asStateFlow()
init {
updatePushToken()
val isStoriesEnabled = try {
serverConfig.getServerConfig().features.stories
} catch (e: Exception) {
true
}
_state.update { it.copy(isStoriesEnabled = isStoriesEnabled) }
val token = tokenManager.getToken()
val baseUrl = serverConfig.getBaseUrl()
// Подключаемся к SignalR только если есть токен И введён URL сервера
if (token != null && baseUrl.isNotBlank()) {
signalrClient.connect(baseUrl.removeSuffix("/api/"), token)
}
// Single Source of Truth - подписываемся на Flow из Room
observeChatsFromLocal()
// Запускаем периодическую синхронизацию
viewModelScope.launch {
repository.schedulePeriodicSync()
}
observeSignalREvents()
}
/**
* Подписка на локальные чаты из Room (Single Source of Truth)
*/
private fun observeChatsFromLocal() {
repository.getChatsFlow()
.onEach { chats ->
_state.update { currentState ->
currentState.copy(
chats = sortChats(chats),
isLoading = false
)
}
}
.catch { e ->
android.util.Log.e("ChatListVM", "Error observing chats", e)
_state.update { it.copy(isLoading = false, error = e.message) }
}
.launchIn(viewModelScope)
}
/**
* Принудительная синхронизация чатов с сервером
*/
fun loadChats() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
try {
repository.syncChats()
} catch (e: Exception) {
_state.update { it.copy(isLoading = false, error = e.message) }
}
}
}
private fun updatePushToken() {
com.google.firebase.messaging.FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
viewModelScope.launch {
try {
authRepository.updatePushToken(token)
} catch (e: Exception) {
android.util.Log.e("ChatListVM", "Failed to update push token: ${e.message}")
}
}
}
}
}
private fun getCurrentUserId(): String = tokenManager.getUserId() ?: ""
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() {
signalrClient.events
.onEach { event ->
when (event) {
is ChatEvent.NewMessage -> {
updateChatsWithNewMessage(event)
}
is ChatEvent.NewChat -> {
val currentUserId = getCurrentUserId()
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
_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 updatedChats = currentState.chats.map { chat ->
if (chat.id.equals(event.message.chatId, ignoreCase = true)) {
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")
}
chat.copy(
lastMessage = lastMsgDomain,
unreadCount = newCount
)
} else chat
}
currentState.copy(chats = sortChats(updatedChats))
}
}
}