change(room directory) : move the the room directory entry from room list filter to start chat screen.

This commit is contained in:
ganfra 2025-03-03 16:25:15 +01:00
parent 9ff00b532d
commit 95ecbb7225
24 changed files with 91 additions and 158 deletions

View file

@ -262,10 +262,6 @@ class LoggedInFlowNode @AssistedInject constructor(
plugins<Callback>().forEach { it.onOpenBugReport() } plugins<Callback>().forEach { it.onOpenBugReport() }
} }
override fun onRoomDirectorySearchClick() {
backstack.push(NavTarget.RoomDirectorySearch)
}
override fun onLogoutForNativeSlidingSyncMigrationNeeded() { override fun onLogoutForNativeSlidingSyncMigrationNeeded() {
backstack.push(NavTarget.LogoutForNativeSlidingSyncMigrationNeeded) backstack.push(NavTarget.LogoutForNativeSlidingSyncMigrationNeeded)
} }
@ -360,6 +356,10 @@ class LoggedInFlowNode @AssistedInject constructor(
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) { override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames)) backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames))
} }
override fun onOpenRoomDirectory() {
backstack.push(NavTarget.RoomDirectorySearch)
}
} }
createRoomEntryPoint createRoomEntryPoint

View file

@ -22,5 +22,6 @@ interface CreateRoomEntryPoint : FeatureEntryPoint {
interface Callback : Plugin { interface Callback : Plugin {
fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>)
fun onOpenRoomDirectory()
} }
} }

View file

@ -21,15 +21,19 @@ interface CreateRoomNavigator : Plugin {
fun onCreateNewRoom() fun onCreateNewRoom()
fun onShowJoinRoomByAddress() fun onShowJoinRoomByAddress()
fun onDismissJoinRoomByAddress() fun onDismissJoinRoomByAddress()
fun onOpenRoomDirectory()
} }
class DefaultCreateRoomNavigator( class DefaultCreateRoomNavigator(
private val backstack: BackStack<NavTarget>, private val backstack: BackStack<NavTarget>,
private val overlay: Overlay<NavTarget>, private val overlay: Overlay<NavTarget>,
private val openRoom: (RoomIdOrAlias, List<String>) -> Unit, private val openRoom: (RoomIdOrAlias, List<String>) -> Unit,
private val openRoomDirectory: () -> Unit,
) : CreateRoomNavigator { ) : CreateRoomNavigator {
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) = openRoom(roomIdOrAlias, serverNames) override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) = openRoom(roomIdOrAlias, serverNames)
override fun onOpenRoomDirectory() = openRoomDirectory()
override fun onCreateNewRoom() { override fun onCreateNewRoom() {
backstack.push(NavTarget.NewRoom) backstack.push(NavTarget.NewRoom)
} }

View file

@ -60,6 +60,9 @@ class CreateRoomFlowNode @AssistedInject constructor(
overlay = overlay, overlay = overlay,
openRoom = { roomIdOrAlias, viaServers -> openRoom = { roomIdOrAlias, viaServers ->
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomIdOrAlias, viaServers) } plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomIdOrAlias, viaServers) }
},
openRoomDirectory = {
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoomDirectory() }
} }
) )

View file

@ -55,7 +55,8 @@ class CreateRoomRootNode @AssistedInject constructor(
navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList()) navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList())
}, },
onJoinByAddressClick = navigator::onShowJoinRoomByAddress, onJoinByAddressClick = navigator::onShowJoinRoomByAddress,
onInviteFriendsClick = { invitePeople(activity) } onInviteFriendsClick = { invitePeople(activity) },
onRoomDirectorySearchClick = navigator::onOpenRoomDirectory
) )
} }

View file

