diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index dc7eaded75..366cc1e0bd 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -38,9 +38,8 @@ anvil { } dependencies { - anvil(projects.anvilcodegen) implementation(projects.anvilannotations) - + anvil(projects.anvilcodegen) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) @@ -48,6 +47,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.elementresources) implementation(projects.libraries.uiStrings) + implementation(projects.features.selectusers.api) api(projects.features.createroom.api) testImplementation(libs.test.junit) @@ -56,6 +56,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.features.selectusers.impl) androidTestImplementation(libs.test.junitext) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt index b9e508032a..5d2f0f684c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt @@ -19,8 +19,6 @@ package io.element.android.features.createroom.impl.root import io.element.android.libraries.matrix.ui.model.MatrixUser sealed interface CreateRoomRootEvents { - data class UpdateSearchQuery(val query: String) : CreateRoomRootEvents data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents object InvitePeople : CreateRoomRootEvents - data class OnSearchActiveChanged(val active: Boolean) : CreateRoomRootEvents } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index 23c8d8eaf3..c9fd95339c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -17,74 +17,33 @@ package io.element.android.features.createroom.impl.root import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue +import io.element.android.features.selectusers.api.SelectUsersPresenter import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.matrix.api.core.MatrixPatterns -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.model.MatrixUser -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import timber.log.Timber import javax.inject.Inject -class CreateRoomRootPresenter @Inject constructor() : Presenter { +class CreateRoomRootPresenter @Inject constructor( + private val selectUsersPresenter: SelectUsersPresenter, +) : Presenter { @Composable override fun present(): CreateRoomRootState { - var isSearchActive by rememberSaveable { mutableStateOf(false) } - var searchQuery by rememberSaveable { mutableStateOf("") } - val searchResults: MutableState> = remember { - mutableStateOf(persistentListOf()) - } + val selectUsersState = selectUsersPresenter.present() fun handleEvents(event: CreateRoomRootEvents) { when (event) { - is CreateRoomRootEvents.OnSearchActiveChanged -> isSearchActive = event.active - is CreateRoomRootEvents.UpdateSearchQuery -> searchQuery = event.query is CreateRoomRootEvents.StartDM -> handleStartDM(event.matrixUser) CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action } } - LaunchedEffect(searchQuery) { - // Clear the search results before performing the search, manually add a fake result with the matrixId, if any - searchResults.value = if (MatrixPatterns.isUserId(searchQuery)) { - persistentListOf(MatrixUser(UserId(searchQuery))) - } else { - persistentListOf() - } - // Perform the search asynchronously - if (searchQuery.isNotEmpty()) { - searchResults.value = performSearch(searchQuery) - } - } - return CreateRoomRootState( + selectUsersState = selectUsersState, eventSink = ::handleEvents, - isSearchActive = isSearchActive, - searchQuery = searchQuery, - searchResults = searchResults.value, ) } - private fun performSearch(query: String): ImmutableList { - val isMatrixId = MatrixPatterns.isUserId(query) - val results = mutableListOf()// TODO trigger /search request - if (isMatrixId && results.none { it.id.value == query }) { - val getProfileResult: MatrixUser? = null // TODO trigger /profile request - val profile = getProfileResult ?: MatrixUser(UserId(query)) - results.add(0, profile) - } - return results.toImmutableList() - } - private fun handleStartDM(matrixUser: MatrixUser) { Timber.d("handleStartDM: $matrixUser") // Todo handle start DM action } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt index be9757f122..b7be3f44fb 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt @@ -16,13 +16,11 @@ package io.element.android.features.createroom.impl.root +import io.element.android.features.selectusers.api.SelectUsersState import io.element.android.libraries.matrix.ui.model.MatrixUser import kotlinx.collections.immutable.ImmutableList -// Do not use default value, so no member get forgotten in the presenters. data class CreateRoomRootState( + val selectUsersState: SelectUsersState, val eventSink: (CreateRoomRootEvents) -> Unit, - val isSearchActive: Boolean, - val searchQuery: String, - val searchResults: ImmutableList, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt index 5f050ab1cc..e129c98fd4 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.createroom.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.selectusers.api.SelectUsersState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.model.MatrixUser import kotlinx.collections.immutable.persistentListOf @@ -25,35 +26,16 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider get() = sequenceOf( aCreateRoomRootState(), - aCreateRoomRootState().copy(isSearchActive = true), - aCreateRoomRootState().copy(isSearchActive = true, searchQuery = "someone"), - aCreateRoomRootState().copy( - isSearchActive = true, - searchQuery = "@someone:matrix.org", - searchResults = persistentListOf( - MatrixUser(id = UserId("@someone:matrix.org")), - MatrixUser(id = UserId("@someone:matrix.org"), username = "someone"), - MatrixUser( - id = UserId("@someone_with_a_very_long_matrix_identifier:a_very_long_domain.org"), - username = "hey, I am someone with a very long display name" - ), - MatrixUser(id = UserId("@someone_2:matrix.org"), username = "someone 2"), - MatrixUser(id = UserId("@someone_3:matrix.org"), username = "someone 3"), - MatrixUser(id = UserId("@someone_4:matrix.org"), username = "someone 4"), - MatrixUser(id = UserId("@someone_5:matrix.org"), username = "someone 5"), - MatrixUser(id = UserId("@someone_6:matrix.org"), username = "someone 6"), - MatrixUser(id = UserId("@someone_7:matrix.org"), username = "someone 7"), - MatrixUser(id = UserId("@someone_8:matrix.org"), username = "someone 8"), - MatrixUser(id = UserId("@someone_9:matrix.org"), username = "someone 9"), - MatrixUser(id = UserId("@someone_10:matrix.org"), username = "someone 10"), - ) - ), ) } fun aCreateRoomRootState() = CreateRoomRootState( eventSink = {}, - isSearchActive = false, - searchQuery = "", - searchResults = persistentListOf(), + selectUsersState = SelectUsersState( + searchQuery = "", + searchResults = persistentListOf(), + selectedUsers = persistentListOf(), + isSearchActive = false, + eventSink = {}, + ) ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt index 5417ca82da..faa2f3eb68 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt @@ -43,6 +43,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import io.element.android.features.selectusers.api.SearchUserBar +import io.element.android.features.selectusers.api.SelectUsersView import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -70,7 +72,7 @@ fun CreateRoomRootView( Scaffold( modifier = modifier.fillMaxWidth(), topBar = { - if (!state.isSearchActive) { + if (!state.selectUsersState.isSearchActive) { CreateRoomRootViewTopBar(onClosePressed = onClosePressed) } } @@ -79,18 +81,12 @@ fun CreateRoomRootView( modifier = Modifier.padding(paddingValues), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - CreateRoomSearchBar( + SelectUsersView( modifier = Modifier.fillMaxWidth(), - query = state.searchQuery, - placeHolderTitle = stringResource(StringR.string.search_for_someone), - results = state.searchResults, - active = state.isSearchActive, - onActiveChanged = { state.eventSink(CreateRoomRootEvents.OnSearchActiveChanged(it)) }, - onTextChanged = { state.eventSink(CreateRoomRootEvents.UpdateSearchQuery(it)) }, - onResultSelected = { state.eventSink(CreateRoomRootEvents.StartDM(it)) } + state = state.selectUsersState ) - if (!state.isSearchActive) { + if (!state.selectUsersState.isSearchActive) { CreateRoomActionButtonsList( onNewRoomClicked = onNewRoomClicked, onInvitePeopleClicked = { state.eventSink(CreateRoomRootEvents.InvitePeople) }, @@ -123,77 +119,6 @@ fun CreateRoomRootViewTopBar( ) } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun CreateRoomSearchBar( - query: String, - placeHolderTitle: String, - results: ImmutableList, - active: Boolean, - modifier: Modifier = Modifier, - onActiveChanged: (Boolean) -> Unit = {}, - onTextChanged: (String) -> Unit = {}, - onResultSelected: (MatrixUser) -> Unit = {}, -) { - val focusManager = LocalFocusManager.current - - if (!active) { - onTextChanged("") - focusManager.clearFocus() - } - - SearchBar( - query = query, - onQueryChange = onTextChanged, - onSearch = { focusManager.clearFocus() }, - active = active, - onActiveChange = onActiveChanged, - modifier = modifier - .padding(horizontal = if (!active) 16.dp else 0.dp), - placeholder = { - Text( - text = placeHolderTitle, - modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine) - ) - }, - leadingIcon = if (active) { - { BackButton(onClick = { onActiveChanged(false) }) } - } else { - null - }, - trailingIcon = when { - active && query.isNotEmpty() -> { - { - IconButton(onClick = { onTextChanged("") }) { - Icon(Icons.Default.Close, stringResource(StringR.string.a11y_clear)) - } - } - } - !active -> { - { - Icon( - imageVector = Icons.Default.Search, - contentDescription = stringResource(StringR.string.search), - modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine) - ) - } - } - else -> null - }, - colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent), - content = { - LazyColumn { - items(results) { - CreateRoomSearchResultItem( - matrixUser = it, - onClick = { onResultSelected(it) } - ) - } - } - }, - ) -} - @Composable fun CreateRoomActionButtonsList( modifier: Modifier = Modifier, @@ -214,20 +139,6 @@ fun CreateRoomActionButtonsList( } } -@Composable -fun CreateRoomSearchResultItem( - matrixUser: MatrixUser, - modifier: Modifier = Modifier, - onClick: () -> Unit = {}, -) { - MatrixUserRow( - modifier = modifier, - matrixUser = matrixUser, - avatarSize = AvatarSize.Custom(36.dp), - onClick = onClick, - ) -} - @Composable fun CreateRoomActionButton( @DrawableRes iconRes: Int, diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index c13d59f3a6..6a852f4599 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -22,6 +22,7 @@ import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.selectusers.impl.DefaultSelectUsersPresenter import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.model.MatrixUser import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -32,7 +33,8 @@ class CreateRoomRootPresenterTests { @Test fun `present - initial state`() = runTest { - val presenter = CreateRoomRootPresenter() + val selectUsersPresenter = DefaultSelectUsersPresenter() + val presenter = CreateRoomRootPresenter(selectUsersPresenter) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -43,45 +45,20 @@ class CreateRoomRootPresenterTests { @Test fun `present - trigger action buttons`() = runTest { - val presenter = CreateRoomRootPresenter() + val selectUsersPresenter = DefaultSelectUsersPresenter() + val presenter = CreateRoomRootPresenter(selectUsersPresenter) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(CreateRoomRootEvents.CreateRoom) // Not implemented yet initialState.eventSink(CreateRoomRootEvents.InvitePeople) // Not implemented yet } } - @Test - fun `present - update search query`() = runTest { - val presenter = CreateRoomRootPresenter() - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - - initialState.eventSink(CreateRoomRootEvents.OnSearchActiveChanged(true)) - assertThat(awaitItem().isSearchActive).isTrue() - - val matrixIdQuery = "@name:matrix.org" - initialState.eventSink(CreateRoomRootEvents.UpdateSearchQuery(matrixIdQuery)) - assertThat(awaitItem().searchQuery).isEqualTo(matrixIdQuery) - assertThat(awaitItem().searchResults).containsExactly(MatrixUser(UserId(matrixIdQuery))) - - val notMatrixIdQuery = "name" - initialState.eventSink(CreateRoomRootEvents.UpdateSearchQuery(notMatrixIdQuery)) - assertThat(awaitItem().searchQuery).isEqualTo(notMatrixIdQuery) - assertThat(awaitItem().searchResults).isEmpty() - - initialState.eventSink(CreateRoomRootEvents.OnSearchActiveChanged(false)) - assertThat(awaitItem().isSearchActive).isFalse() - } - } - @Test fun `present - trigger start DM action`() = runTest { - val presenter = CreateRoomRootPresenter() + val selectUsersPresenter = DefaultSelectUsersPresenter() + val presenter = CreateRoomRootPresenter(selectUsersPresenter) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 52063dd08e..7ae1a0bd15 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -80,6 +80,7 @@ fun DependencyHandlerScope.allFeaturesApi() { implementation(project(":features:preferences:api")) implementation(project(":features:createroom:api")) implementation(project(":features:verifysession:api")) + implementation(project(":features:selectusers:api")) } fun DependencyHandlerScope.allFeaturesImpl() { @@ -92,4 +93,5 @@ fun DependencyHandlerScope.allFeaturesImpl() { implementation(project(":features:preferences:impl")) implementation(project(":features:createroom:impl")) implementation(project(":features:verifysession:impl")) + implementation(project(":features:selectusers:impl")) }