Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: bottom sheet state [WPB-9045] #3065

Merged
merged 6 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -22,11 +22,8 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.ramcosta.composedestinations.spec.Direction
import com.wire.android.R
import com.wire.android.ui.common.WireBottomNavigationItemData
import com.wire.android.ui.destinations.AllConversationScreenDestination
import com.wire.android.ui.destinations.ArchiveScreenDestination
import com.wire.android.ui.destinations.CallsScreenDestination
import com.wire.android.ui.destinations.MentionScreenDestination
import com.wire.android.ui.destinations.SettingsScreenDestination
import com.wire.android.ui.destinations.VaultScreenDestination
import com.wire.android.ui.destinations.WhatsNewScreenDestination
Expand All @@ -49,22 +46,6 @@ sealed class HomeDestination(
direction = AllConversationScreenDestination
)

data object Calls : HomeDestination(
title = R.string.conversations_calls_tab_title,
icon = R.drawable.ic_call,
isSearchable = true,
withNewConversationFab = true,
direction = CallsScreenDestination
)

data object Mentions : HomeDestination(
title = R.string.conversations_mentions_tab_title,
icon = R.drawable.ic_mention,
isSearchable = true,
withNewConversationFab = true,
direction = MentionScreenDestination
)

data object Settings : HomeDestination(
title = R.string.settings_screen_title,
icon = R.drawable.ic_settings,
Expand Down Expand Up @@ -96,22 +77,13 @@ sealed class HomeDestination(
direction = WhatsNewScreenDestination
)

val withBottomTabs: Boolean get() = bottomTabItems.contains(this)

fun toBottomNavigationItemData(notificationAmount: Long): WireBottomNavigationItemData =
WireBottomNavigationItemData(icon, tabName, notificationAmount, direction.route)

val itemName: String get() = ITEM_NAME_PREFIX + this

companion object {
// TODO uncomment when CallsScreen and MentionScreen will be implemented
// val bottomTabItems = listOf(Conversations, Calls, Mentions)
val bottomTabItems = listOf<HomeDestination>()

private const val ITEM_NAME_PREFIX = "HomeNavigationItem."
fun fromRoute(fullRoute: String): HomeDestination? =
values().find { it.direction.route.getBaseRoute() == fullRoute.getBaseRoute() }
fun values(): Array<HomeDestination> =
arrayOf(Conversations, Calls, Mentions, Settings, Vault, Archive, Support, WhatsNew)
arrayOf(Conversations, Settings, Vault, Archive, Support, WhatsNew)
}
}
156 changes: 24 additions & 132 deletions app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
Expand All @@ -36,11 +34,11 @@ import androidx.compose.material3.DrawerDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.SnackbarHostState
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
import androidx.compose.ui.graphics.RectangleShape
Expand Down Expand Up @@ -72,20 +70,15 @@ import com.wire.android.navigation.handleNavigation
import com.wire.android.ui.NavGraphs
import com.wire.android.ui.common.CollapsingTopBarScaffold
import com.wire.android.ui.common.FloatingActionButton
import com.wire.android.ui.common.WireBottomNavigationBar
import com.wire.android.ui.common.WireBottomNavigationItemData
import com.wire.android.ui.common.bottomsheet.WireModalSheetLayout
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.topappbar.search.SearchTopBar
import com.wire.android.ui.destinations.ConversationScreenDestination
import com.wire.android.ui.destinations.NewConversationSearchPeopleScreenDestination
import com.wire.android.ui.destinations.OtherUserProfileScreenDestination
import com.wire.android.ui.destinations.SelfUserProfileScreenDestination
import com.wire.android.ui.home.conversations.details.GroupConversationActionType
import com.wire.android.ui.home.conversations.details.GroupConversationDetailsNavBackArgs
import com.wire.android.ui.home.conversationslist.ConversationListState
import com.wire.android.ui.home.conversationslist.ConversationListViewModel
import com.wire.android.ui.home.conversationslist.model.ConversationsSource
import com.wire.android.ui.home.drawer.HomeDrawer
import com.wire.android.ui.home.drawer.HomeDrawerState
import com.wire.android.ui.home.drawer.HomeDrawerViewModel
Expand All @@ -97,19 +90,21 @@ import kotlinx.coroutines.launch
@Composable
fun HomeScreen(
navigator: Navigator,
homeViewModel: HomeViewModel = hiltViewModel(),
appSyncViewModel: AppSyncViewModel = hiltViewModel(),
homeDrawerViewModel: HomeDrawerViewModel = hiltViewModel(),
conversationListViewModel: ConversationListViewModel = hiltViewModel(), // TODO: move required elements from this one to HomeViewModel?,
groupDetailsScreenResultRecipient: ResultRecipient<ConversationScreenDestination, GroupConversationDetailsNavBackArgs>,
otherUserProfileScreenResultRecipient: ResultRecipient<OtherUserProfileScreenDestination, String>,
homeViewModel: HomeViewModel = hiltViewModel(),
appSyncViewModel: AppSyncViewModel = hiltViewModel(),
homeDrawerViewModel: HomeDrawerViewModel = hiltViewModel()
) {
homeViewModel.checkRequirements() { it.navigate(navigator::navigate) }
homeViewModel.checkRequirements { it.navigate(navigator::navigate) }
val homeScreenState = rememberHomeScreenState(navigator)
val showNotificationsFlow = rememberRequestPushNotificationsPermissionFlow(
onPermissionDenied = { /** TODO: Show a dialog rationale explaining why the permission is needed **/ })

val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current
val coroutineScope = rememberCoroutineScope()

DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
Expand All @@ -128,20 +123,6 @@ fun HomeScreen(
showNotificationsFlow.launch()
}

LaunchedEffect(homeScreenState.currentNavigationItem) {
when (homeScreenState.currentNavigationItem) {
HomeDestination.Archive -> conversationListViewModel.updateConversationsSource(ConversationsSource.ARCHIVE)
HomeDestination.Conversations -> conversationListViewModel.updateConversationsSource(ConversationsSource.MAIN)
else -> {}
}
}

handleSnackBarMessage(
snackbarHostState = homeScreenState.snackBarHostState,
conversationListSnackBarState = homeScreenState.snackbarState,
onMessageShown = homeScreenState::clearSnackbarMessage
)

val homeState = homeViewModel.homeState
if (homeViewModel.homeState.shouldDisplayWelcomeMessage) {
WelcomeNewUserDialog(
Expand All @@ -153,7 +134,6 @@ fun HomeScreen(
homeState = homeState,
homeDrawerState = homeDrawerViewModel.drawerState,
homeStateHolder = homeScreenState,
conversationListState = conversationListViewModel.conversationListState,
onNewConversationClick = { navigator.navigate(NavigationCommand(NewConversationSearchPeopleScreenDestination)) },
onSelfUserClick = remember(navigator) { { navigator.navigate(NavigationCommand(SelfUserProfileScreenDestination)) } }
)
Expand All @@ -176,12 +156,19 @@ fun HomeScreen(
is NavResult.Value -> {
when (result.value.groupConversationActionType) {
GroupConversationActionType.LEAVE_GROUP -> {
homeScreenState.setSnackBarState(HomeSnackbarState.LeftConversationSuccess)
coroutineScope.launch {
snackbarHostState.showSnackbar((HomeSnackBarMessage.LeftConversationSuccess.uiText.asString(context.resources)))
}
}

GroupConversationActionType.DELETE_GROUP -> {
val groupDeletedSnackBar = HomeSnackbarState.DeletedConversationGroupSuccess(result.value.conversationName)
homeScreenState.setSnackBarState(groupDeletedSnackBar)
coroutineScope.launch {
snackbarHostState.showSnackbar(
HomeSnackBarMessage.DeletedConversationGroupSuccess(result.value.conversationName).uiText.asString(
context.resources
)
)
}
}
}
}
Expand All @@ -195,7 +182,11 @@ fun HomeScreen(
}

is NavResult.Value -> {
homeScreenState.setSnackBarState(HomeSnackbarState.SuccessConnectionIgnoreRequest(result.value))
coroutineScope.launch {
snackbarHostState.showSnackbar(
HomeSnackBarMessage.SuccessConnectionIgnoreRequest(result.value).uiText.asString(context.resources)
)
}
}
}
}
Expand All @@ -207,7 +198,6 @@ fun HomeContent(
homeState: HomeState,
homeDrawerState: HomeDrawerState,
homeStateHolder: HomeStateHolder,
conversationListState: ConversationListState,
onNewConversationClick: () -> Unit,
onSelfUserClick: () -> Unit,
) {
Expand Down Expand Up @@ -330,107 +320,9 @@ fun HomeContent(
onClick = onNewConversationClick
)
}
},
bottomBar = {
AnimatedVisibility(
visible = currentNavigationItem.withBottomTabs,
enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }),
) {
WireBottomNavigationBar(
items = HomeDestination.bottomTabItems.toBottomNavigationItemData(
conversationListState = conversationListState
),
selectedItemRoute = homeStateHolder.currentNavigationItem.direction.route,
onItemSelected = { HomeDestination.fromRoute(it.route)?.let { openHomeDestination(it) } }
)
}
}
)

