diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index cd071131d9..dab9b7828c 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -335,11 +335,11 @@ class LoggedInFlowNode( } NavTarget.Home -> { val callback = object : HomeEntryPoint.Callback { - override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) { + override fun navigateToRoom(roomId: RoomId, eventId: EventId?, joinedRoom: JoinedRoom?) { lifecycleScope.launch { attachRoom( roomIdOrAlias = roomId.toRoomIdOrAlias(), - initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom), + initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom, eventId = eventId), clearBackstack = false, ) } diff --git a/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt b/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt index 1a90245611..1c6a0a906e 100644 --- a/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt +++ b/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt @@ -12,6 +12,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin 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.room.JoinedRoom @@ -23,7 +24,7 @@ interface HomeEntryPoint : FeatureEntryPoint { ): Node interface Callback : Plugin { - fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) + fun navigateToRoom(roomId: RoomId, eventId: EventId?, joinedRoom: JoinedRoom?) fun navigateToCreateRoom() fun navigateToCreateSpace() fun navigateToSettings() diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt index b7b05cca87..20f27f67bd 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt @@ -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.annotations.SessionCoroutineScope 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.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope @@ -183,6 +184,7 @@ class HomeFlowNode( fun navigateToRoom( roomId: RoomId, + eventId: EventId?, ) { if (!loadingJoinedRoomJob.value.isUninitialized()) { Timber.w("Already loading a room, ignoring navigateToRoom for $roomId") @@ -195,7 +197,7 @@ class HomeFlowNode( }.fold( onSuccess = { joinedRoom -> if (isActive) { - callback.navigateToRoom(roomId, joinedRoom) + callback.navigateToRoom(roomId = roomId, eventId = eventId, joinedRoom = joinedRoom) loadingJoinedRoomJob.value = AsyncData.Success(coroutineContext.job) // Wait a bit before resetting the state to avoid allowing to open several rooms delay(200.milliseconds) @@ -205,7 +207,7 @@ class HomeFlowNode( onFailure = { // If the operation wasn't cancelled, navigate without the room, using the room id 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) // Wait a bit before resetting the state to avoid allowing to open several rooms @@ -233,7 +235,7 @@ class HomeFlowNode( acceptDeclineInviteView = { acceptDeclineInviteView.Render( state = state.roomListState.acceptDeclineInviteState, - onAcceptInviteSuccess = ::navigateToRoom, + onAcceptInviteSuccess = { navigateToRoom(roomId = it, eventId = null) }, onDeclineInviteSuccess = { }, modifier = Modifier ) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt index f5a2400f2f..97b43b17cc 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt @@ -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.RoomListEvent 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.spacefilters.SpaceFiltersEvent 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.snackbar.SnackbarHost 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.ui.strings.CommonStrings import kotlinx.coroutines.launch @@ -80,7 +83,7 @@ import kotlinx.coroutines.launch @Composable fun HomeView( homeState: HomeState, - onRoomClick: (RoomId) -> Unit, + onRoomClick: (RoomId, EventId?) -> Unit, onSettingsClick: () -> Unit, onSetUpRecoveryClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit, @@ -122,22 +125,34 @@ fun HomeView( state = homeState, onSetUpRecoveryClick = onSetUpRecoveryClick, onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, - onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, + onRoomClick = { roomId -> if (firstThrottler.canHandle()) onRoomClick(roomId, null) }, onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() }, onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() }, onCreateSpaceClick = { if (firstThrottler.canHandle()) onCreateSpaceClick() }, onMenuActionClick = onMenuActionClick, ) - // This overlaid view will only be visible when state.displaySearchResults is true - RoomListSearchView( - state = state.searchState, - eventSink = state.eventSink, - hideInvitesAvatars = state.hideInvitesAvatars, - onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, - modifier = Modifier - .fillMaxSize() - .background(ElementTheme.colors.bgCanvasDefault) - ) + + if (state.globalSearchState.isEnabled) { + GlobalSearchView( + state = state.globalSearchState, + onSelectSearchResult = { roomId, eventId -> if (firstThrottler.canHandle()) onRoomClick(roomId, eventId) }, + modifier = Modifier + .fillMaxSize() + .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() } } @@ -186,8 +201,18 @@ private fun HomeScaffold( selectedNavigationItem = state.currentHomeNavigationBarItem, currentUserAndNeighbors = state.currentUserAndNeighbors, showAvatarIndicator = state.showAvatarIndicator, - areSearchResultsDisplayed = roomListState.searchState.isSearchActive, - onToggleSearch = { roomListState.eventSink(RoomListEvent.ToggleSearchResults) }, + areSearchResultsDisplayed = if (roomListState.globalSearchState.isEnabled) { + roomListState.globalSearchState.isSearchActive + } else { + roomListState.searchState.isSearchActive + }, + onToggleSearch = { + if (roomListState.globalSearchState.isEnabled) { + roomListState.globalSearchState.eventSink(GlobalSearchEvent.ToggleSearchVisibility) + } else { + roomListState.eventSink(RoomListEvent.ToggleSearchResults) + } + }, onMenuActionClick = onMenuActionClick, onOpenSettings = onOpenSettings, onAccountSwitch = { @@ -349,7 +374,7 @@ internal fun RoomListRoomSummary.contentType() = displayType.ordinal internal fun HomeViewPreview(@PreviewParameter(HomeStateProvider::class) state: HomeState) = ElementPreview { HomeView( homeState = state, - onRoomClick = {}, + onRoomClick = { _, _ -> }, onSettingsClick = {}, onSetUpRecoveryClick = {}, onConfirmRecoveryKeyClick = {}, @@ -369,7 +394,7 @@ internal fun HomeViewPreview(@PreviewParameter(HomeStateProvider::class) state: internal fun HomeViewA11yPreview() = ElementPreview { HomeView( homeState = aHomeState(), - onRoomClick = {}, + onRoomClick = { _, _ -> }, onSettingsClick = {}, onSetUpRecoveryClick = {}, onConfirmRecoveryKeyClick = {}, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt index fe09bd7cd1..47b02cac08 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt @@ -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.filters.RoomListFiltersState 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.RoomListSearchState import io.element.android.features.home.impl.spacefilters.SpaceFiltersState @@ -85,10 +86,15 @@ class RoomListPresenter( private val announcementService: AnnouncementService, private val coldStartWatcher: AnalyticsColdStartWatcher, private val spaceFiltersPresenter: Presenter, + private val globalSearchPresenterFactory: GlobalSearchPresenter.Factory, private val featureFlagService: FeatureFlagService, ) : Presenter { private val encryptionService = client.encryptionService + private val globalSearchPresenter by lazy { + globalSearchPresenterFactory.create(coroutineScope = client.sessionCoroutineScope) + } + @Composable override fun present(): RoomListState { val coroutineScope = rememberCoroutineScope() @@ -97,6 +103,7 @@ class RoomListPresenter( val searchState = searchPresenter.present() val spaceFiltersState = spaceFiltersPresenter.present() val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() + val globalSearchState = globalSearchPresenter.present() LaunchedEffect(Unit) { roomListDataSource.launchIn(this) @@ -177,6 +184,7 @@ class RoomListPresenter( leaveRoomState = leaveRoomState, filtersState = filtersState, searchState = searchState, + globalSearchState = globalSearchState, spaceFiltersState = spaceFiltersState, contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt index e184a5a57f..4164c56e9d 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt @@ -11,6 +11,7 @@ package io.element.android.features.home.impl.roomlist import androidx.compose.runtime.Immutable 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.search.GlobalSearchState import io.element.android.features.home.impl.search.RoomListSearchState import io.element.android.features.home.impl.spacefilters.SpaceFiltersState import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState @@ -27,6 +28,7 @@ data class RoomListState( val leaveRoomState: LeaveRoomState, val filtersState: RoomListFiltersState, val searchState: RoomListSearchState, + val globalSearchState: GlobalSearchState, val spaceFiltersState: SpaceFiltersState, val contentState: RoomListContentState, val acceptDeclineInviteState: AcceptDeclineInviteState, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt index 97600e0247..034e7acd42 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt @@ -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.aRoomListRoomSummary 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.aGlobalSearchState 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.anUnselectedSpaceFiltersState @@ -53,6 +55,7 @@ internal fun aRoomListState( declineInviteMenu: RoomListState.DeclineInviteMenu = RoomListState.DeclineInviteMenu.Hidden, leaveRoomState: LeaveRoomState = aLeaveRoomState(), searchState: RoomListSearchState = aRoomListSearchState(), + globalSearchState: GlobalSearchState = aGlobalSearchState(), filtersState: RoomListFiltersState = aRoomListFiltersState(), spaceFiltersState: SpaceFiltersState = anUnselectedSpaceFiltersState(), contentState: RoomListContentState = aRoomsContentState(), @@ -66,6 +69,7 @@ internal fun aRoomListState( leaveRoomState = leaveRoomState, filtersState = filtersState, searchState = searchState, + globalSearchState = globalSearchState, spaceFiltersState = spaceFiltersState, contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt index 5e8570adec..c0cb5f7c01 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt @@ -12,6 +12,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat import io.element.android.features.home.api.HomeEntryPoint 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.room.JoinedRoom import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -44,7 +45,7 @@ class DefaultHomeEntryPointTest : RobolectricTest() { ) } 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 navigateToCreateSpace() = lambdaError() override fun navigateToSettings() = lambdaError()