@ -9,6 +9,8 @@ package io.element.android.features.createroom.impl.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -20,6 +22,8 @@ import io.element.android.features.createroom.impl.userlist.UserListPresenterArg
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.usersearch.api.UserRepository import io.element.android.libraries.usersearch.api.UserRepository
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -31,6 +35,7 @@ class CreateRoomRootPresenter @Inject constructor(
userListDataStore: UserListDataStore, userListDataStore: UserListDataStore,
private val startDMAction: StartDMAction, private val startDMAction: StartDMAction,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val featureFlagService: FeatureFlagService,
) : Presenter<CreateRoomRootState> { ) : Presenter<CreateRoomRootState> {
private val presenter = presenterFactory.create( private val presenter = presenterFactory.create(
UserListPresenterArgs( UserListPresenterArgs(
@ -47,6 +52,8 @@ class CreateRoomRootPresenter @Inject constructor(
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) } val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val isRoomDirectorySearchEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch).collectAsState(initial = false)
fun handleEvents(event: CreateRoomRootEvents) { fun handleEvents(event: CreateRoomRootEvents) {
when (event) { when (event) {
is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch { is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch {
@ -64,6 +71,7 @@ class CreateRoomRootPresenter @Inject constructor(
applicationName = buildMeta.applicationName, applicationName = buildMeta.applicationName,
userListState = userListState, userListState = userListState,
startDmAction = startDmActionState.value, startDmAction = startDmActionState.value,
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
eventSink = ::handleEvents, eventSink = ::handleEvents,
) )
} }

View file

@ -15,5 +15,6 @@ data class CreateRoomRootState(
val applicationName: String, val applicationName: String,
val userListState: UserListState, val userListState: UserListState,
val startDmAction: AsyncAction<RoomId>, val startDmAction: AsyncAction<RoomId>,
val isRoomDirectorySearchEnabled: Boolean,
val eventSink: (CreateRoomRootEvents) -> Unit, val eventSink: (CreateRoomRootEvents) -> Unit,
) )

View file

@ -53,6 +53,9 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
aCreateRoomRootState( aCreateRoomRootState(
startDmAction = ConfirmingStartDmWithMatrixUser(aMatrixUser()), startDmAction = ConfirmingStartDmWithMatrixUser(aMatrixUser()),
), ),
aCreateRoomRootState(
isRoomDirectorySearchEnabled = true,
),
) )
} }
@ -60,10 +63,12 @@ fun aCreateRoomRootState(
applicationName: String = "Element X Preview", applicationName: String = "Element X Preview",
userListState: UserListState = aUserListState(), userListState: UserListState = aUserListState(),
startDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized, startDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
isRoomDirectorySearchEnabled: Boolean = false,
eventSink: (CreateRoomRootEvents) -> Unit = {}, eventSink: (CreateRoomRootEvents) -> Unit = {},
) = CreateRoomRootState( ) = CreateRoomRootState(
applicationName = applicationName, applicationName = applicationName,
userListState = userListState, userListState = userListState,
startDmAction = startDmAction, startDmAction = startDmAction,
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
eventSink = eventSink, eventSink = eventSink,
) )

View file

