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 @@ -24,6 +24,7 @@ import android.content.Context
import android.net.Uri
import android.text.format.DateUtils
import androidx.activity.compose.BackHandler
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
Expand Down Expand Up @@ -58,10 +59,10 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
Expand All @@ -77,13 +78,11 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.annotation.VisibleForTesting
import androidx.compose.ui.text.style.TextOverflow
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.LoadState
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.ramcosta.composedestinations.generated.app.destinations.ConversationScreenDestination
Expand All @@ -99,11 +98,13 @@ import com.ramcosta.composedestinations.result.NavResult.Value
import com.ramcosta.composedestinations.result.OpenResultRecipient
import com.ramcosta.composedestinations.result.ResultBackNavigator
import com.ramcosta.composedestinations.result.ResultRecipient
import com.sebaslogen.resaca.rememberKeysInScope
import com.wire.android.BuildConfig.IS_BUBBLE_UI_ENABLED
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.feature.analytics.AnonymousAnalyticsManagerImpl
import com.wire.android.feature.analytics.model.AnalyticsEvent
import com.wire.android.feature.cells.ui.dialog.IncompatibleFileNameDialog
import com.wire.android.feature.sketch.model.DrawingCanvasNavArgs
import com.wire.android.feature.sketch.model.DrawingCanvasNavBackArgs
import com.wire.android.mapper.MessageDateTimeGroup
Expand Down Expand Up @@ -145,9 +146,8 @@ import com.wire.android.ui.emoji.EmojiPickerBottomSheet
import com.wire.android.ui.home.conversations.AuthorHeaderHelper.rememberShouldHaveSmallBottomPadding
import com.wire.android.ui.home.conversations.AuthorHeaderHelper.rememberShouldShowHeader
import com.wire.android.ui.home.conversations.ConversationSnackbarMessages.OnFileDownloaded
import com.wire.android.ui.home.conversations.attachment.MessageAttachmentsViewModel
import com.wire.android.ui.home.conversations.attachment.IncompatibleFileNameDialogState
import com.wire.android.feature.cells.ui.dialog.IncompatibleFileNameDialog
import com.wire.android.ui.home.conversations.attachment.MessageAttachmentsViewModel
import com.wire.android.ui.home.conversations.banner.ConversationBanner
import com.wire.android.ui.home.conversations.banner.ConversationBannerViewModel
import com.wire.android.ui.home.conversations.call.ConversationCallViewActions
Expand Down Expand Up @@ -197,6 +197,7 @@ import com.wire.android.util.openDownloadFolder
import com.wire.android.util.serverDate
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.android.util.ui.UIText
import com.wire.android.util.ui.collectAsLazyPagingItemsWithLifecycle
import com.wire.kalium.common.error.NetworkFailure
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode
Expand All @@ -208,7 +209,6 @@ import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.user.type.isInternal
import com.wire.kalium.logic.data.user.type.isTeamAdmin
import com.wire.kalium.logic.feature.call.usecase.ConferenceCallingResult
import com.sebaslogen.resaca.rememberKeysInScope
import kotlinx.collections.immutable.PersistentMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
Expand Down Expand Up @@ -1150,7 +1150,7 @@ private fun ConversationScreenContent(
isBubbleUiEnabled: Boolean = false,
isWireCellsEnabled: Boolean = false,
) {
val lazyPagingMessages = messages.collectAsLazyPagingItems()
val lazyPagingMessages = messages.collectAsLazyPagingItemsWithLifecycle()

val lazyListState = rememberSaveable(unreadEventCount, lazyPagingMessages, saver = LazyListState.Saver) {
LazyListState(unreadEventCount)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ import androidx.compose.ui.platform.LocalInspectionMode
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.ramcosta.composedestinations.generated.app.destinations.BrowseChannelsScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ConversationFoldersScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ConversationScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.DebugConversationScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.NewConversationSearchPeopleScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.OtherUserProfileScreenDestination
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.feature.analytics.AnonymousAnalyticsManagerImpl
Expand All @@ -52,12 +57,6 @@ import com.wire.android.ui.common.search.SearchBarState
import com.wire.android.ui.common.search.rememberSearchbarState
import com.wire.android.ui.common.visbility.rememberVisibilityState
import com.wire.android.ui.debug.conversation.DebugConversationScreenNavArgs
import com.ramcosta.composedestinations.generated.app.destinations.BrowseChannelsScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ConversationFoldersScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ConversationScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.DebugConversationScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.NewConversationSearchPeopleScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.OtherUserProfileScreenDestination
import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState
import com.wire.android.ui.home.conversationslist.common.ConversationList
import com.wire.android.ui.home.conversationslist.model.ConversationItem
Expand All @@ -67,6 +66,7 @@ import com.wire.android.ui.home.conversationslist.search.SearchConversationsEmpt
import com.wire.android.ui.theme.WireTheme
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.android.util.ui.SnackBarMessageHandler
import com.wire.android.util.ui.collectAsLazyPagingItemsWithLifecycle
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.UserId

Expand Down Expand Up @@ -164,7 +164,7 @@ fun ConversationsScreenContent(

when (val state = conversationListViewModel.conversationListState) {
is ConversationListState.Paginated -> {
val lazyPagingItems = state.conversations.collectAsLazyPagingItems()
val lazyPagingItems = state.conversations.collectAsLazyPagingItemsWithLifecycle()
searchBarState.searchVisibleChanged(lazyPagingItems.itemCount > 0 || searchBarState.isSearchActive)
when {
// when conversation list is not yet fetched, show loading indicator
Expand Down
3 changes: 3 additions & 0 deletions core/ui-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ dependencies {

implementation(libs.visibilityModifiers)

implementation(libs.androidx.paging3)
implementation(libs.androidx.paging3Compose)

// hilt
implementation(libs.hilt.navigationCompose)
implementation(libs.hilt.work)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.util.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.Flow
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

/**
* Collects a [Flow] of [PagingData] as [LazyPagingItems] that are lifecycle-aware.
*
* This function ensures that the collection of the paging data is tied to the lifecycle of the
* composable, preventing memory leaks and unnecessary work when the composable is not active.
*
* @param minActiveState The minimum lifecycle state at which the flow should be collected. Default is [Lifecycle.State.RESUMED].
* @param context The coroutine context to use for collecting the flow. Default is [EmptyCoroutineContext].
* @param lifecycle The lifecycle to use for determining when to collect the flow. Default is the current one from [LocalLifecycleOwner].
* @return A [LazyPagingItems] instance that can be used in a LazyColumn or similar composable.
*/
@Composable
fun <T : Any> Flow<PagingData<T>>.collectAsLazyPagingItemsWithLifecycle(
minActiveState: Lifecycle.State = Lifecycle.State.RESUMED,
context: CoroutineContext = EmptyCoroutineContext,
lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle
): LazyPagingItems<T> {
val isPreview = LocalInspectionMode.current
val lifecycleAwareFlow = remember(this, lifecycle, isPreview) {
if (isPreview) this else this.flowWithLifecycle(lifecycle, minActiveState)
}
return lifecycleAwareFlow.collectAsLazyPagingItems(context)
}
Loading