Use new selectusers module

This commit is contained in:
Maxime NATUREL
2023-03-15 11:22:14 +01:00
committed by Florian Renaud
parent 68f8d72a98
commit f950729d17
8 changed files with 34 additions and 206 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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<CreateRoomRootState> {
class CreateRoomRootPresenter @Inject constructor(
private val selectUsersPresenter: SelectUsersPresenter,
) : Presenter<CreateRoomRootState> {
@Composable
override fun present(): CreateRoomRootState {
var isSearchActive by rememberSaveable { mutableStateOf(false) }
var searchQuery by rememberSaveable { mutableStateOf("") }
val searchResults: MutableState<ImmutableList<MatrixUser>> = 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<MatrixUser> {
val isMatrixId = MatrixPatterns.isUserId(query)
val results = mutableListOf<MatrixUser>()// 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
}

View File

@@ -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<MatrixUser>,
)

View File

@@ -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<CreateRoomRoot
override val values: Sequence<CreateRoomRootState>
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 = {},
)
)

View File

@@ -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<MatrixUser>,
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,

View File

@@ -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 {

View File

@@ -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"))
}