Merge pull request #4964 from element-hq/feature/bma/roomListNavigationBar
Home navigation bar
This commit is contained in:
@@ -51,6 +51,8 @@ dependencies {
|
||||
implementation(projects.features.rageshake.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.haze)
|
||||
implementation(libs.haze.materials)
|
||||
implementation(projects.features.reportroom.api)
|
||||
api(projects.features.home.api)
|
||||
|
||||
|
||||
@@ -7,4 +7,6 @@
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
sealed interface HomeEvents
|
||||
sealed interface HomeEvents {
|
||||
data class SelectHomeNavigationBarItem(val item: HomeNavigationBarItem) : HomeEvents
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
|
||||
enum class HomeNavigationBarItem(
|
||||
@StringRes
|
||||
val labelRes: Int,
|
||||
) {
|
||||
Chats(
|
||||
labelRes = R.string.screen_roomlist_main_space_title
|
||||
),
|
||||
Spaces(
|
||||
// TODO Create a new entry in Localazy
|
||||
labelRes = R.string.screen_roomlist_main_space_title
|
||||
);
|
||||
|
||||
@Composable
|
||||
fun icon() = when (this) {
|
||||
Chats -> CompoundIcons.ChatSolid()
|
||||
// TODO Spaces -> CompoundIcons.Workspace()
|
||||
Spaces -> CompoundIcons.Code()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(index: Int): HomeNavigationBarItem {
|
||||
return entries.getOrElse(index) { Chats }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,20 @@ package io.element.android.features.home.impl
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
@@ -31,6 +37,7 @@ class HomePresenter @Inject constructor(
|
||||
private val roomListPresenter: Presenter<RoomListState>,
|
||||
private val logoutPresenter: Presenter<DirectLogoutState>,
|
||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : Presenter<HomeState> {
|
||||
@Composable
|
||||
override fun present(): HomeState {
|
||||
@@ -38,31 +45,42 @@ class HomePresenter @Inject constructor(
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() }
|
||||
val roomListState = roomListPresenter.present()
|
||||
|
||||
val isSpaceFeatureEnabled by remember {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space)
|
||||
}.collectAsState(initial = false)
|
||||
var currentHomeNavigationBarItemOrdinal by rememberSaveable { mutableIntStateOf(HomeNavigationBarItem.Chats.ordinal) }
|
||||
val currentHomeNavigationBarItem by remember {
|
||||
derivedStateOf {
|
||||
HomeNavigationBarItem.from(currentHomeNavigationBarItemOrdinal)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
// Force a refresh of the profile
|
||||
client.getUserProfile()
|
||||
}
|
||||
|
||||
// Avatar indicator
|
||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||
|
||||
val directLogoutState = logoutPresenter.present()
|
||||
|
||||
fun handleEvents(event: HomeEvents) {
|
||||
// TODO
|
||||
when (event) {
|
||||
is HomeEvents.SelectHomeNavigationBarItem -> {
|
||||
currentHomeNavigationBarItemOrdinal = event.item.ordinal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
|
||||
return HomeState(
|
||||
matrixUser = matrixUser.value,
|
||||
showAvatarIndicator = showAvatarIndicator,
|
||||
hasNetworkConnection = isOnline,
|
||||
currentHomeNavigationBarItem = currentHomeNavigationBarItem,
|
||||
roomListState = roomListState,
|
||||
snackbarMessage = snackbarMessage,
|
||||
canReportBug = canReportBug,
|
||||
directLogoutState = directLogoutState,
|
||||
isSpaceFeatureEnabled = isSpaceFeatureEnabled,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@ data class HomeState(
|
||||
val matrixUser: MatrixUser,
|
||||
val showAvatarIndicator: Boolean,
|
||||
val hasNetworkConnection: Boolean,
|
||||
val currentHomeNavigationBarItem: HomeNavigationBarItem,
|
||||
val roomListState: RoomListState,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val canReportBug: Boolean,
|
||||
val directLogoutState: DirectLogoutState,
|
||||
val isSpaceFeatureEnabled: Boolean,
|
||||
val eventSink: (HomeEvents) -> Unit,
|
||||
) {
|
||||
val displayActions = true
|
||||
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.roomlist.RoomListStateProvider
|
||||
import io.element.android.features.home.impl.roomlist.aRoomListState
|
||||
import io.element.android.features.home.impl.roomlist.aRoomsContentState
|
||||
import io.element.android.features.home.impl.roomlist.generateRoomListRoomSummaryList
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
@@ -24,6 +26,19 @@ open class HomeStateProvider : PreviewParameterProvider<HomeState> {
|
||||
aHomeState(),
|
||||
aHomeState(hasNetworkConnection = false),
|
||||
aHomeState(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)),
|
||||
aHomeState(
|
||||
isSpaceFeatureEnabled = true,
|
||||
roomListState = aRoomListState(
|
||||
// Add more rooms to see the blur effect under the NavigationBar
|
||||
contentState = aRoomsContentState(
|
||||
summaries = generateRoomListRoomSummaryList(),
|
||||
)
|
||||
),
|
||||
),
|
||||
aHomeState(
|
||||
isSpaceFeatureEnabled = true,
|
||||
currentHomeNavigationBarItem = HomeNavigationBarItem.Spaces,
|
||||
),
|
||||
) + RoomListStateProvider().values.map {
|
||||
aHomeState(roomListState = it)
|
||||
}
|
||||
@@ -34,8 +49,10 @@ internal fun aHomeState(
|
||||
showAvatarIndicator: Boolean = false,
|
||||
hasNetworkConnection: Boolean = true,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
currentHomeNavigationBarItem: HomeNavigationBarItem = HomeNavigationBarItem.Chats,
|
||||
roomListState: RoomListState = aRoomListState(),
|
||||
canReportBug: Boolean = true,
|
||||
isSpaceFeatureEnabled: Boolean = false,
|
||||
directLogoutState: DirectLogoutState = aDirectLogoutState(),
|
||||
eventSink: (HomeEvents) -> Unit = {}
|
||||
) = HomeState(
|
||||
@@ -45,6 +62,8 @@ internal fun aHomeState(
|
||||
snackbarMessage = snackbarMessage,
|
||||
canReportBug = canReportBug,
|
||||
directLogoutState = directLogoutState,
|
||||
currentHomeNavigationBarItem = currentHomeNavigationBarItem,
|
||||
roomListState = roomListState,
|
||||
isSpaceFeatureEnabled = isSpaceFeatureEnabled,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
@@ -5,10 +5,15 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalHazeMaterialsApi::class)
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -19,10 +24,19 @@ import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.home.impl.components.RoomListContentView
|
||||
@@ -41,7 +55,10 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.NavigationBar
|
||||
import io.element.android.libraries.designsystem.theme.components.NavigationBarItem
|
||||
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.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -138,10 +155,19 @@ private fun HomeScaffold(
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
val roomListState: RoomListState = state.roomListState
|
||||
|
||||
BackHandler(
|
||||
enabled = state.currentHomeNavigationBarItem != HomeNavigationBarItem.Chats,
|
||||
) {
|
||||
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats))
|
||||
}
|
||||
|
||||
val hazeState = rememberHazeState()
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
RoomListTopBar(
|
||||
title = stringResource(state.currentHomeNavigationBarItem.labelRes),
|
||||
matrixUser = state.matrixUser,
|
||||
showAvatarIndicator = state.showAvatarIndicator,
|
||||
areSearchResultsDisplayed = roomListState.searchState.isSearchActive,
|
||||
@@ -150,25 +176,83 @@ private fun HomeScaffold(
|
||||
onOpenSettings = onOpenSettings,
|
||||
scrollBehavior = scrollBehavior,
|
||||
displayMenuItems = state.displayActions,
|
||||
displayFilters = roomListState.displayFilters,
|
||||
displayFilters = roomListState.displayFilters && state.currentHomeNavigationBarItem == HomeNavigationBarItem.Chats,
|
||||
filtersState = roomListState.filtersState,
|
||||
canReportBug = state.canReportBug,
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
if (state.isSpaceFeatureEnabled) {
|
||||
NavigationBar(
|
||||
containerColor = Color.Transparent,
|
||||
modifier = Modifier
|
||||
.hazeEffect(
|
||||
state = hazeState,
|
||||
style = HazeMaterials.regular(),
|
||||
)
|
||||
) {
|
||||
HomeNavigationBarItem.entries.forEach { item ->
|
||||
NavigationBarItem(
|
||||
selected = state.currentHomeNavigationBarItem == item,
|
||||
onClick = {
|
||||
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item))
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = item.icon(),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(stringResource(item.labelRes))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
content = { padding ->
|
||||
RoomListContentView(
|
||||
contentState = roomListState.contentState,
|
||||
filtersState = roomListState.filtersState,
|
||||
hideInvitesAvatars = roomListState.hideInvitesAvatars,
|
||||
eventSink = roomListState.eventSink,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = ::onRoomClick,
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
)
|
||||
when (state.currentHomeNavigationBarItem) {
|
||||
HomeNavigationBarItem.Chats -> {
|
||||
RoomListContentView(
|
||||
contentState = roomListState.contentState,
|
||||
filtersState = roomListState.filtersState,
|
||||
hideInvitesAvatars = roomListState.hideInvitesAvatars,
|
||||
eventSink = roomListState.eventSink,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = ::onRoomClick,
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80,
|
||||
// and include provided bottom padding
|
||||
contentBottomPadding = 80.dp + padding.calculateBottomPadding(),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = padding.calculateTopPadding(),
|
||||
bottom = 0.dp,
|
||||
start = padding.calculateStartPadding(LocalLayoutDirection.current),
|
||||
end = padding.calculateEndPadding(LocalLayoutDirection.current),
|
||||
)
|
||||
.consumeWindowInsets(padding)
|
||||
.hazeSource(state = hazeState)
|
||||
)
|
||||
}
|
||||
HomeNavigationBarItem.Spaces -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = "Spaces are coming soon!",
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (state.displayActions) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
@@ -66,6 +67,7 @@ fun RoomListContentView(
|
||||
onConfirmRecoveryKeyClick: () -> Unit,
|
||||
onRoomClick: (RoomListRoomSummary) -> Unit,
|
||||
onCreateRoomClick: () -> Unit,
|
||||
contentBottomPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
@@ -93,6 +95,7 @@ fun RoomListContentView(
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = onRoomClick,
|
||||
contentBottomPadding = contentBottomPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -164,6 +167,7 @@ private fun RoomsView(
|
||||
onSetUpRecoveryClick: () -> Unit,
|
||||
onConfirmRecoveryKeyClick: () -> Unit,
|
||||
onRoomClick: (RoomListRoomSummary) -> Unit,
|
||||
contentBottomPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (state.summaries.isEmpty() && filtersState.hasAnyFilterSelected) {
|
||||
@@ -179,6 +183,7 @@ private fun RoomsView(
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = onRoomClick,
|
||||
contentBottomPadding = contentBottomPadding,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
@@ -192,6 +197,7 @@ private fun RoomsViewList(
|
||||
onSetUpRecoveryClick: () -> Unit,
|
||||
onConfirmRecoveryKeyClick: () -> Unit,
|
||||
onRoomClick: (RoomListRoomSummary) -> Unit,
|
||||
contentBottomPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
@@ -210,8 +216,7 @@ private fun RoomsViewList(
|
||||
LazyColumn(
|
||||
state = lazyListState,
|
||||
modifier = modifier,
|
||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
|
||||
contentPadding = PaddingValues(bottom = 80.dp)
|
||||
contentPadding = PaddingValues(bottom = contentBottomPadding)
|
||||
) {
|
||||
when (state.securityBannerState) {
|
||||
SecurityBannerState.SetUpRecovery -> {
|
||||
@@ -324,5 +329,6 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr
|
||||
onConfirmRecoveryKeyClick = {},
|
||||
onRoomClick = {},
|
||||
onCreateRoomClick = {},
|
||||
contentBottomPadding = 0.dp,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ private val avatarBloomSize = 430.dp
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomListTopBar(
|
||||
title: String,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
@@ -90,6 +91,7 @@ fun RoomListTopBar(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
DefaultRoomListTopBar(
|
||||
title = title,
|
||||
matrixUser = matrixUser,
|
||||
showAvatarIndicator = showAvatarIndicator,
|
||||
areSearchResultsDisplayed = areSearchResultsDisplayed,
|
||||
@@ -108,6 +110,7 @@ fun RoomListTopBar(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun DefaultRoomListTopBar(
|
||||
title: String,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
@@ -194,7 +197,7 @@ private fun DefaultRoomListTopBar(
|
||||
scrolledContainerColor = Color.Transparent,
|
||||
),
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.screen_roomlist_main_space_title))
|
||||
Text(text = title)
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon(
|
||||
@@ -315,6 +318,7 @@ private fun NavigationIcon(
|
||||
@Composable
|
||||
internal fun DefaultRoomListTopBarPreview() = ElementPreview {
|
||||
DefaultRoomListTopBar(
|
||||
title = stringResource(R.string.screen_roomlist_main_space_title),
|
||||
matrixUser = MatrixUser(UserId("@id:domain"), "Alice"),
|
||||
showAvatarIndicator = false,
|
||||
areSearchResultsDisplayed = false,
|
||||
@@ -334,6 +338,7 @@ internal fun DefaultRoomListTopBarPreview() = ElementPreview {
|
||||
@Composable
|
||||
internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview {
|
||||
DefaultRoomListTopBar(
|
||||
title = stringResource(R.string.screen_roomlist_main_space_title),
|
||||
matrixUser = MatrixUser(UserId("@id:domain"), "Alice"),
|
||||
showAvatarIndicator = true,
|
||||
areSearchResultsDisplayed = false,
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.push.api.battery.aBatteryOptimizationState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||
override val values: Sequence<RoomListState>
|
||||
@@ -113,3 +114,18 @@ internal fun aRoomListRoomSummaryList(): ImmutableList<RoomListRoomSummary> {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun generateRoomListRoomSummaryList(
|
||||
numberOfRooms: Int = 10,
|
||||
): ImmutableList<RoomListRoomSummary> {
|
||||
return List(numberOfRooms) { index ->
|
||||
aRoomListRoomSummary(
|
||||
name = "Room#$index",
|
||||
numberOfUnreadMessages = 0,
|
||||
timestamp = "14:16",
|
||||
lastMessage = "A message",
|
||||
avatarData = AvatarData("!id$index", "${(65 + index % 26).toChar()}", size = AvatarSize.RoomListItem),
|
||||
id = "!roomId$index:domain",
|
||||
)
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ import io.element.android.features.home.impl.roomlist.aRoomListState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.indicator.test.FakeIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@@ -27,6 +30,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
@@ -58,6 +62,21 @@ class HomePresenterTest {
|
||||
assertThat(withUserState.matrixUser.displayName).isEqualTo(A_USER_NAME)
|
||||
assertThat(withUserState.matrixUser.avatarUrl).isEqualTo(AN_AVATAR_URL)
|
||||
assertThat(withUserState.showAvatarIndicator).isFalse()
|
||||
assertThat(withUserState.isSpaceFeatureEnabled).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - space feature enabled`() = runTest {
|
||||
val presenter = createHomePresenter(
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.Space.key to true),
|
||||
),
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isSpaceFeatureEnabled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,12 +114,27 @@ class HomePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - NavigationBar change`() = runTest {
|
||||
val presenter = createHomePresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats)
|
||||
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createHomePresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
syncService: SyncService = FakeSyncService(),
|
||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
||||
rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true },
|
||||
indicatorService: IndicatorService = FakeIndicatorService(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService()
|
||||
) = HomePresenter(
|
||||
client = client,
|
||||
syncService = syncService,
|
||||
@@ -109,5 +143,6 @@ class HomePresenterTest {
|
||||
logoutPresenter = { aDirectLogoutState() },
|
||||
roomListPresenter = { aRoomListState() },
|
||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ appyx = "1.7.1"
|
||||
sqldelight = "2.1.0"
|
||||
wysiwyg = "2.38.4"
|
||||
telephoto = "0.16.0"
|
||||
haze = "1.6.4"
|
||||
|
||||
# Dependency analysis
|
||||
dependencyAnalysis = "2.19.0"
|
||||
@@ -191,6 +192,8 @@ maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2"
|
||||
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2"
|
||||
opusencoder = "io.element.android:opusencoder:1.2.0"
|
||||
zxing_cpp = "io.github.zxing-cpp:android:2.3.0"
|
||||
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
|
||||
haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
|
||||
|
||||
# Analytics
|
||||
posthog = "com.posthog:posthog-android:3.19.0"
|
||||
|
||||
@@ -130,6 +130,13 @@ enum class FeatureFlags(
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
Space(
|
||||
key = "feature.space",
|
||||
title = "Spaces",
|
||||
description = "Spaces are under active development, only developers should enable this flog for now.",
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
MediaUploadOnSendQueue(
|
||||
key = "feature.media_upload_through_send_queue",
|
||||
title = "Media upload through send queue",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user