@ -56,6 +56,7 @@ fun CreateRoomRootView(
onOpenDM: (RoomId) -> Unit, onOpenDM: (RoomId) -> Unit,
onInviteFriendsClick: () -> Unit, onInviteFriendsClick: () -> Unit,
onJoinByAddressClick: () -> Unit, onJoinByAddressClick: () -> Unit,
onRoomDirectorySearchClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Scaffold( Scaffold(
@ -91,6 +92,7 @@ fun CreateRoomRootView(
onNewRoomClick = onNewRoomClick, onNewRoomClick = onNewRoomClick,
onInvitePeopleClick = onInviteFriendsClick, onInvitePeopleClick = onInviteFriendsClick,
onJoinByAddressClick = onJoinByAddressClick, onJoinByAddressClick = onJoinByAddressClick,
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
onDmClick = onOpenDM, onDmClick = onOpenDM,
) )
} }
@ -156,6 +158,7 @@ private fun CreateRoomActionButtonsList(
onNewRoomClick: () -> Unit, onNewRoomClick: () -> Unit,
onInvitePeopleClick: () -> Unit, onInvitePeopleClick: () -> Unit,
onJoinByAddressClick: () -> Unit, onJoinByAddressClick: () -> Unit,
onRoomDirectorySearchClick: () -> Unit,
onDmClick: (RoomId) -> Unit, onDmClick: (RoomId) -> Unit,
) { ) {
LazyColumn { LazyColumn {
@ -166,6 +169,15 @@ private fun CreateRoomActionButtonsList(
onClick = onNewRoomClick, onClick = onNewRoomClick,
) )
} }
if (state.isRoomDirectorySearchEnabled) {
item {
CreateRoomActionButton(
iconRes = CompoundDrawables.ic_compound_list_bulleted,
text = stringResource(id = R.string.screen_room_directory_search_title),
onClick = onRoomDirectorySearchClick,
)
}
}
item { item {
CreateRoomActionButton( CreateRoomActionButton(
iconRes = CompoundDrawables.ic_compound_share_android, iconRes = CompoundDrawables.ic_compound_share_android,
@ -242,5 +254,6 @@ internal fun CreateRoomRootViewPreview(@PreviewParameter(CreateRoomRootStateProv
onOpenDM = {}, onOpenDM = {},
onJoinByAddressClick = {}, onJoinByAddressClick = {},
onInviteFriendsClick = {}, onInviteFriendsClick = {},
onRoomDirectorySearchClick = {},
) )
} }

View file

@ -19,6 +19,7 @@ You can change this anytime in room settings."</string>
<string name="screen_create_room_room_visibility_section_title">"Room visibility"</string> <string name="screen_create_room_room_visibility_section_title">"Room visibility"</string>
<string name="screen_create_room_title">"Create a room"</string> <string name="screen_create_room_title">"Create a room"</string>
<string name="screen_create_room_topic_label">"Topic (optional)"</string> <string name="screen_create_room_topic_label">"Topic (optional)"</string>
<string name="screen_room_directory_search_title">"Room directory"</string>
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string> <string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
<string name="screen_start_chat_join_room_by_address_action">"Join room by address"</string> <string name="screen_start_chat_join_room_by_address_action">"Join room by address"</string>
<string name="screen_start_chat_join_room_by_address_invalid_address">"Not a valid address"</string> <string name="screen_start_chat_join_room_by_address_invalid_address">"Not a valid address"</string>

View file

@ -15,7 +15,8 @@ class FakeCreateRoomNavigator(
private val createNewRoomLambda: () -> Unit = {}, private val createNewRoomLambda: () -> Unit = {},
private val showJoinRoomByAddressLambda: () -> Unit = {}, private val showJoinRoomByAddressLambda: () -> Unit = {},
private val dismissJoinRoomByAddressLambda: () -> Unit = {}, private val dismissJoinRoomByAddressLambda: () -> Unit = {},
) : CreateRoomNavigator { private val openRoomDirectoryLambda: () -> Unit = {},
) : CreateRoomNavigator {
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) { override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
openRoomLambda(roomIdOrAlias, serverNames) openRoomLambda(roomIdOrAlias, serverNames)
} }
@ -31,4 +32,8 @@ class FakeCreateRoomNavigator(
override fun onDismissJoinRoomByAddress() { override fun onDismissJoinRoomByAddress() {
dismissJoinRoomByAddressLambda() dismissJoinRoomByAddressLambda()
} }
override fun onOpenRoomDirectory() {
openRoomDirectoryLambda()
}
} }

View file

