Browse Source

RoomListFilters : integrate with TopBar (and bloom)

pull/2536/head
ganfra 6 months ago
parent
commit
a2c4d7debd
  1. 54
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt
  2. 226
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt

54
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt

@ -17,63 +17,36 @@
package io.element.android.features.roomlist.impl package io.element.android.features.roomlist.impl
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.leaveroom.api.LeaveRoomView import io.element.android.features.leaveroom.api.LeaveRoomView
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer
import io.element.android.features.roomlist.impl.components.ConfirmRecoveryKeyBanner
import io.element.android.features.roomlist.impl.components.RequestVerificationHeader
import io.element.android.features.roomlist.impl.components.RoomListContentView import io.element.android.features.roomlist.impl.components.RoomListContentView
import io.element.android.features.roomlist.impl.components.RoomListMenuAction import io.element.android.features.roomlist.impl.components.RoomListMenuAction
import io.element.android.features.roomlist.impl.components.RoomListTopBar import io.element.android.features.roomlist.impl.components.RoomListTopBar
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
import io.element.android.features.roomlist.impl.filters.RoomListFiltersView
import io.element.android.features.roomlist.impl.migration.MigrationScreenView
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.search.RoomListSearchView import io.element.android.features.roomlist.impl.search.RoomListSearchView
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun RoomListView( fun RoomListView(
@ -161,21 +134,18 @@ private fun RoomListScaffold(
Scaffold( Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
Column { RoomListTopBar(
RoomListTopBar( matrixUser = state.matrixUser,
matrixUser = state.matrixUser, showAvatarIndicator = state.showAvatarIndicator,
showAvatarIndicator = state.showAvatarIndicator, areSearchResultsDisplayed = state.searchState.isSearchActive,
areSearchResultsDisplayed = state.searchState.isSearchActive, onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) },
onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) }, onMenuActionClicked = onMenuActionClicked,
onMenuActionClicked = onMenuActionClicked, onOpenSettings = onOpenSettings,
onOpenSettings = onOpenSettings, scrollBehavior = scrollBehavior,
scrollBehavior = scrollBehavior, displayMenuItems = state.displayActions,
displayMenuItems = !state.displayActions, displayFilters = state.displayFilters,
) filtersState = state.filtersState,
if (state.displayFilters) { )
RoomListFiltersView(state = state.filtersState)
}
}
}, },
content = { padding -> content = { padding ->
RoomListContentView( RoomListContentView(

226
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt

@ -17,6 +17,7 @@
package io.element.android.features.roomlist.impl.components package io.element.android.features.roomlist.impl.components
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -40,7 +41,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
@ -52,8 +52,12 @@ import io.element.android.appconfig.RoomListConfig
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.roomlist.impl.R import io.element.android.features.roomlist.impl.R
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
import io.element.android.features.roomlist.impl.filters.RoomListFiltersView
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatarBloom import io.element.android.libraries.designsystem.components.avatarBloom
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
@ -91,6 +95,8 @@ fun RoomListTopBar(
onOpenSettings: () -> Unit, onOpenSettings: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
displayMenuItems: Boolean, displayMenuItems: Boolean,
displayFilters: Boolean,
filtersState: RoomListFiltersState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
DefaultRoomListTopBar( DefaultRoomListTopBar(
@ -102,6 +108,8 @@ fun RoomListTopBar(
onMenuActionClicked = onMenuActionClicked, onMenuActionClicked = onMenuActionClicked,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
displayMenuItems = displayMenuItems, displayMenuItems = displayMenuItems,
displayFilters = displayFilters,
filtersState = filtersState,
modifier = modifier, modifier = modifier,
) )
} }
@ -117,6 +125,8 @@ private fun DefaultRoomListTopBar(
onSearchClicked: () -> Unit, onSearchClicked: () -> Unit,
onMenuActionClicked: (RoomListMenuAction) -> Unit, onMenuActionClicked: (RoomListMenuAction) -> Unit,
displayMenuItems: Boolean, displayMenuItems: Boolean,
displayFilters: Boolean,
filtersState: RoomListFiltersState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
// We need this to manually clip the top app bar in preview mode // We need this to manually clip the top app bar in preview mode
@ -153,12 +163,11 @@ private fun DefaultRoomListTopBar(
titleLarge = collapsedTitleTextStyle titleLarge = collapsedTitleTextStyle
), ),
) { ) {
MediumTopAppBar( Column(
modifier = Modifier modifier = Modifier
.onSizeChanged { .onSizeChanged {
appBarHeight = it.height appBarHeight = it.height
} }
.nestedScroll(scrollBehavior.nestedScrollConnection)
.avatarBloom( .avatarBloom(
avatarData = avatarData, avatarData = avatarData,
background = if (ElementTheme.isLightTheme) { background = if (ElementTheme.isLightTheme) {
@ -178,113 +187,104 @@ private fun DefaultRoomListTopBar(
DpSize.Unspecified DpSize.Unspecified
}, },
bottomSoftEdgeColor = ElementTheme.materialColors.background, bottomSoftEdgeColor = ElementTheme.materialColors.background,
bottomSoftEdgeAlpha = 1f - collapsedFraction, bottomSoftEdgeAlpha = if (displayFilters) {
1f
} else {
1f - collapsedFraction
},
alpha = if (areSearchResultsDisplayed) 0f else 1f, alpha = if (areSearchResultsDisplayed) 0f else 1f,
) )
.statusBarsPadding(), .statusBarsPadding(),
colors = TopAppBarDefaults.mediumTopAppBarColors( ) {
containerColor = Color.Transparent, MediumTopAppBar(
scrolledContainerColor = Color.Transparent, colors = TopAppBarDefaults.mediumTopAppBarColors(
), containerColor = Color.Transparent,
title = { scrolledContainerColor = Color.Transparent,
Text(text = stringResource(id = R.string.screen_roomlist_main_space_title)) ),
}, title = {
navigationIcon = { Text(text = stringResource(id = R.string.screen_roomlist_main_space_title))
IconButton( },
modifier = Modifier.testTag(TestTags.homeScreenSettings), navigationIcon = {
onClick = onOpenSettings NavigationIcon(
) { avatarData = avatarData,
if (avatarData != null) { showAvatarIndicator = showAvatarIndicator,
Avatar( onClick = onOpenSettings,
avatarData = avatarData!!, )
contentDescription = stringResource(CommonStrings.common_settings), },
) actions = {
} else { if (displayMenuItems) {
// Placeholder avatar until the avatarData is available
Surface(
modifier = Modifier.size(AvatarSize.CurrentUserTopBar.dp),
shape = CircleShape,
color = ElementTheme.colors.iconSecondary,
content = {}
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier
.padding(4.5.dp)
.align(Alignment.TopEnd)
)
}
}
},
actions = {
if (displayMenuItems) {
IconButton(
onClick = onSearchClicked,
) {
Icon(
imageVector = CompoundIcons.Search(),
contentDescription = stringResource(CommonStrings.action_search),
)
}
if (RoomListConfig.HAS_DROP_DOWN_MENU) {
var showMenu by remember { mutableStateOf(false) }
IconButton( IconButton(
onClick = { showMenu = !showMenu } onClick = onSearchClicked,
) { ) {
Icon( Icon(
imageVector = CompoundIcons.OverflowVertical(), imageVector = CompoundIcons.Search(),
contentDescription = null, contentDescription = stringResource(CommonStrings.action_search),
) )
} }
DropdownMenu( if (RoomListConfig.HAS_DROP_DOWN_MENU) {
expanded = showMenu, var showMenu by remember { mutableStateOf(false) }
onDismissRequest = { showMenu = false } IconButton(
) { onClick = { showMenu = !showMenu }
if (RoomListConfig.SHOW_INVITE_MENU_ITEM) { ) {
DropdownMenuItem( Icon(
onClick = { imageVector = CompoundIcons.OverflowVertical(),
showMenu = false contentDescription = null,
onMenuActionClicked(RoomListMenuAction.InviteFriends)
},
text = { Text(stringResource(id = CommonStrings.action_invite)) },
leadingIcon = {
Icon(
imageVector = CompoundIcons.ShareAndroid(),
tint = ElementTheme.materialColors.secondary,
contentDescription = null,
)
}
) )
} }
if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM) { DropdownMenu(
DropdownMenuItem( expanded = showMenu,
onClick = { onDismissRequest = { showMenu = false }
showMenu = false ) {
onMenuActionClicked(RoomListMenuAction.ReportBug) if (RoomListConfig.SHOW_INVITE_MENU_ITEM) {
}, DropdownMenuItem(
text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) }, onClick = {
leadingIcon = { showMenu = false
Icon( onMenuActionClicked(RoomListMenuAction.InviteFriends)
imageVector = CompoundIcons.ChatProblem(), },
tint = ElementTheme.materialColors.secondary, text = { Text(stringResource(id = CommonStrings.action_invite)) },
contentDescription = null, leadingIcon = {
) Icon(
} imageVector = CompoundIcons.ShareAndroid(),
) tint = ElementTheme.materialColors.secondary,
contentDescription = null,
)
}
)
}
if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM) {
DropdownMenuItem(
onClick = {
showMenu = false
onMenuActionClicked(RoomListMenuAction.ReportBug)
},
text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) },
leadingIcon = {
Icon(
imageVector = CompoundIcons.ChatProblem(),
tint = ElementTheme.materialColors.secondary,
contentDescription = null,
)
}
)
}
} }
} }
} }
} },
}, scrollBehavior = scrollBehavior,
scrollBehavior = scrollBehavior, windowInsets = WindowInsets(0.dp),
windowInsets = WindowInsets(0.dp), )
) if (displayFilters) {
RoomListFiltersView(
state = filtersState,
modifier = Modifier.padding(bottom = 16.dp)
)
}
}
} }
HorizontalDivider( HorizontalDivider(
modifier = modifier = Modifier
Modifier
.fillMaxWidth() .fillMaxWidth()
.alpha(collapsedFraction) .alpha(collapsedFraction)
.align(Alignment.BottomCenter), .align(Alignment.BottomCenter),
@ -293,6 +293,40 @@ private fun DefaultRoomListTopBar(
} }
} }
@Composable
private fun NavigationIcon(
avatarData: AvatarData?,
showAvatarIndicator: Boolean,
onClick: () -> Unit,
) {
IconButton(
modifier = Modifier.testTag(TestTags.homeScreenSettings),
onClick = onClick,
) {
Box {
if (avatarData != null) {
Avatar(
avatarData = avatarData,
contentDescription = stringResource(CommonStrings.common_settings),
)
} else {
// Placeholder avatar until the avatarData is available
Surface(
modifier = Modifier.size(AvatarSize.CurrentUserTopBar.dp),
shape = CircleShape,
color = ElementTheme.colors.iconSecondary,
content = {}
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier.align(Alignment.TopEnd)
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
@ -305,6 +339,8 @@ internal fun DefaultRoomListTopBarPreview() = ElementPreview {
onOpenSettings = {}, onOpenSettings = {},
onSearchClicked = {}, onSearchClicked = {},
displayMenuItems = true, displayMenuItems = true,
displayFilters = true,
filtersState = aRoomListFiltersState(),
onMenuActionClicked = {}, onMenuActionClicked = {},
) )
} }
@ -321,6 +357,8 @@ internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview {
onOpenSettings = {}, onOpenSettings = {},
onSearchClicked = {}, onSearchClicked = {},
displayMenuItems = true, displayMenuItems = true,
displayFilters = true,
filtersState = aRoomListFiltersState(),
onMenuActionClicked = {}, onMenuActionClicked = {},
) )
} }

Loading…
Cancel
Save