WireModalSheetLayout(
sheetState = bottomSheetState,
coroutineScope = coroutineScope,
// we want to render "nothing" instead of doing a if/else check
// on homeBottomSheetContent and wrap homeContent() into WireModalSheetLayout
// or render it without WireModalSheetLayout to avoid
// recomposing the homeContent() when homeBottomSheetContent
// changes from null to "something"
sheetContent = homeBottomSheetContent ?: { }
)
}
)
}
}

@Suppress("ComplexMethod")
@Composable
private fun handleSnackBarMessage(
snackbarHostState: SnackbarHostState,
conversationListSnackBarState: HomeSnackbarState,
onMessageShown: () -> Unit
) {
conversationListSnackBarState.let { messageType ->
val message = when (messageType) {
is HomeSnackbarState.SuccessConnectionIgnoreRequest ->
stringResource(id = R.string.connection_request_ignored, messageType.userName)

is HomeSnackbarState.BlockingUserOperationSuccess ->
stringResource(id = R.string.blocking_user_success, messageType.userName)

HomeSnackbarState.MutingOperationError -> stringResource(id = R.string.error_updating_muting_setting)
HomeSnackbarState.BlockingUserOperationError -> stringResource(id = R.string.error_blocking_user)
HomeSnackbarState.UnblockingUserOperationError -> stringResource(id = R.string.error_unblocking_user)
HomeSnackbarState.None -> ""
is HomeSnackbarState.DeletedConversationGroupSuccess -> stringResource(
id = R.string.conversation_group_removed_success,
messageType.groupName
)

HomeSnackbarState.LeftConversationSuccess -> stringResource(id = R.string.left_conversation_group_success)
HomeSnackbarState.LeaveConversationError -> stringResource(id = R.string.leave_group_conversation_error)
HomeSnackbarState.DeleteConversationGroupError -> stringResource(id = R.string.delete_group_conversation_error)
is HomeSnackbarState.ClearConversationContentFailure -> stringResource(
if (messageType.isGroup) R.string.group_content_delete_failure
else R.string.conversation_content_delete_failure
)

is HomeSnackbarState.ClearConversationContentSuccess -> stringResource(
if (messageType.isGroup) R.string.group_content_deleted else R.string.conversation_content_deleted
)

is HomeSnackbarState.UpdateArchivingStatusSuccess -> {
stringResource(
id = if (messageType.isArchiving) R.string.success_archiving_conversation
else R.string.success_unarchiving_conversation
)
}

is HomeSnackbarState.UpdateArchivingStatusError -> {
stringResource(
id = if (messageType.isArchiving) R.string.error_archiving_conversation
else R.string.error_archiving_conversation
)
}
}

LaunchedEffect(messageType) {
if (messageType != HomeSnackbarState.None) {
snackbarHostState.showSnackbar(message)
onMessageShown()
}
}
}
}