@ -19,6 +19,8 @@ import io.element.android.features.createroom.impl.userlist.FakeUserListPresente
import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.features.createroom.impl.userlist.UserListDataStore
import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.features.createroom.test.FakeStartDMAction
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
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.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -163,14 +165,34 @@ class CreateRoomRootPresenterTest {
} }
} }
@Test
fun `present - room directory search`() = runTest {
val presenter = createCreateRoomRootPresenter(isRoomDirectorySearchEnabled = true)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().let { state ->
assertThat(state.isRoomDirectorySearchEnabled).isTrue()
}
}
}
private fun createCreateRoomRootPresenter( private fun createCreateRoomRootPresenter(
startDMAction: StartDMAction = FakeStartDMAction(), startDMAction: StartDMAction = FakeStartDMAction(),
isRoomDirectorySearchEnabled: Boolean = false,
): CreateRoomRootPresenter { ): CreateRoomRootPresenter {
val featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.RoomDirectorySearch.key to isRoomDirectorySearchEnabled,
),
)
return CreateRoomRootPresenter( return CreateRoomRootPresenter(
presenterFactory = FakeUserListPresenterFactory(FakeUserListPresenter()), presenterFactory = FakeUserListPresenterFactory(FakeUserListPresenter()),
userRepository = FakeUserRepository(), userRepository = FakeUserRepository(),
userListDataStore = UserListDataStore(), userListDataStore = UserListDataStore(),
startDMAction = startDMAction, startDMAction = startDMAction,
featureFlagService = featureFlagService,
buildMeta = aBuildMeta(), buildMeta = aBuildMeta(),
) )
} }

View file

@ -116,6 +116,21 @@ class CreateRoomRootViewTest {
rule.clickOn(R.string.screen_start_chat_join_room_by_address_action) rule.clickOn(R.string.screen_start_chat_join_room_by_address_action)
} }
} }
@Test
fun `clicking on room directory invokes the expected callback`() {
val eventsRecorder = EventsRecorder<CreateRoomRootEvents>(expectEvents = false)
ensureCalledOnce {
rule.setCreateRoomRootView(
aCreateRoomRootState(
eventSink = eventsRecorder,
isRoomDirectorySearchEnabled = true
),
onRoomDirectorySearchClick = it
)
rule.clickOn(R.string.screen_room_directory_search_title)
}
}
} }
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreateRoomRootView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreateRoomRootView(
@ -125,6 +140,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreat
onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
onInviteFriendsClick: () -> Unit = EnsureNeverCalled(), onInviteFriendsClick: () -> Unit = EnsureNeverCalled(),
onJoinRoomByAddressClick: () -> Unit = EnsureNeverCalled(), onJoinRoomByAddressClick: () -> Unit = EnsureNeverCalled(),
onRoomDirectorySearchClick: () -> Unit = EnsureNeverCalled(),
) { ) {
setContent { setContent {
CreateRoomRootView( CreateRoomRootView(
@ -133,7 +149,8 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreat
onNewRoomClick = onNewRoomClick, onNewRoomClick = onNewRoomClick,
onOpenDM = onOpenDM, onOpenDM = onOpenDM,
onInviteFriendsClick = onInviteFriendsClick, onInviteFriendsClick = onInviteFriendsClick,
onJoinByAddressClick = onJoinRoomByAddressClick onJoinByAddressClick = onJoinRoomByAddressClick,
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
) )
} }
} }

View file

@ -28,7 +28,6 @@ interface RoomListEntryPoint : FeatureEntryPoint {
fun onSessionConfirmRecoveryKeyClick() fun onSessionConfirmRecoveryKeyClick()
fun onRoomSettingsClick(roomId: RoomId) fun onRoomSettingsClick(roomId: RoomId)
fun onReportBugClick() fun onReportBugClick()
fun onRoomDirectorySearchClick()
fun onLogoutForNativeSlidingSyncMigrationNeeded() fun onLogoutForNativeSlidingSyncMigrationNeeded()
} }
} }

View file

