Skip to content

Commit

Permalink
fix: retain filters and simplify navigation [WPB-14518] (#3737)
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk authored Dec 16, 2024
1 parent 05c53e8 commit d37cf33
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 183 deletions.
78 changes: 4 additions & 74 deletions app/src/main/kotlin/com/wire/android/navigation/HomeDestination.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,14 @@
package com.wire.android.navigation

import androidx.annotation.DrawableRes
import androidx.navigation.NavBackStackEntry
import com.ramcosta.composedestinations.spec.Direction
import com.wire.android.R
import com.wire.android.ui.destinations.AllConversationsScreenDestination
import com.wire.android.ui.destinations.ArchiveScreenDestination
import com.wire.android.ui.destinations.FavoritesConversationsScreenDestination
import com.wire.android.ui.destinations.FolderConversationsScreenDestination
import com.wire.android.ui.destinations.GroupConversationsScreenDestination
import com.wire.android.ui.destinations.OneOnOneConversationsScreenDestination
import com.wire.android.ui.destinations.SettingsScreenDestination
import com.wire.android.ui.destinations.VaultScreenDestination
import com.wire.android.ui.destinations.WhatsNewScreenDestination
import com.wire.android.util.ui.UIText
import com.wire.kalium.logic.data.conversation.ConversationFilter
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf

@Suppress("LongParameterList")
sealed class HomeDestination(
Expand All @@ -45,10 +37,6 @@ sealed class HomeDestination(
val withUserAvatar: Boolean = true,
val direction: Direction
) {

internal fun NavBackStackEntry.baseRouteMatches(): Boolean = direction.route.getBaseRoute() == destination.route?.getBaseRoute()
open fun entryMatches(entry: NavBackStackEntry): Boolean = entry.baseRouteMatches()

data object Conversations : HomeDestination(
title = UIText.StringResource(R.string.conversations_screen_title),
icon = R.drawable.ic_conversation,
Expand All @@ -57,43 +45,6 @@ sealed class HomeDestination(
direction = AllConversationsScreenDestination
)

data object Favorites : HomeDestination(
title = UIText.StringResource(R.string.label_filter_favorites),
icon = R.drawable.ic_conversation,
isSearchable = true,
withNewConversationFab = true,
direction = FavoritesConversationsScreenDestination
)

data class Folder(
val folderNavArgs: FolderNavArgs
) : HomeDestination(
title = UIText.DynamicString(folderNavArgs.folderName),
icon = R.drawable.ic_conversation,
isSearchable = true,
withNewConversationFab = true,
direction = FolderConversationsScreenDestination(folderNavArgs)
) {
override fun entryMatches(entry: NavBackStackEntry): Boolean =
entry.baseRouteMatches() && FolderConversationsScreenDestination.argsFrom(entry).folderId == folderNavArgs.folderId
}

data object Group : HomeDestination(
title = UIText.StringResource(R.string.label_filter_group),
icon = R.drawable.ic_conversation,
isSearchable = true,
withNewConversationFab = true,
direction = GroupConversationsScreenDestination
)

data object OneOnOne : HomeDestination(
title = UIText.StringResource(R.string.label_filter_one_on_one),
icon = R.drawable.ic_conversation,
isSearchable = true,
withNewConversationFab = true,
direction = OneOnOneConversationsScreenDestination
)

data object Settings : HomeDestination(
title = UIText.StringResource(R.string.settings_screen_title),
icon = R.drawable.ic_settings,
Expand Down Expand Up @@ -130,32 +81,11 @@ sealed class HomeDestination(

companion object {
private const val ITEM_NAME_PREFIX = "HomeNavigationItem."
fun values(): PersistentList<HomeDestination> =
persistentListOf(Conversations, Favorites, Group, OneOnOne, Settings, Vault, Archive, Support, WhatsNew)
}
}

fun HomeDestination.currentFilter(): ConversationFilter {
return when (this) {
HomeDestination.Conversations -> ConversationFilter.All
HomeDestination.Favorites -> ConversationFilter.Favorites
HomeDestination.Group -> ConversationFilter.Groups
HomeDestination.OneOnOne -> ConversationFilter.OneOnOne
is HomeDestination.Folder -> ConversationFilter.Folder(folderName = folderNavArgs.folderName, folderId = folderNavArgs.folderId)
HomeDestination.Archive,
HomeDestination.Settings,
HomeDestination.Support,
HomeDestination.Vault,
HomeDestination.WhatsNew -> ConversationFilter.All
}
}
fun fromRoute(fullRoute: String): HomeDestination? =
values().find { it.direction.route.getBaseRoute() == fullRoute.getBaseRoute() }

fun ConversationFilter.toDestination(): HomeDestination {
return when (this) {
ConversationFilter.All -> HomeDestination.Conversations
ConversationFilter.Favorites -> HomeDestination.Favorites
ConversationFilter.Groups -> HomeDestination.Group
ConversationFilter.OneOnOne -> HomeDestination.OneOnOne
is ConversationFilter.Folder -> HomeDestination.Folder(FolderNavArgs(folderId, folderName))
fun values(): Array<HomeDestination> =
arrayOf(Conversations, Settings, Vault, Archive, Support, WhatsNew)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import com.wire.kalium.logic.feature.session.CurrentSessionUseCase
import com.wire.kalium.logic.feature.user.screenshotCensoring.ObserveScreenshotCensoringConfigResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -61,7 +60,7 @@ class CallActivityViewModel @Inject constructor(
}

fun switchAccountIfNeeded(userId: UserId, actions: SwitchAccountActions) {
viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(dispatchers.io()) {
val shouldSwitchAccount = when (val result = currentSession()) {
is CurrentSessionResult.Failure.Generic -> true
CurrentSessionResult.Failure.SessionNotFound -> true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Wire
* Copyright (C) 2024 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.topappbar

import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.wire.kalium.logic.data.conversation.ConversationFilter
import dev.ahmedmourad.bundlizer.Bundlizer

@Composable
fun rememberConversationFilterState(): ConversationFilterState = rememberSaveable(saver = ConversationFilterState.saver()) {
ConversationFilterState()
}

class ConversationFilterState(initialValue: ConversationFilter = ConversationFilter.All) {
var filter: ConversationFilter by mutableStateOf(initialValue)
private set

fun changeFilter(newFilter: ConversationFilter) {
filter = newFilter
}

companion object {
fun saver(): Saver<ConversationFilterState, Bundle> = Saver(
save = {
Bundlizer.bundle(ConversationFilter.serializer(), it.filter)
},
restore = {
ConversationFilterState(Bundlizer.unbundle(ConversationFilter.serializer(), it))
}
)
}
}
21 changes: 5 additions & 16 deletions app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
Expand All @@ -64,13 +63,11 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.di.hiltViewModelScoped
import com.wire.android.navigation.FolderNavArgs
import com.wire.android.navigation.HomeDestination
import com.wire.android.navigation.NavigationCommand
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.WireDestination
import com.wire.android.navigation.handleNavigation
import com.wire.android.navigation.toDestination
import com.wire.android.ui.NavGraphs
import com.wire.android.ui.analytics.AnalyticsUsageViewModel
import com.wire.android.ui.common.CollapsingTopBarScaffold
Expand Down Expand Up @@ -121,14 +118,8 @@ fun HomeScreen(
)
) {
homeViewModel.checkRequirements { it.navigate(navigator::navigate) }
val homeDestinations = remember(foldersViewModel.state().folders) {
HomeDestination.values()
.plus(
foldersViewModel.state().folders.map { HomeDestination.Folder(FolderNavArgs(it.id, it.name)) }
)
}

val homeScreenState = rememberHomeScreenState(navigator, homeDestinations = homeDestinations)
val homeScreenState = rememberHomeScreenState(navigator)
val notificationsPermissionDeniedDialogState = rememberVisibilityState<PermissionPermanentlyDeniedDialogState>()
val showNotificationsPermissionDeniedDialog = {
notificationsPermissionDeniedDialogState.show(
Expand Down Expand Up @@ -318,6 +309,8 @@ fun HomeContent(
exit = shrinkVertically() + fadeOut(),
) {
HomeTopBar(
title = currentTitle.asString(),
currentFilter = currentConversationFilter,
navigationItem = currentNavigationItem,
userAvatarData = homeState.userAvatarData,
elevation = dimensions().spacing0x, // CollapsingTopBarScaffold manages applied elevation
Expand Down Expand Up @@ -347,7 +340,7 @@ fun HomeContent(
}
},
collapsingEnabled = !searchBarState.isSearchActive,
contentLazyListState = homeStateHolder.nullAbleLazyListStateFor(currentNavigationItem),
contentLazyListState = homeStateHolder.lazyListStateFor(currentNavigationItem),
content = {
/**
* This "if" is a workaround, otherwise it can crash because of the SubcomposeLayout's nature.
Expand Down Expand Up @@ -407,11 +400,7 @@ fun HomeContent(
ConversationFilterSheetContent(
onChangeFilter = { filter ->
filterSheetState.hide()
openHomeDestination(filter.toDestination())
},
onChangeFolder = {
filterSheetState.hide()
openHomeDestination(it.toDestination())
homeStateHolder.changeFilter(filter)
},
filterSheetState = sheetContentState
)
Expand Down
53 changes: 36 additions & 17 deletions app/src/main/kotlin/com/wire/android/ui/home/HomeStateHolder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package com.wire.android.ui.home

import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.rememberDrawerState
Expand All @@ -34,10 +33,13 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import com.wire.android.navigation.HomeDestination
import com.wire.android.navigation.HomeDestination.Conversations
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.getBaseRoute
import com.wire.android.navigation.rememberTrackingAnimatedNavController
import com.wire.android.ui.common.topappbar.ConversationFilterState
import com.wire.android.ui.common.topappbar.rememberConversationFilterState
import com.wire.android.ui.common.topappbar.search.SearchBarState
import com.wire.android.ui.common.topappbar.search.rememberSearchbarState
import com.wire.android.ui.home.conversationslist.filter.uiText
import com.wire.kalium.logic.data.conversation.ConversationFilter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

Expand All @@ -49,18 +51,34 @@ class HomeStateHolder(
val searchBarState: SearchBarState,
val navigator: Navigator,
private val currentNavigationItemState: State<HomeDestination>,
private val lazyListStates: Map<HomeDestination, LazyListState>,
private val conversationFilterState: ConversationFilterState,
) {
val currentNavigationItem
get() = currentNavigationItemState.value

fun lazyListStateFor(destination: HomeDestination): LazyListState {
return lazyListStates[destination] ?: error("No LazyListState found for $destination")
}
val currentConversationFilter
get() = conversationFilterState.filter

fun nullAbleLazyListStateFor(destination: HomeDestination): LazyListState? {
return lazyListStates[destination]
}
val currentTitle
get() = when (currentNavigationItemState.value) {
Conversations -> conversationFilterState.filter.uiText()
else -> currentNavigationItemState.value.title
}

private val lazyListStatesMap = mutableMapOf<String, LazyListState>()

fun lazyListStateFor(
destination: HomeDestination,
conversationFilter: ConversationFilter = ConversationFilter.All,
): LazyListState =
lazyListStatesMap.getOrPut(
key = destination.itemName + when (destination) {
Conversations -> ":$conversationFilter" // each filter has its own scroll state
else -> "" // other destinations shouldn't care about the conversation filter
}
) {
LazyListState()
}

fun closeDrawer() {
coroutineScope.launch {
Expand All @@ -73,38 +91,39 @@ class HomeStateHolder(
drawerState.open()
}
}

fun changeFilter(filter: ConversationFilter) = conversationFilterState.changeFilter(filter)
}

@Composable
fun rememberHomeScreenState(
navigator: Navigator,
homeDestinations: List<HomeDestination>,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
navController: NavHostController = rememberTrackingAnimatedNavController { route ->
homeDestinations.find { it.direction.route.getBaseRoute() == route }?.itemName
navController: NavHostController = rememberTrackingAnimatedNavController {
HomeDestination.fromRoute(it)?.itemName
},
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed)
): HomeStateHolder {

val searchBarState = rememberSearchbarState()
val navBackStackEntry by navController.currentBackStackEntryAsState()

val currentNavigationItemState = remember(homeDestinations) {
val currentNavigationItemState = remember {
derivedStateOf {
navBackStackEntry?.let { entry -> homeDestinations.find { it.entryMatches(entry) } } ?: Conversations
navBackStackEntry?.destination?.route?.let { HomeDestination.fromRoute(it) } ?: Conversations
}
}
val lazyListStates = homeDestinations.associateWith { rememberLazyListState() }
val conversationFilterState = rememberConversationFilterState()

return remember(homeDestinations) {
return remember {
HomeStateHolder(
coroutineScope = coroutineScope,
navController = navController,
drawerState = drawerState,
searchBarState = searchBarState,
navigator = navigator,
currentNavigationItemState = currentNavigationItemState,
lazyListStates = lazyListStates
conversationFilterState = conversationFilterState,
)
}
}
Loading

0 comments on commit d37cf33

Please sign in to comment.