Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
Expand All @@ -74,6 +75,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch

@Suppress("TooManyFunctions")
Expand Down Expand Up @@ -184,6 +186,7 @@ class ConversationListViewModelImpl @AssistedInject constructor(
}
.flowOn(dispatcher.io())
.cachedIn(viewModelScope)
.shareIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(), replay = 1)

override var conversationListState by mutableStateOf(
when (usePagination) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ fun ConversationsScreenContent(
searchBarState: SearchBarState,
emptyListContent: @Composable (domain: String) -> Unit = {},
lazyListState: LazyListState = rememberLazyListState(),
loadingListContent: @Composable (LazyListState) -> Unit = { LoadingListContent(it) },
loadingListContent: @Composable () -> Unit = { LoadingListContent() },
conversationsSource: ConversationsSource = ConversationsSource.MAIN,
conversationListViewModel: ConversationListViewModel = when {
LocalInspectionMode.current -> ConversationListViewModelPreview()
Expand Down Expand Up @@ -170,7 +170,7 @@ fun ConversationsScreenContent(
searchBarState.searchVisibleChanged(lazyPagingItems.itemCount > 0 || searchBarState.isSearchActive)
when {
// when conversation list is not yet fetched, show loading indicator
lazyPagingItems.isLoading() -> loadingListContent(lazyListState)
lazyPagingItems.isLoading() -> loadingListContent()
// when there is at least one conversation
lazyPagingItems.itemCount > 0 -> ConversationList(
lazyPagingConversations = lazyPagingItems,
Expand Down Expand Up @@ -204,7 +204,7 @@ fun ConversationsScreenContent(
searchBarState.searchVisibleChanged(isSearchVisible = hasConversations || searchBarState.isSearchActive)
when {
// when conversation list is not yet fetched, show loading indicator
state.isLoading -> loadingListContent(lazyListState)
state.isLoading -> loadingListContent()
// when there is at least one conversation in any folder
hasConversations -> ConversationList(
lazyListState = lazyListState,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/

package com.wire.android.ui.common.search

import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.runtime.saveable.SaverScope
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class SearchBarStateTest {

@Test
fun `given search visibility changed, when state is restored, then visibility is preserved`() {
val state = SearchBarState(
isSearchActive = true,
searchQueryTextState = TextFieldState(initialText = "query")
)
state.searchVisibleChanged(false)

val restored = with(SearchBarState.saver()) {
val saved = SaverScope { true }.save(state)
restore(saved!!)
}!!

assertTrue(restored.isSearchActive)
assertFalse(restored.isSearchVisible)
assertEquals("query", restored.searchQueryTextState.text.toString())
}

@Test
fun `given old saved search state, when state is restored, then query is preserved`() {
val savedQuery = with(TextFieldState.Saver) {
SaverScope { true }.save(TextFieldState(initialText = "query"))
}

val restored = SearchBarState.saver().restore(listOf(true, savedQuery))!!

assertTrue(restored.isSearchActive)
assertTrue(restored.isSearchVisible)
assertEquals("query", restored.searchQueryTextState.text.toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ fun CollapsingTopBarScaffold(
val maxBarElevationPx = with(LocalDensity.current) { maxBarElevation.toPx() }
val anchoredDraggableState = remember {
AnchoredDraggableState(
initialValue = State.EXPANDED,
initialValue = when {
!collapsingEnabled -> State.EXPANDED
contentLazyListState == null -> State.EXPANDED
contentLazyListState.firstVisibleItemIndex > 0 || contentLazyListState.firstVisibleItemScrollOffset > 0 -> State.COLLAPSED
else -> State.EXPANDED
},
anchors = calculateAnchors(collapsingEnabled, 0),
positionalThreshold = { totalDistance: Float -> totalDistance * 0.5f },
velocityThreshold = { with(density) { 125.dp.toPx() } },
Expand Down Expand Up @@ -240,7 +245,10 @@ fun CollapsingTopBarScaffold(
)
hasCollapsingSegment = collapsingPlaceable.height > 0
hasFooterSegment = footerPlaceable.height > 0
anchoredDraggableState.updateAnchors(calculateAnchors(collapsingEnabled, collapsingPlaceable.height))
anchoredDraggableState.updateAnchors(
newAnchors = calculateAnchors(collapsingEnabled, collapsingPlaceable.height),
newTarget = contentLazyListState.targetTopBarState(collapsingEnabled, collapsingPlaceable.height)
)
layout(constraints.maxWidth, constraints.maxHeight) {
val swipeOffset = anchoredDraggableState.offset.roundToInt()
contentPlaceable.placeRelative(0, collapsingPlaceable.height + footerPlaceable.height + swipeOffset)
Expand All @@ -260,6 +268,12 @@ private fun LazyListState?.calculateContentOffset(maxValue: Float) = when {
else -> maxValue
}

private fun LazyListState?.targetTopBarState(collapsingEnabled: Boolean, collapsingHeight: Int): State = when {
!collapsingEnabled || collapsingHeight == 0 || this == null -> State.EXPANDED
firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0 -> State.COLLAPSED
else -> State.EXPANDED
}

@OptIn(ExperimentalFoundationApi::class)
private fun AnchoredDraggableState<State>.calculateCollapsingHeight() = anchors.positionOf(State.COLLAPSED).let {
if (it.isNaN()) 0f else -it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ import com.wire.android.util.PreviewMultipleThemes

@Composable
fun LoadingListContent(
lazyListState: LazyListState,
modifier: Modifier = Modifier,
userScrollEnabled: Boolean = false,
lazyListState: LazyListState = rememberLazyListState(),
) {
LazyColumn(
state = lazyListState,
userScrollEnabled = userScrollEnabled,
modifier = modifier.fillMaxSize()
) {
items(count = LOADING_PLACEHOLDER_ITEMS_COUNT) { index ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ class SearchBarState(
}

companion object {
fun saver(): Saver<SearchBarState, *> = Saver(
fun saver(): Saver<SearchBarState, List<Any?>> = Saver(
save = {
listOf(
it.isSearchActive,
with(TextFieldState.Saver) {
save(it.searchQueryTextState)
}
},
it.isSearchVisible,
)
},
restore = {
Expand All @@ -82,7 +83,8 @@ class SearchBarState(
with(TextFieldState.Saver) {
restore(it)
}
} ?: TextFieldState()
} ?: TextFieldState(),
isSearchVisible = (it.getOrNull(2) as? Boolean) ?: true
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import kotlin.coroutines.EmptyCoroutineContext
*/
@Composable
fun <T : Any> Flow<PagingData<T>>.collectAsLazyPagingItemsWithLifecycle(
minActiveState: Lifecycle.State = Lifecycle.State.RESUMED,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
context: CoroutineContext = EmptyCoroutineContext,
lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle
): LazyPagingItems<T> {
Expand Down
Loading