@ -82,10 +82,6 @@ class RoomListNode @AssistedInject constructor(
} }
} }
private fun onRoomDirectorySearchClick() {
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomDirectorySearchClick() }
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
@ -100,7 +96,6 @@ class RoomListNode @AssistedInject constructor(
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick, onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
onRoomSettingsClick = this::onRoomSettingsClick, onRoomSettingsClick = this::onRoomSettingsClick,
onMenuActionClick = { onMenuActionClick(activity, it) }, onMenuActionClick = { onMenuActionClick(activity, it) },
onRoomDirectorySearchClick = this::onRoomDirectorySearchClick,
modifier = modifier, modifier = modifier,
) { ) {
acceptDeclineInviteView.Render( acceptDeclineInviteView.Render(

View file

@ -49,7 +49,6 @@ fun RoomListView(
onCreateRoomClick: () -> Unit, onCreateRoomClick: () -> Unit,
onRoomSettingsClick: (roomId: RoomId) -> Unit, onRoomSettingsClick: (roomId: RoomId) -> Unit,
onMenuActionClick: (RoomListMenuAction) -> Unit, onMenuActionClick: (RoomListMenuAction) -> Unit,
onRoomDirectorySearchClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
acceptDeclineInviteView: @Composable () -> Unit, acceptDeclineInviteView: @Composable () -> Unit,
) { ) {
@ -83,7 +82,6 @@ fun RoomListView(
state = state.searchState, state = state.searchState,
eventSink = state.eventSink, eventSink = state.eventSink,
onRoomClick = onRoomClick, onRoomClick = onRoomClick,
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
modifier = Modifier modifier = Modifier
.statusBarsPadding() .statusBarsPadding()
.padding(top = topPadding) .padding(top = topPadding)
@ -177,7 +175,6 @@ internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class)
onCreateRoomClick = {}, onCreateRoomClick = {},
onRoomSettingsClick = {}, onRoomSettingsClick = {},
onMenuActionClick = {}, onMenuActionClick = {},
onRoomDirectorySearchClick = {},
acceptDeclineInviteView = {}, acceptDeclineInviteView = {},
) )
} }

View file

@ -15,14 +15,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import javax.inject.Inject import javax.inject.Inject
class RoomListSearchPresenter @Inject constructor( class RoomListSearchPresenter @Inject constructor(
private val dataSource: RoomListSearchDataSource, private val dataSource: RoomListSearchDataSource,
private val featureFlagService: FeatureFlagService,
) : Presenter<RoomListSearchState> { ) : Presenter<RoomListSearchState> {
@Composable @Composable
override fun present(): RoomListSearchState { override fun present(): RoomListSearchState {
@ -57,14 +54,12 @@ class RoomListSearchPresenter @Inject constructor(
} }
} }
val isRoomDirectorySearchEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch).collectAsState(initial = false)
val searchResults by dataSource.roomSummaries.collectAsState(initial = persistentListOf()) val searchResults by dataSource.roomSummaries.collectAsState(initial = persistentListOf())
return RoomListSearchState( return RoomListSearchState(
isSearchActive = isSearchActive, isSearchActive = isSearchActive,
query = searchQuery, query = searchQuery,
results = searchResults, results = searchResults,
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }

View file

@ -14,8 +14,5 @@ data class RoomListSearchState(
val isSearchActive: Boolean, val isSearchActive: Boolean,
val query: String, val query: String,
val results: ImmutableList<RoomListRoomSummary>, val results: ImmutableList<RoomListRoomSummary>,
val isRoomDirectorySearchEnabled: Boolean,
val eventSink: (RoomListSearchEvents) -> Unit val eventSink: (RoomListSearchEvents) -> Unit
) { )
val displayRoomDirectorySearch = query.isEmpty() && isRoomDirectorySearchEnabled
}

View file

