diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ui/ConversationsListScreen.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ui/ConversationsListScreen.kt index 899222af6f..3338f34144 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/ui/ConversationsListScreen.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ui/ConversationsListScreen.kt @@ -140,6 +140,7 @@ fun ConversationsListScreen( // ViewModel state val entries by viewModel.conversationListEntriesFlow.collectAsStateWithLifecycle() val rooms by viewModel.getRoomsStateFlow.collectAsStateWithLifecycle() + val visibleRooms by viewModel.visibleRoomsFlow.collectAsStateWithLifecycle() val isShimmerVisible by viewModel.isShimmerVisible.collectAsStateWithLifecycle() val isSearchActive by viewModel.isSearchActiveFlow.collectAsStateWithLifecycle() val searchQuery by viewModel.currentSearchQueryFlow.collectAsStateWithLifecycle() @@ -184,8 +185,17 @@ fun ConversationsListScreen( val hasConversationTagsCapability = state.currentUser?.capabilities?.spreedCapability?.let { CapabilitiesUtil.hasSpreedFeatureCapability(it, SpreedFeatures.CONVERSATION_TAGS) } == true + val nonEmptyConversationTags = remember(conversationTags, visibleRooms) { + conversationTags.filter { tag -> + if (tag.type == ConversationTag.TYPE_FAVORITES) { + visibleRooms.any { it.favorite } + } else { + visibleRooms.any { it.tagIds.contains(tag.id) } + } + } + } val showConversationTagsRow = hasConversationTagsCapability && - conversationTags.isNotEmpty() && + nonEmptyConversationTags.isNotEmpty() && !isSearchActive && !showShareTo && !isForward @@ -336,7 +346,7 @@ fun ConversationsListScreen( tagsRowContent = if (showConversationTagsRow) { { ConversationTagsRow( - tags = conversationTags, + tags = nonEmptyConversationTags, selectedTagId = selectedTagFilter, onTagSelected = { tagId -> val isFavorites = conversationTags.any { diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt index fb03272fa8..f890e3f730 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt @@ -255,6 +255,14 @@ class ConversationsListViewModel @Inject constructor( selectedTagIsFavoritesFlow.value = tagId != null && isFavorites } + private val searchDisplayModeFlow = combine(_isSearchActiveFlow, _currentSearchQueryFlow) { active, query -> + when { + !active -> SearchDisplayMode.OFF + query.isEmpty() -> SearchDisplayMode.ALL_CONVERSATIONS + else -> SearchDisplayMode.RESULTS + } + } + /** * Single source of truth for the [ConversationList] LazyColumn. * Auto-reacts to rooms, filter, tag filter, search-active and search-result changes. @@ -262,19 +270,29 @@ class ConversationsListViewModel @Inject constructor( val conversationListEntriesFlow: StateFlow> = combine( getRoomsStateFlow, _filterStateFlow, - combine(_isSearchActiveFlow, _currentSearchQueryFlow) { active, query -> - when { - !active -> SearchDisplayMode.OFF - query.isEmpty() -> SearchDisplayMode.ALL_CONVERSATIONS - else -> SearchDisplayMode.RESULTS - } - }, + searchDisplayModeFlow, combine(_selectedTagFilterFlow, selectedTagIsFavoritesFlow, ::TagFilterSelection), combine(searchResultEntries, hideRoomToken, ::Pair) ) { rooms, filterState, searchMode, tagFilter, (searchResults, hideToken) -> buildConversationListEntries(rooms, filterState, searchMode, tagFilter, searchResults, hideToken) }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) + /** + * Rooms that would actually be shown in the list under the current filter chips + * (mention/unread/archive) and hidden-room/lobby rules, but *before* narrowing to a + * specific tag. Used to decide which tag filter chips currently have a matching, + * visible conversation - e.g. a tag whose only conversation is archived should not + * show up as a chip unless the "Archived" filter is active. + */ + val visibleRoomsFlow: StateFlow> = combine( + getRoomsStateFlow, + _filterStateFlow, + searchDisplayModeFlow, + hideRoomToken + ) { rooms, filterState, searchMode, hideToken -> + baseFilterRooms(rooms, filterState, searchMode, hideToken) + }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) + /** Clears the tag filter when the filtered-by tag no longer exists (e.g. it was deleted). */ fun clearTagFilterIfMatches(tagId: String) { if (_selectedTagFilterFlow.value == tagId) { @@ -640,6 +658,34 @@ class ConversationsListViewModel @Inject constructor( ): List { if (searchMode == SearchDisplayMode.RESULTS) return searchResults + var filtered = baseFilterRooms(rooms, filterState, searchMode, hideToken) + + if (searchMode != SearchDisplayMode.ALL_CONVERSATIONS) { + filtered = when { + tagFilter.isFavorites -> filtered.filter { it.favorite } + tagFilter.tagId != null -> filtered.filter { it.tagIds.contains(tagFilter.tagId) } + else -> filtered + } + } + + val sorted = filtered.sortedWith( + compareByDescending { it.favorite } + .thenByDescending { it.lastActivity } + ) + return sorted.map { ConversationListEntry.ConversationEntry(it) } + } + + /** + * Applies the hidden-room/lobby rules and the current filter chips (mention/unread/archive), + * but not the tag filter. Shared by [buildConversationListEntries] and [visibleRoomsFlow] so + * that both the list content and the tag-chip visibility agree on what "visible" means. + */ + private fun baseFilterRooms( + rooms: List, + filterState: Map, + searchMode: SearchDisplayMode, + hideToken: String? + ): List { val hasFilterEnabled = filterState[MENTION] == true || filterState[UNREAD] == true || filterState[ARCHIVE] == true @@ -655,25 +701,12 @@ class ConversationsListViewModel @Inject constructor( filtered = when { // While search is open with an empty query, all conversations are listed, - // ignoring active filters, the tag filter and the default hiding of archived/future-event rooms + // ignoring active filters and the default hiding of archived/future-event rooms searchMode == SearchDisplayMode.ALL_CONVERSATIONS -> filtered hasFilterEnabled -> filtered.filter { filterConversationModel(it, filterState) } else -> filtered.filter { !isFutureEvent(it) && !it.hasArchived } } - - if (searchMode != SearchDisplayMode.ALL_CONVERSATIONS) { - filtered = when { - tagFilter.isFavorites -> filtered.filter { it.favorite } - tagFilter.tagId != null -> filtered.filter { it.tagIds.contains(tagFilter.tagId) } - else -> filtered - } - } - - val sorted = filtered.sortedWith( - compareByDescending { it.favorite } - .thenByDescending { it.lastActivity } - ) - return sorted.map { ConversationListEntry.ConversationEntry(it) } + return filtered } @Suppress("CyclomaticComplexMethod", "NestedBlockDepth")