refactor: NavHostViewModel stage 4 - extract favorites state holder

This commit is contained in:
Garfie 2026-04-07 20:19:20 -05:00
parent d942fbf3a0
commit 72054ad442
3 changed files with 78 additions and 46 deletions

View file

@ -0,0 +1,72 @@
package com.garfiec.librechat.shared.navigation
import co.touchlab.kermit.Logger
import com.garfiec.librechat.core.data.datastore.SettingsDataStore
import com.garfiec.librechat.core.model.Conversation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class FavoritesStateHolder(
private val settingsDataStore: SettingsDataStore,
private val recentConversations: StateFlow<List<Conversation>>,
private val scope: CoroutineScope,
) {
private val _favorites = MutableStateFlow<Set<String>>(emptySet())
val favorites: StateFlow<Set<String>> = _favorites.asStateFlow()
private val _favoriteConversations = MutableStateFlow<List<Conversation>>(emptyList())
val favoriteConversations: StateFlow<List<Conversation>> = _favoriteConversations.asStateFlow()
init {
observeBookmarks()
observeRecentConversations()
}
private fun observeBookmarks() {
scope.launch {
try {
settingsDataStore.bookmarkedConversationIds.collect { bookmarkedIds ->
_favorites.value = bookmarkedIds
updateFavoriteConversations()
}
} catch (e: Exception) {
Logger.w(e) { "Failed to observe bookmarks" }
}
}
}
private fun observeRecentConversations() {
scope.launch {
recentConversations.collect {
updateFavoriteConversations()
}
}
}
fun toggleFavorite(conversationId: String) {
scope.launch {
settingsDataStore.toggleBookmark(conversationId)
}
}
private fun updateFavoriteConversations() {
val bookmarkedIds = _favorites.value
val allConversations = recentConversations.value
_favoriteConversations.value = allConversations.filter { conversation ->
conversation.conversationId in bookmarkedIds ||
conversation.tags.any {
it.equals("favorite", ignoreCase = true) ||
it.equals("bookmark", ignoreCase = true)
}
}
}
fun reset() {
_favorites.value = emptySet()
_favoriteConversations.value = emptyList()
}
}

View file

@ -68,15 +68,13 @@ class NavHostViewModel(
private val bannerStateHolder = BannerStateHolder(bannerRepository, viewModelScope) private val bannerStateHolder = BannerStateHolder(bannerRepository, viewModelScope)
private val versionCheckStateHolder = VersionCheckStateHolder(configRepository, settingsDataStore, viewModelScope) private val versionCheckStateHolder = VersionCheckStateHolder(configRepository, settingsDataStore, viewModelScope)
private val conversationListStateHolder = ConversationListStateHolder(conversationRepository, viewModelScope) private val conversationListStateHolder = ConversationListStateHolder(conversationRepository, viewModelScope)
private val favoritesStateHolder = FavoritesStateHolder(settingsDataStore, conversationListStateHolder.recentConversations, viewModelScope)
private val _isLoggedIn = MutableStateFlow(tokenManager.isAuthenticated) private val _isLoggedIn = MutableStateFlow(tokenManager.isAuthenticated)
val isLoggedIn: StateFlow<Boolean> = _isLoggedIn.asStateFlow() val isLoggedIn: StateFlow<Boolean> = _isLoggedIn.asStateFlow()
val versionMismatch: StateFlow<VersionMismatchState?> = versionCheckStateHolder.versionMismatch val versionMismatch: StateFlow<VersionMismatchState?> = versionCheckStateHolder.versionMismatch
private val _favorites = MutableStateFlow<Set<String>>(emptySet())
private val _favoriteConversations = MutableStateFlow<List<Conversation>>(emptyList())
val banners: StateFlow<List<Banner>> = bannerStateHolder.banners val banners: StateFlow<List<Banner>> = bannerStateHolder.banners
val dismissedBannerIds: StateFlow<Set<String>> = bannerStateHolder.dismissedBannerIds val dismissedBannerIds: StateFlow<Set<String>> = bannerStateHolder.dismissedBannerIds
@ -110,8 +108,8 @@ class NavHostViewModel(
combine( combine(
conversationListStateHolder.groupedConversations, conversationListStateHolder.groupedConversations,
conversationListStateHolder.activeConversationId, conversationListStateHolder.activeConversationId,
_favorites, favoritesStateHolder.favorites,
_favoriteConversations, favoritesStateHolder.favoriteConversations,
conversationListStateHolder.searchQuery, conversationListStateHolder.searchQuery,
) { grouped, activeId, favIds, favConvos, query -> ) { grouped, activeId, favIds, favConvos, query ->
DrawerDataSnapshot(grouped, activeId, favIds, favConvos, query) DrawerDataSnapshot(grouped, activeId, favIds, favConvos, query)
@ -148,19 +146,9 @@ class NavHostViewModel(
Logger.w(e) { "Failed to check auth state on init" } Logger.w(e) { "Failed to check auth state on init" }
} }
} }
observeBookmarks()
observeRecentConversationsForFavorites()
bannerStateHolder.fetchBanners() bannerStateHolder.fetchBanners()
} }
private fun observeRecentConversationsForFavorites() {
viewModelScope.launch {
conversationListStateHolder.recentConversations.collect {
updateFavoriteConversations()
}
}
}
fun loadMoreConversations() { fun loadMoreConversations() {
conversationListStateHolder.loadMoreConversations() conversationListStateHolder.loadMoreConversations()
} }
@ -184,35 +172,8 @@ class NavHostViewModel(
conversationListStateHolder.onSearchQueryChanged(query) conversationListStateHolder.onSearchQueryChanged(query)
} }
private fun observeBookmarks() {
viewModelScope.launch {
try {
settingsDataStore.bookmarkedConversationIds.collect { bookmarkedIds ->
_favorites.value = bookmarkedIds
updateFavoriteConversations()
}
} catch (e: Exception) {
Logger.w(e) { "Failed to observe bookmarks" }
}
}
}
fun toggleFavorite(conversationId: String) { fun toggleFavorite(conversationId: String) {
viewModelScope.launch { favoritesStateHolder.toggleFavorite(conversationId)
settingsDataStore.toggleBookmark(conversationId)
}
}
private fun updateFavoriteConversations() {
val bookmarkedIds = _favorites.value
val allConversations = conversationListStateHolder.recentConversations.value
_favoriteConversations.value = allConversations.filter { conversation ->
conversation.conversationId in bookmarkedIds ||
conversation.tags.any {
it.equals("favorite", ignoreCase = true) ||
it.equals("bookmark", ignoreCase = true)
}
}
} }
fun setTabletSidebarOpen(open: Boolean) { fun setTabletSidebarOpen(open: Boolean) {
@ -238,8 +199,7 @@ class NavHostViewModel(
authRepository.logout() authRepository.logout()
_isLoggedIn.value = false _isLoggedIn.value = false
conversationListStateHolder.reset() conversationListStateHolder.reset()
_favorites.value = emptySet() favoritesStateHolder.reset()
_favoriteConversations.value = emptyList()
_sidebarMode.value = SidebarMode.Conversations _sidebarMode.value = SidebarMode.Conversations
_selectedSettingsCategory.value = null _selectedSettingsCategory.value = null
} }

View file

@ -46,7 +46,7 @@ class VersionCheckStateHolder(
is Result.Error -> { is Result.Error -> {
Logger.w(result.exception) { "Failed to check backend version: ${result.message}" } Logger.w(result.exception) { "Failed to check backend version: ${result.message}" }
} }
else -> {} is Result.Loading -> Unit
} }
} }
} }