@ -17,7 +17,6 @@ class RoomListSearchStateProvider : PreviewParameterProvider<RoomListSearchState
override val values: Sequence<RoomListSearchState> override val values: Sequence<RoomListSearchState>
get() = sequenceOf( get() = sequenceOf(
aRoomListSearchState(), aRoomListSearchState(),
aRoomListSearchState(isRoomDirectorySearchEnabled = true),
aRoomListSearchState( aRoomListSearchState(
isSearchActive = true, isSearchActive = true,
query = "Test", query = "Test",
@ -30,12 +29,10 @@ fun aRoomListSearchState(
isSearchActive: Boolean = false, isSearchActive: Boolean = false,
query: String = "", query: String = "",
results: ImmutableList<RoomListRoomSummary> = persistentListOf(), results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
isRoomDirectorySearchEnabled: Boolean = false,
eventSink: (RoomListSearchEvents) -> Unit = { }, eventSink: (RoomListSearchEvents) -> Unit = { },
) = RoomListSearchState( ) = RoomListSearchState(
isSearchActive = isSearchActive, isSearchActive = isSearchActive,
query = query, query = query,
results = results, results = results,
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
eventSink = eventSink, eventSink = eventSink,
) )

View file

@ -12,12 +12,9 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -26,7 +23,6 @@ import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
@ -38,22 +34,18 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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.RoomListEvents import io.element.android.features.roomlist.impl.RoomListEvents
import io.element.android.features.roomlist.impl.components.RoomSummaryRow import io.element.android.features.roomlist.impl.components.RoomSummaryRow
import io.element.android.features.roomlist.impl.contentType import io.element.android.features.roomlist.impl.contentType
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.button.SuperButton
import io.element.android.libraries.designsystem.modifiers.applyIf import io.element.android.libraries.designsystem.modifiers.applyIf
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.ButtonSize
import io.element.android.libraries.designsystem.theme.components.FilledTextField import io.element.android.libraries.designsystem.theme.components.FilledTextField
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.IconButton import io.element.android.libraries.designsystem.theme.components.IconButton
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.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.copy import io.element.android.libraries.designsystem.utils.copy
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -64,7 +56,6 @@ internal fun RoomListSearchView(
state: RoomListSearchState, state: RoomListSearchState,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvents) -> Unit,
onRoomClick: (RoomId) -> Unit, onRoomClick: (RoomId) -> Unit,
onRoomDirectorySearchClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
BackHandler(enabled = state.isSearchActive) { BackHandler(enabled = state.isSearchActive) {
@ -91,7 +82,6 @@ internal fun RoomListSearchView(
state = state, state = state,
onRoomClick = onRoomClick, onRoomClick = onRoomClick,
eventSink = eventSink, eventSink = eventSink,
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
) )
} }
} }
@ -104,7 +94,6 @@ private fun RoomListSearchContent(
state: RoomListSearchState, state: RoomListSearchState,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvents) -> Unit,
onRoomClick: (RoomId) -> Unit, onRoomClick: (RoomId) -> Unit,
onRoomDirectorySearchClick: () -> Unit,
) { ) {
val borderColor = MaterialTheme.colorScheme.tertiary val borderColor = MaterialTheme.colorScheme.tertiary
val strokeWidth = 1.dp val strokeWidth = 1.dp
@ -175,14 +164,6 @@ private fun RoomListSearchContent(
.padding(padding) .padding(padding)
.consumeWindowInsets(padding) .consumeWindowInsets(padding)
) { ) {
if (state.displayRoomDirectorySearch) {
RoomDirectorySearchButton(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp, horizontal = 16.dp),
onClick = onRoomDirectorySearchClick
)
}
LazyColumn( LazyColumn(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) { ) {
@ -201,31 +182,6 @@ private fun RoomListSearchContent(
} }
} }
@Composable
private fun RoomDirectorySearchButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
SuperButton(
onClick = onClick,
modifier = modifier,
buttonSize = ButtonSize.Large,
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = CompoundIcons.ListBulleted(),
contentDescription = null,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(R.string.screen_roomlist_room_directory_button_title),
)
}
}
}
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun RoomListSearchContentPreview(@PreviewParameter(RoomListSearchStateProvider::class) state: RoomListSearchState) = ElementPreview { internal fun RoomListSearchContentPreview(@PreviewParameter(RoomListSearchStateProvider::class) state: RoomListSearchState) = ElementPreview {
@ -233,6 +189,5 @@ internal fun RoomListSearchContentPreview(@PreviewParameter(RoomListSearchStateP
state = state, state = state,
onRoomClick = {}, onRoomClick = {},
eventSink = {}, eventSink = {},
onRoomDirectorySearchClick = {},
) )
} }

