Integrate the new screen with the existing home screen, display it only when the search feature flag is enabled

This commit is contained in:
Jorge Martín
2026-08-20 13:56:29 +02:00
parent be2909bca2
commit 3ca5081f33
8 changed files with 66 additions and 23 deletions
@@ -335,11 +335,11 @@ class LoggedInFlowNode(
} }
NavTarget.Home -> { NavTarget.Home -> {
val callback = object : HomeEntryPoint.Callback { val callback = object : HomeEntryPoint.Callback {
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) { override fun navigateToRoom(roomId: RoomId, eventId: EventId?, joinedRoom: JoinedRoom?) {
lifecycleScope.launch { lifecycleScope.launch {
attachRoom( attachRoom(
roomIdOrAlias = roomId.toRoomIdOrAlias(), roomIdOrAlias = roomId.toRoomIdOrAlias(),
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom), initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom, eventId = eventId),
clearBackstack = false, clearBackstack = false,
) )
} }
@@ -12,6 +12,7 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@@ -23,7 +24,7 @@ interface HomeEntryPoint : FeatureEntryPoint {
): Node ): Node
interface Callback : Plugin { interface Callback : Plugin {
fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) fun navigateToRoom(roomId: RoomId, eventId: EventId?, joinedRoom: JoinedRoom?)
fun navigateToCreateRoom() fun navigateToCreateRoom()
fun navigateToCreateSpace() fun navigateToCreateSpace()
fun navigateToSettings() fun navigateToSettings()
@@ -56,6 +56,7 @@ import io.element.android.libraries.designsystem.utils.DelayedVisibility
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -183,6 +184,7 @@ class HomeFlowNode(
fun navigateToRoom( fun navigateToRoom(
roomId: RoomId, roomId: RoomId,
eventId: EventId?,
) { ) {
if (!loadingJoinedRoomJob.value.isUninitialized()) { if (!loadingJoinedRoomJob.value.isUninitialized()) {
Timber.w("Already loading a room, ignoring navigateToRoom for $roomId") Timber.w("Already loading a room, ignoring navigateToRoom for $roomId")
@@ -195,7 +197,7 @@ class HomeFlowNode(
}.fold( }.fold(
onSuccess = { joinedRoom -> onSuccess = { joinedRoom ->
if (isActive) { if (isActive) {
callback.navigateToRoom(roomId, joinedRoom) callback.navigateToRoom(roomId = roomId, eventId = eventId, joinedRoom = joinedRoom)
loadingJoinedRoomJob.value = AsyncData.Success(coroutineContext.job) loadingJoinedRoomJob.value = AsyncData.Success(coroutineContext.job)
// Wait a bit before resetting the state to avoid allowing to open several rooms // Wait a bit before resetting the state to avoid allowing to open several rooms
delay(200.milliseconds) delay(200.milliseconds)
@@ -205,7 +207,7 @@ class HomeFlowNode(
onFailure = { onFailure = {
// If the operation wasn't cancelled, navigate without the room, using the room id // If the operation wasn't cancelled, navigate without the room, using the room id
if (it !is CancellationException) { if (it !is CancellationException) {
callback.navigateToRoom(roomId, null) callback.navigateToRoom(roomId = roomId, eventId = null, joinedRoom = null)
} }
loadingJoinedRoomJob.value = AsyncData.Failure(error = it, prevData = coroutineContext.job) loadingJoinedRoomJob.value = AsyncData.Failure(error = it, prevData = coroutineContext.job)
// Wait a bit before resetting the state to avoid allowing to open several rooms // Wait a bit before resetting the state to avoid allowing to open several rooms
@@ -233,7 +235,7 @@ class HomeFlowNode(
acceptDeclineInviteView = { acceptDeclineInviteView = {
acceptDeclineInviteView.Render( acceptDeclineInviteView.Render(
state = state.roomListState.acceptDeclineInviteState, state = state.roomListState.acceptDeclineInviteState,
onAcceptInviteSuccess = ::navigateToRoom, onAcceptInviteSuccess = { navigateToRoom(roomId = it, eventId = null) },
onDeclineInviteSuccess = { }, onDeclineInviteSuccess = { },
modifier = Modifier modifier = Modifier
) )
@@ -55,6 +55,8 @@ import io.element.android.features.home.impl.roomlist.RoomListContextMenu
import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu
import io.element.android.features.home.impl.roomlist.RoomListEvent import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.home.impl.roomlist.RoomListState import io.element.android.features.home.impl.roomlist.RoomListState
import io.element.android.features.home.impl.search.GlobalSearchEvent
import io.element.android.features.home.impl.search.GlobalSearchView
import io.element.android.features.home.impl.search.RoomListSearchView import io.element.android.features.home.impl.search.RoomListSearchView
import io.element.android.features.home.impl.spacefilters.SpaceFiltersEvent import io.element.android.features.home.impl.spacefilters.SpaceFiltersEvent
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
@@ -73,6 +75,7 @@ import io.element.android.libraries.designsystem.utils.lazyColumnContentPadding
import io.element.android.libraries.designsystem.utils.scaffoldScrollableContentInsets import io.element.android.libraries.designsystem.utils.scaffoldScrollableContentInsets
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.EventId
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 import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -80,7 +83,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun HomeView( fun HomeView(
homeState: HomeState, homeState: HomeState,
onRoomClick: (RoomId) -> Unit, onRoomClick: (RoomId, EventId?) -> Unit,
onSettingsClick: () -> Unit, onSettingsClick: () -> Unit,
onSetUpRecoveryClick: () -> Unit, onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit,
@@ -122,22 +125,34 @@ fun HomeView(
state = homeState, state = homeState,
onSetUpRecoveryClick = onSetUpRecoveryClick, onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, onRoomClick = { roomId -> if (firstThrottler.canHandle()) onRoomClick(roomId, null) },
onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() }, onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() },
onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() }, onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() },
onCreateSpaceClick = { if (firstThrottler.canHandle()) onCreateSpaceClick() }, onCreateSpaceClick = { if (firstThrottler.canHandle()) onCreateSpaceClick() },
onMenuActionClick = onMenuActionClick, onMenuActionClick = onMenuActionClick,
) )
// This overlaid view will only be visible when state.displaySearchResults is true
RoomListSearchView( if (state.globalSearchState.isEnabled) {
state = state.searchState, GlobalSearchView(
eventSink = state.eventSink, state = state.globalSearchState,
hideInvitesAvatars = state.hideInvitesAvatars, onSelectSearchResult = { roomId, eventId -> if (firstThrottler.canHandle()) onRoomClick(roomId, eventId) },
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, modifier = Modifier
modifier = Modifier .fillMaxSize()
.fillMaxSize() .background(ElementTheme.colors.bgCanvasDefault),
.background(ElementTheme.colors.bgCanvasDefault) )
) } else {
// This overlaid view will only be visible when state.displaySearchResults is true
RoomListSearchView(
state = state.searchState,
eventSink = state.eventSink,
hideInvitesAvatars = state.hideInvitesAvatars,
onRoomClick = { roomId -> if (firstThrottler.canHandle()) onRoomClick(roomId, null) },
modifier = Modifier
.fillMaxSize()
.background(ElementTheme.colors.bgCanvasDefault)
)
}
acceptDeclineInviteView() acceptDeclineInviteView()
} }
} }
@@ -186,8 +201,18 @@ private fun HomeScaffold(
selectedNavigationItem = state.currentHomeNavigationBarItem, selectedNavigationItem = state.currentHomeNavigationBarItem,
currentUserAndNeighbors = state.currentUserAndNeighbors, currentUserAndNeighbors = state.currentUserAndNeighbors,
showAvatarIndicator = state.showAvatarIndicator, showAvatarIndicator = state.showAvatarIndicator,
areSearchResultsDisplayed = roomListState.searchState.isSearchActive, areSearchResultsDisplayed = if (roomListState.globalSearchState.isEnabled) {
onToggleSearch = { roomListState.eventSink(RoomListEvent.ToggleSearchResults) }, roomListState.globalSearchState.isSearchActive
} else {
roomListState.searchState.isSearchActive
},
onToggleSearch = {
if (roomListState.globalSearchState.isEnabled) {
roomListState.globalSearchState.eventSink(GlobalSearchEvent.ToggleSearchVisibility)
} else {
roomListState.eventSink(RoomListEvent.ToggleSearchResults)
}
},
onMenuActionClick = onMenuActionClick, onMenuActionClick = onMenuActionClick,
onOpenSettings = onOpenSettings, onOpenSettings = onOpenSettings,
onAccountSwitch = { onAccountSwitch = {
@@ -349,7 +374,7 @@ internal fun RoomListRoomSummary.contentType() = displayType.ordinal
internal fun HomeViewPreview(@PreviewParameter(HomeStateProvider::class) state: HomeState) = ElementPreview { internal fun HomeViewPreview(@PreviewParameter(HomeStateProvider::class) state: HomeState) = ElementPreview {
HomeView( HomeView(
homeState = state, homeState = state,
onRoomClick = {}, onRoomClick = { _, _ -> },
onSettingsClick = {}, onSettingsClick = {},
onSetUpRecoveryClick = {}, onSetUpRecoveryClick = {},
onConfirmRecoveryKeyClick = {}, onConfirmRecoveryKeyClick = {},
@@ -369,7 +394,7 @@ internal fun HomeViewPreview(@PreviewParameter(HomeStateProvider::class) state:
internal fun HomeViewA11yPreview() = ElementPreview { internal fun HomeViewA11yPreview() = ElementPreview {
HomeView( HomeView(
homeState = aHomeState(), homeState = aHomeState(),
onRoomClick = {}, onRoomClick = { _, _ -> },
onSettingsClick = {}, onSettingsClick = {},
onSetUpRecoveryClick = {}, onSetUpRecoveryClick = {},
onConfirmRecoveryKeyClick = {}, onConfirmRecoveryKeyClick = {},
@@ -30,6 +30,7 @@ import io.element.android.features.announcement.api.AnnouncementService
import io.element.android.features.home.impl.datasource.RoomListDataSource import io.element.android.features.home.impl.datasource.RoomListDataSource
import io.element.android.features.home.impl.filters.RoomListFiltersState import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.filters.into import io.element.android.features.home.impl.filters.into
import io.element.android.features.home.impl.search.GlobalSearchPresenter
import io.element.android.features.home.impl.search.RoomListSearchEvent import io.element.android.features.home.impl.search.RoomListSearchEvent
import io.element.android.features.home.impl.search.RoomListSearchState import io.element.android.features.home.impl.search.RoomListSearchState
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
@@ -85,10 +86,15 @@ class RoomListPresenter(
private val announcementService: AnnouncementService, private val announcementService: AnnouncementService,
private val coldStartWatcher: AnalyticsColdStartWatcher, private val coldStartWatcher: AnalyticsColdStartWatcher,
private val spaceFiltersPresenter: Presenter<SpaceFiltersState>, private val spaceFiltersPresenter: Presenter<SpaceFiltersState>,
private val globalSearchPresenterFactory: GlobalSearchPresenter.Factory,
private val featureFlagService: FeatureFlagService, private val featureFlagService: FeatureFlagService,
) : Presenter<RoomListState> { ) : Presenter<RoomListState> {
private val encryptionService = client.encryptionService private val encryptionService = client.encryptionService
private val globalSearchPresenter by lazy {
globalSearchPresenterFactory.create(coroutineScope = client.sessionCoroutineScope)
}
@Composable @Composable
override fun present(): RoomListState { override fun present(): RoomListState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@@ -97,6 +103,7 @@ class RoomListPresenter(
val searchState = searchPresenter.present() val searchState = searchPresenter.present()
val spaceFiltersState = spaceFiltersPresenter.present() val spaceFiltersState = spaceFiltersPresenter.present()
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
val globalSearchState = globalSearchPresenter.present()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
roomListDataSource.launchIn(this) roomListDataSource.launchIn(this)
@@ -177,6 +184,7 @@ class RoomListPresenter(
leaveRoomState = leaveRoomState, leaveRoomState = leaveRoomState,
filtersState = filtersState, filtersState = filtersState,
searchState = searchState, searchState = searchState,
globalSearchState = globalSearchState,
spaceFiltersState = spaceFiltersState, spaceFiltersState = spaceFiltersState,
contentState = contentState, contentState = contentState,
acceptDeclineInviteState = acceptDeclineInviteState, acceptDeclineInviteState = acceptDeclineInviteState,
@@ -11,6 +11,7 @@ package io.element.android.features.home.impl.roomlist
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.features.home.impl.filters.RoomListFiltersState import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.search.GlobalSearchState
import io.element.android.features.home.impl.search.RoomListSearchState import io.element.android.features.home.impl.search.RoomListSearchState
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
@@ -27,6 +28,7 @@ data class RoomListState(
val leaveRoomState: LeaveRoomState, val leaveRoomState: LeaveRoomState,
val filtersState: RoomListFiltersState, val filtersState: RoomListFiltersState,
val searchState: RoomListSearchState, val searchState: RoomListSearchState,
val globalSearchState: GlobalSearchState,
val spaceFiltersState: SpaceFiltersState, val spaceFiltersState: SpaceFiltersState,
val contentState: RoomListContentState, val contentState: RoomListContentState,
val acceptDeclineInviteState: AcceptDeclineInviteState, val acceptDeclineInviteState: AcceptDeclineInviteState,
@@ -16,7 +16,9 @@ import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.model.RoomSummaryDisplayType import io.element.android.features.home.impl.model.RoomSummaryDisplayType
import io.element.android.features.home.impl.model.aRoomListRoomSummary import io.element.android.features.home.impl.model.aRoomListRoomSummary
import io.element.android.features.home.impl.model.anInviteSender import io.element.android.features.home.impl.model.anInviteSender
import io.element.android.features.home.impl.search.GlobalSearchState
import io.element.android.features.home.impl.search.RoomListSearchState import io.element.android.features.home.impl.search.RoomListSearchState
import io.element.android.features.home.impl.search.aGlobalSearchState
import io.element.android.features.home.impl.search.aRoomListSearchState import io.element.android.features.home.impl.search.aRoomListSearchState
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
import io.element.android.features.home.impl.spacefilters.anUnselectedSpaceFiltersState import io.element.android.features.home.impl.spacefilters.anUnselectedSpaceFiltersState
@@ -53,6 +55,7 @@ internal fun aRoomListState(
declineInviteMenu: RoomListState.DeclineInviteMenu = RoomListState.DeclineInviteMenu.Hidden, declineInviteMenu: RoomListState.DeclineInviteMenu = RoomListState.DeclineInviteMenu.Hidden,
leaveRoomState: LeaveRoomState = aLeaveRoomState(), leaveRoomState: LeaveRoomState = aLeaveRoomState(),
searchState: RoomListSearchState = aRoomListSearchState(), searchState: RoomListSearchState = aRoomListSearchState(),
globalSearchState: GlobalSearchState = aGlobalSearchState(),
filtersState: RoomListFiltersState = aRoomListFiltersState(), filtersState: RoomListFiltersState = aRoomListFiltersState(),
spaceFiltersState: SpaceFiltersState = anUnselectedSpaceFiltersState(), spaceFiltersState: SpaceFiltersState = anUnselectedSpaceFiltersState(),
contentState: RoomListContentState = aRoomsContentState(), contentState: RoomListContentState = aRoomsContentState(),
@@ -66,6 +69,7 @@ internal fun aRoomListState(
leaveRoomState = leaveRoomState, leaveRoomState = leaveRoomState,
filtersState = filtersState, filtersState = filtersState,
searchState = searchState, searchState = searchState,
globalSearchState = globalSearchState,
spaceFiltersState = spaceFiltersState, spaceFiltersState = spaceFiltersState,
contentState = contentState, contentState = contentState,
acceptDeclineInviteState = acceptDeclineInviteState, acceptDeclineInviteState = acceptDeclineInviteState,
@@ -12,6 +12,7 @@ import com.bumble.appyx.core.modality.BuildContext
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.home.api.HomeEntryPoint import io.element.android.features.home.api.HomeEntryPoint
import io.element.android.features.invite.test.declineandblock.FakeDeclineInviteAndBlockEntryPoint import io.element.android.features.invite.test.declineandblock.FakeDeclineInviteAndBlockEntryPoint
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
@@ -44,7 +45,7 @@ class DefaultHomeEntryPointTest : RobolectricTest() {
) )
} }
val callback = object : HomeEntryPoint.Callback { val callback = object : HomeEntryPoint.Callback {
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) = lambdaError() override fun navigateToRoom(roomId: RoomId, eventId: EventId?, joinedRoom: JoinedRoom?) = lambdaError()
override fun navigateToCreateRoom() = lambdaError() override fun navigateToCreateRoom() = lambdaError()
override fun navigateToCreateSpace() = lambdaError() override fun navigateToCreateSpace() = lambdaError()
override fun navigateToSettings() = lambdaError() override fun navigateToSettings() = lambdaError()