@Composable
private fun List<HomeDestination>.toBottomNavigationItemData(
conversationListState: ConversationListState
): List<WireBottomNavigationItemData> = map {
when (it) {
HomeDestination.Conversations -> it.toBottomNavigationItemData(conversationListState.newActivityCount)
HomeDestination.Calls -> it.toBottomNavigationItemData(conversationListState.missedCallsCount)
HomeDestination.Mentions -> it.toBottomNavigationItemData(conversationListState.unreadMentionsCount)
else -> it.toBottomNavigationItemData(0L)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.home

import com.wire.android.R
import com.wire.android.model.SnackBarMessage
import com.wire.android.util.ui.UIText

sealed class HomeSnackBarMessage(override val uiText: UIText) : SnackBarMessage {

data class ClearConversationContentSuccess(val isGroup: Boolean) : HomeSnackBarMessage(
UIText.StringResource(
if (isGroup) {
R.string.group_content_deleted
} else {
R.string.conversation_content_deleted
}
)
)

data class ClearConversationContentFailure(val isGroup: Boolean) : HomeSnackBarMessage(
UIText.StringResource(
if (isGroup) {
R.string.group_content_delete_failure
} else {
R.string.conversation_content_delete_failure
}
)
)

class SuccessConnectionIgnoreRequest(val userName: String) :
HomeSnackBarMessage(UIText.StringResource(R.string.connection_request_ignored, userName))

data object MutingOperationError : HomeSnackBarMessage(UIText.StringResource(R.string.error_updating_muting_setting))
data object BlockingUserOperationError : HomeSnackBarMessage(UIText.StringResource(R.string.error_blocking_user))
data class BlockingUserOperationSuccess(val userName: String) :
HomeSnackBarMessage(UIText.StringResource(R.string.blocking_user_success, userName))

data object UnblockingUserOperationError : HomeSnackBarMessage(UIText.StringResource(R.string.error_unblocking_user))
data class DeletedConversationGroupSuccess(val groupName: String) : HomeSnackBarMessage(
UIText.StringResource(
R.string.conversation_group_removed_success,
groupName
)
)

data object DeleteConversationGroupError : HomeSnackBarMessage(UIText.StringResource(R.string.delete_group_conversation_error))
data object LeftConversationSuccess : HomeSnackBarMessage(UIText.StringResource(R.string.left_conversation_group_success))
data object LeaveConversationError : HomeSnackBarMessage(UIText.StringResource(R.string.leave_group_conversation_error))
data class UpdateArchivingStatusSuccess(val isArchiving: Boolean) : HomeSnackBarMessage(
UIText.StringResource(
if (isArchiving) {
R.string.success_archiving_conversation
} else {
R.string.success_unarchiving_conversation
}
)
)

data class UpdateArchivingStatusError(val isArchiving: Boolean) : HomeSnackBarMessage(
UIText.StringResource(
if (isArchiving) {
R.string.error_archiving_conversation
} else {
R.string.error_archiving_conversation
}
)
)
}
Loading
Loading