View file

@ -240,7 +240,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
onCreateRoomClick: () -> Unit = EnsureNeverCalled(), onCreateRoomClick: () -> Unit = EnsureNeverCalled(),
onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
onMenuActionClick: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(), onMenuActionClick: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(),
onRoomDirectorySearchClick: () -> Unit = EnsureNeverCalled(),
) { ) {
setContent { setContent {
RoomListView( RoomListView(
@ -252,7 +251,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
onCreateRoomClick = onCreateRoomClick, onCreateRoomClick = onCreateRoomClick,
onRoomSettingsClick = onRoomSettingsClick, onRoomSettingsClick = onRoomSettingsClick,
onMenuActionClick = onMenuActionClick, onMenuActionClick = onMenuActionClick,
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
acceptDeclineInviteView = { }, acceptDeclineInviteView = { },
) )
} }

View file

@ -14,9 +14,6 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
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.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.room.aRoomSummary
@ -118,26 +115,10 @@ class RoomListSearchPresenterTest {
} }
} }
} }
@Test
fun `present - room directory search`() = runTest {
val featureFlagService = FakeFeatureFlagService()
featureFlagService.setFeatureEnabled(FeatureFlags.RoomDirectorySearch, true)
val presenter = createRoomListSearchPresenter(featureFlagService = featureFlagService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().let { state ->
assertThat(state.isRoomDirectorySearchEnabled).isTrue()
}
}
}
} }
fun TestScope.createRoomListSearchPresenter( fun TestScope.createRoomListSearchPresenter(
roomListService: RoomListService = FakeRoomListService(), roomListService: RoomListService = FakeRoomListService(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
): RoomListSearchPresenter { ): RoomListSearchPresenter {
return RoomListSearchPresenter( return RoomListSearchPresenter(
dataSource = RoomListSearchDataSource( dataSource = RoomListSearchDataSource(
@ -148,6 +129,5 @@ fun TestScope.createRoomListSearchPresenter(
), ),
coroutineDispatchers = testCoroutineDispatchers(), coroutineDispatchers = testCoroutineDispatchers(),
), ),
featureFlagService = featureFlagService,
) )
} }

View file

@ -1,62 +0,0 @@
/*
* Copyright 2024 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.roomlist.impl.search
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.roomlist.impl.R
import io.element.android.features.roomlist.impl.RoomListEvents
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoomListSearchViewTest {
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `clicking on 'Browse all rooms' invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListSearchEvents>(expectEvents = false)
ensureCalledOnce {
rule.setRoomListSearchView(
aRoomListSearchState(
isSearchActive = true,
isRoomDirectorySearchEnabled = true,
eventSink = eventsRecorder,
),
onRoomDirectorySearchClick = it,
)
rule.clickOn(R.string.screen_roomlist_room_directory_button_title)
}
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomListSearchView(
state: RoomListSearchState,
eventSink: (RoomListEvents) -> Unit = EventsRecorder(expectEvents = false),
onRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
onRoomDirectorySearchClick: () -> Unit = EnsureNeverCalled(),
) {
setContent {
RoomListSearchView(
state = state,
eventSink = eventSink,
onRoomClick = onRoomClick,
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
)
}
}

View file

@ -65,7 +65,8 @@
"screen_create_room_.*", "screen_create_room_.*",
"screen\\.create_room\\..*", "screen\\.create_room\\..*",
"screen_start_chat_.*", "screen_start_chat_.*",
"screen\\.start_chat\\..*" "screen\\.start_chat\\..*",
"screen_room_directory_search_title*"
] ]
}, },
{ {