From 72054ad44210740d2c8ea7cccf81a05bd72bb325 Mon Sep 17 00:00:00 2001 From: Garfie Date: Tue, 7 Apr 2026 20:19:20 -0500 Subject: [PATCH] refactor: NavHostViewModel stage 4 - extract favorites state holder --- .../shared/navigation/FavoritesStateHolder.kt | 72 +++++++++++++++++++ .../shared/navigation/NavHostViewModel.kt | 50 ++----------- .../navigation/VersionCheckStateHolder.kt | 2 +- 3 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/FavoritesStateHolder.kt diff --git a/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/FavoritesStateHolder.kt b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/FavoritesStateHolder.kt new file mode 100644 index 0000000..43c256c --- /dev/null +++ b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/FavoritesStateHolder.kt @@ -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>, + private val scope: CoroutineScope, +) { + + private val _favorites = MutableStateFlow>(emptySet()) + val favorites: StateFlow> = _favorites.asStateFlow() + + private val _favoriteConversations = MutableStateFlow>(emptyList()) + val favoriteConversations: StateFlow> = _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() + } +} diff --git a/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt index faca131..e5e1f07 100644 --- a/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt +++ b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt @@ -68,15 +68,13 @@ class NavHostViewModel( private val bannerStateHolder = BannerStateHolder(bannerRepository, viewModelScope) private val versionCheckStateHolder = VersionCheckStateHolder(configRepository, settingsDataStore, viewModelScope) private val conversationListStateHolder = ConversationListStateHolder(conversationRepository, viewModelScope) + private val favoritesStateHolder = FavoritesStateHolder(settingsDataStore, conversationListStateHolder.recentConversations, viewModelScope) private val _isLoggedIn = MutableStateFlow(tokenManager.isAuthenticated) val isLoggedIn: StateFlow = _isLoggedIn.asStateFlow() val versionMismatch: StateFlow = versionCheckStateHolder.versionMismatch - private val _favorites = MutableStateFlow>(emptySet()) - private val _favoriteConversations = MutableStateFlow>(emptyList()) - val banners: StateFlow> = bannerStateHolder.banners val dismissedBannerIds: StateFlow> = bannerStateHolder.dismissedBannerIds @@ -110,8 +108,8 @@ class NavHostViewModel( combine( conversationListStateHolder.groupedConversations, conversationListStateHolder.activeConversationId, - _favorites, - _favoriteConversations, + favoritesStateHolder.favorites, + favoritesStateHolder.favoriteConversations, conversationListStateHolder.searchQuery, ) { 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" } } } - observeBookmarks() - observeRecentConversationsForFavorites() bannerStateHolder.fetchBanners() } - private fun observeRecentConversationsForFavorites() { - viewModelScope.launch { - conversationListStateHolder.recentConversations.collect { - updateFavoriteConversations() - } - } - } - fun loadMoreConversations() { conversationListStateHolder.loadMoreConversations() } @@ -184,35 +172,8 @@ class NavHostViewModel( 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) { - viewModelScope.launch { - 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) - } - } + favoritesStateHolder.toggleFavorite(conversationId) } fun setTabletSidebarOpen(open: Boolean) { @@ -238,8 +199,7 @@ class NavHostViewModel( authRepository.logout() _isLoggedIn.value = false conversationListStateHolder.reset() - _favorites.value = emptySet() - _favoriteConversations.value = emptyList() + favoritesStateHolder.reset() _sidebarMode.value = SidebarMode.Conversations _selectedSettingsCategory.value = null } diff --git a/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/VersionCheckStateHolder.kt b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/VersionCheckStateHolder.kt index 3f88d64..78d3295 100644 --- a/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/VersionCheckStateHolder.kt +++ b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/VersionCheckStateHolder.kt @@ -46,7 +46,7 @@ class VersionCheckStateHolder( is Result.Error -> { Logger.w(result.exception) { "Failed to check backend version: ${result.message}" } } - else -> {} + is Result.Loading -> Unit } } }