Skip to content
Open
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
6 changes: 6 additions & 0 deletions app/src/main/kotlin/com/wire/android/navigation/Navigator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ class Navigator(
steps--
}
}

override fun getBackStackEntry(route: String) = try {
navController.getBackStackEntry(route)
} catch (_: IllegalArgumentException) {
null
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.wire.android.ui.home.cell

import androidx.compose.runtime.Composable
import com.ramcosta.composedestinations.generated.app.destinations.HomeScreenDestination
import com.wire.android.feature.cells.ui.AllFilesScreen
import com.wire.android.navigation.annotation.app.WireHomeDestination
import com.wire.android.ui.home.HomeStateHolder
Expand All @@ -29,5 +30,6 @@ fun GlobalCellsScreen(
) {
AllFilesScreen(
navigator = homeStateHolder.navigator,
parentRoute = HomeScreenDestination.route
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ object PreviewNavigator : WireNavigator {
override fun navigateBack() { /* No-op */ }
override fun navigateBackAndRemoveAllConsecutive(currentRoute: String) { /* No-op */ }
override fun navigateBackAndRemoveAllConsecutiveXTimes(currentRoute: String, stepsBack: Int) { /* No-op */ }
override fun getBackStackEntry(route: String) = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
*/
package com.wire.android.navigation

import androidx.navigation.NavBackStackEntry

interface WireNavigator {
fun navigate(navigationCommand: NavigationCommand)
fun navigateBack()
fun navigateBackAndRemoveAllConsecutive(currentRoute: String)
fun navigateBackAndRemoveAllConsecutiveXTimes(currentRoute: String, stepsBack: Int)
fun getBackStackEntry(route: String): NavBackStackEntry?
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
Expand All @@ -44,8 +45,20 @@ import com.wire.android.ui.common.topappbar.search.SearchTopBar
fun AllFilesScreen(
navigator: WireNavigator,
modifier: Modifier = Modifier,
viewModel: CellViewModel = hiltViewModel()
parentRoute: String? = null,
) {
// Scope CellViewModel to the parentRoute's back stack entry when available.
// This allows SearchScreen (which uses the same parentRoute) to share the same
// already-loaded ViewModel instance and avoid a blank screen on navigation.
val backStackEntry = remember(navigator, parentRoute) {
parentRoute?.let { navigator.getBackStackEntry(it) }
}
val viewModel: CellViewModel = if (backStackEntry != null) {
hiltViewModel(backStackEntry)
} else {
hiltViewModel()
}

val pagingListItems = viewModel.nodesFlow.collectAsLazyPagingItems()

WireScaffold(
Expand All @@ -59,7 +72,12 @@ fun AllFilesScreen(
searchQueryTextState = rememberTextFieldState(),
onTap = {
navigator.navigate(
NavigationCommand(SearchScreenDestination(screenType = DriveSearchScreenType.DRIVE))
NavigationCommand(
SearchScreenDestination(
screenType = DriveSearchScreenType.DRIVE,
parentRoute = parentRoute,
)
)
)
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
import com.wire.android.feature.cells.util.FileHelper
import com.wire.android.feature.cells.util.FileNameResolver
import com.wire.android.ui.common.ActionsViewModel
import com.wire.android.feature.cells.ui.search.sort.SortingCriteria
import com.wire.android.feature.cells.ui.search.sort.toKaliumCriteria
import com.wire.kalium.cells.data.FileFilters
import com.wire.kalium.cells.data.SortingSpec
import com.wire.kalium.cells.domain.model.Node
import com.wire.kalium.cells.domain.usecase.DeleteCellAssetUseCase
import com.wire.kalium.cells.domain.usecase.GetEditorUrlUseCase
Expand Down Expand Up @@ -134,6 +137,16 @@

private val cellAvailableFlow = MutableStateFlow(false)

// AllFiles context (no conversationId, not recycle bin) defaults to newest-first;
// ConversationFiles and RecycleBin default to folders-first.
private val _defaultSortingCriteria = MutableStateFlow(
if (navArgs.conversationId == null && !(navArgs.isRecycleBin ?: false)) {
SortingCriteria.ByDate.NewestFirst
} else {
SortingCriteria.FoldersFirst
}
)

private var isCollaboraEnabled: Boolean = false

init {
Expand All @@ -158,42 +171,48 @@
}

refreshTrigger.flatMapLatest {
combine(
getCellFilesPaged(
conversationId = navArgs.conversationId,
fileFilters = FileFilters(
onlyDeleted = navArgs.isRecycleBin ?: false,
),
).cachedIn(viewModelScope),
removedItemsFlow,
downloadDataFlow
) { pagingData, removedItems, downloadData ->
var emittedRefreshDone = false

pagingData
.filter { node: Node -> node.uuid !in removedItems }
.map { node ->
if (!emittedRefreshDone) {
emittedRefreshDone = true

if (_isPullToRefresh.value) {
_isPullToRefresh.value = false
_defaultSortingCriteria.flatMapLatest { sortingCriteria ->
combine(
getCellFilesPaged(
conversationId = navArgs.conversationId,
fileFilters = FileFilters(
onlyDeleted = navArgs.isRecycleBin ?: false,
),
sortingSpec = SortingSpec(
criteria = sortingCriteria.toKaliumCriteria(),
descending = sortingCriteria.isDescending,
),
).cachedIn(viewModelScope),
removedItemsFlow,
downloadDataFlow
) { pagingData, removedItems, downloadData ->
var emittedRefreshDone = false

pagingData
.filter { node: Node -> node.uuid !in removedItems }
.map { node ->
if (!emittedRefreshDone) {
emittedRefreshDone = true

if (_isPullToRefresh.value) {
_isPullToRefresh.value = false
}

_pagingRefreshDone.tryEmit(Unit)
}

_pagingRefreshDone.tryEmit(Unit)
}

when (node) {
is Node.Folder -> node.toUiModel().copy(
downloadProgress = downloadData[node.uuid]?.progress
)
when (node) {
is Node.Folder -> node.toUiModel().copy(
downloadProgress = downloadData[node.uuid]?.progress
)

is Node.File -> node.toUiModel().copy(
downloadProgress = downloadData[node.uuid]?.progress,
localPath = downloadData[node.uuid]?.localPath?.toString()
)
is Node.File -> node.toUiModel().copy(
downloadProgress = downloadData[node.uuid]?.progress,
localPath = downloadData[node.uuid]?.localPath?.toString()
)
}
}
}
}
}
}
}.shareIn(viewModelScope, started = SharingStarted.Eagerly, replay = 1)
Expand Down Expand Up @@ -260,7 +279,7 @@

isConversationFiles() -> "${currentNodeUuid()}/${cellNode.name}"
else -> cellNode.remotePath
} ?: run {

Check warning on line 282 in features/cells/src/main/java/com/wire/android/feature/cells/ui/CellViewModel.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this useless elvis operation ?:, it always succeeds.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-android&issues=AZ14S3yk0GpFjC_qHyL8&open=AZ14S3yk0GpFjC_qHyL8&pullRequest=4722
sendAction(ShowError(CellError.OTHER_ERROR))
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ package com.wire.android.feature.cells.ui.search
data class SearchNavArgs(
val conversationId: String? = null,
val screenType: DriveSearchScreenType = DriveSearchScreenType.SHARED_DRIVE,
val parentRoute: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavBackStackEntry
import androidx.paging.compose.collectAsLazyPagingItems
import com.ramcosta.composedestinations.generated.cells.destinations.AddRemoveTagsScreenDestination
import com.ramcosta.composedestinations.generated.cells.destinations.ConversationFilesScreenDestination
import com.ramcosta.composedestinations.generated.cells.destinations.MoveToFolderScreenDestination
import com.ramcosta.composedestinations.generated.cells.destinations.PublicLinkScreenDestination
import com.ramcosta.composedestinations.generated.cells.destinations.RenameNodeScreenDestination
Expand All @@ -64,7 +66,6 @@ import com.wire.android.ui.common.bottomsheet.WireSheetValue
import com.wire.android.ui.common.bottomsheet.rememberWireModalSheetState
import com.wire.android.ui.common.scaffold.WireScaffold
import com.wire.android.ui.common.topappbar.search.SearchTopBar
import kotlinx.coroutines.flow.first

@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@WireCellsDestination(
Expand All @@ -77,8 +78,22 @@ fun SearchScreen(
animatedVisibilityScope: AnimatedVisibilityScope,
modifier: Modifier = Modifier,
searchScreenViewModel: SearchScreenViewModel = hiltViewModel(),
cellViewModel: CellViewModel = hiltViewModel(),
) {
// Reuse the same CellViewModel instance from the parent screen if available in the backstack.
// Try parentRoute first, then ConversationFilesScreen, then fallback.
val backStackEntry: NavBackStackEntry? = remember(navigator) {
val parentRoute = searchScreenViewModel.parentRoute
if (parentRoute != null) {
navigator.getBackStackEntry(parentRoute)
} else {
navigator.getBackStackEntry(ConversationFilesScreenDestination.route)
}
}
val cellViewModel: CellViewModel = if (backStackEntry != null) {
hiltViewModel(backStackEntry)
} else {
hiltViewModel()
}

val uiState by searchScreenViewModel.uiState.collectAsStateWithLifecycle()

Expand Down Expand Up @@ -166,14 +181,16 @@ fun SearchScreen(
}
) { innerPadding ->
val lazyListState = rememberLazyListState()
val lazyItems = searchScreenViewModel.cellNodesFlow.collectAsLazyPagingItems()

val isShowingFilteredResults = uiState.hasAnyFilter ||
searchState.text.isNotEmpty() ||
uiState.sortingCriteria != searchScreenViewModel.defaultSortingCriteria
val initialItems = cellViewModel.nodesFlow.collectAsLazyPagingItems()
val filteredItems = searchScreenViewModel.cellNodesFlow.collectAsLazyPagingItems()
val lazyItems = if (isShowingFilteredResults) filteredItems else initialItems

LaunchedEffect(uiState.sortingCriteria) {
lazyItems.refresh()
// wait for refresh to complete
snapshotFlow { lazyItems.loadState.refresh }
.first { it is androidx.paging.LoadState.NotLoading }
lazyListState.animateScrollToItem(0)
lazyListState.animateScrollToItem(0)
}

CellScreenContent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package com.wire.android.feature.cells.ui.search
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.LoadState
import androidx.paging.LoadStates
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
Expand Down Expand Up @@ -86,8 +88,21 @@ class SearchScreenViewModel @Inject constructor(
private val navArgs: SearchNavArgs = SearchScreenDestination.argsFrom(savedStateHandle)

val screenType = navArgs.screenType
val parentRoute = navArgs.parentRoute

/**
* The default (no-filter) sorting for this screen type.
* DRIVE (all-files) defaults to newest-first; SHARED_DRIVE (conversation) defaults to folders-first.
*/
val defaultSortingCriteria: SortingCriteria = if (screenType == DriveSearchScreenType.DRIVE) {
SortingCriteria.ByDate.NewestFirst
} else {
SortingCriteria.FoldersFirst
}

private val _uiState = MutableStateFlow(SearchUiState())
private val _uiState = MutableStateFlow(
SearchUiState(sortingCriteria = defaultSortingCriteria)
)
val uiState: StateFlow<SearchUiState> = _uiState.asStateFlow()

private val queryFlow = MutableStateFlow("")
Expand Down Expand Up @@ -115,28 +130,47 @@ class SearchScreenViewModel @Inject constructor(

val cellNodesFlow: Flow<PagingData<CellNodeUi>> =
searchParamsFlow.flatMapLatest<SearchParams, PagingData<CellNodeUi>> { params: SearchParams ->
getCellFilesPaged(
conversationId = params.conversationId,
query = params.query,
fileFilters = FileFilters(
tags = params.tagIds,
owners = params.ownerIds,
mimeTypes = params.mimeTypes,
hasPublicLink = params.filesWithPublicLink,
),
sortingSpec = SortingSpec(
criteria = params.sortingCriteria.toKaliumCriteria(),
descending = params.sortingCriteria.isDescending
)
).map { pagingData: PagingData<Node> ->
pagingData.map { node: Node ->
when (node) {
is Node.Folder -> node.toUiModel()
is Node.File -> node.toUiModel()
val hasFilters = params.sortingCriteria != defaultSortingCriteria ||
params.query.isNotEmpty() ||
params.tagIds.isNotEmpty() ||
params.ownerIds.isNotEmpty() ||
params.mimeTypes.isNotEmpty() ||
params.filesWithPublicLink == true

if (!hasFilters) {
return@flatMapLatest kotlinx.coroutines.flow.flowOf(
PagingData.empty(
LoadStates(
refresh = LoadState.Loading,
prepend = LoadState.NotLoading(true),
append = LoadState.NotLoading(true),
)
)
)
}

getCellFilesPaged(
conversationId = params.conversationId,
query = params.query,
fileFilters = FileFilters(
tags = params.tagIds,
owners = params.ownerIds,
mimeTypes = params.mimeTypes,
hasPublicLink = params.filesWithPublicLink,
),
sortingSpec = SortingSpec(
criteria = params.sortingCriteria.toKaliumCriteria(),
descending = params.sortingCriteria.isDescending
)
).map { pagingData: PagingData<Node> ->
pagingData.map { node: Node ->
when (node) {
is Node.Folder -> node.toUiModel()
is Node.File -> node.toUiModel()
}
}
}
}
}.cachedIn(viewModelScope)
}.cachedIn(viewModelScope)

init {
loadTags()
Expand Down Expand Up @@ -341,7 +375,8 @@ class SearchScreenViewModel @Inject constructor(
}

fun defaultCriteriaFor(by: SortBy): SortingCriteria = when (by) {
SortBy.Modified -> SortingCriteria.Modified.NewestFirst
SortBy.Name -> SortingCriteria.Name.AtoZ
SortBy.Size -> SortingCriteria.Size.SmallestFirst
SortBy.Default -> SortingCriteria.FoldersFirst
SortBy.Modified -> SortingCriteria.ByDate.NewestFirst
SortBy.Name -> SortingCriteria.ByName.AtoZ
SortBy.Size -> SortingCriteria.BySize.SmallestFirst
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ data class SearchUiState(

val isSearchActive: Boolean = true,

val sortingCriteria: SortingCriteria = SortingCriteria.Modified.NewestFirst,
val sortingCriteria: SortingCriteria = SortingCriteria.ByDate.NewestFirst,
) {
val tagsCount: Int get() = availableTags.count { it.selected }
val typeCount: Int get() = availableTypes.count { it.selected }
Expand Down
Loading
Loading