Fixes after rebase

This commit is contained in:
Maxime NATUREL 2023-03-14 17:53:55 +01:00 committed by Florian Renaud
parent 09af485560
commit b20ea74a10
7 changed files with 109 additions and 37 deletions

View file

@ -26,5 +26,6 @@ dependencies {
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui) implementation(projects.libraries.matrixui)
} }

View file

@ -22,4 +22,5 @@ sealed interface SelectUsersEvents {
data class UpdateSearchQuery(val query: String) : SelectUsersEvents data class UpdateSearchQuery(val query: String) : SelectUsersEvents
data class AddToSelection(val matrixUser: MatrixUser) : SelectUsersEvents data class AddToSelection(val matrixUser: MatrixUser) : SelectUsersEvents
data class RemoveFromSelection(val matrixUser: MatrixUser) : SelectUsersEvents data class RemoveFromSelection(val matrixUser: MatrixUser) : SelectUsersEvents
data class OnSearchActiveChanged(val active: Boolean) : SelectUsersEvents
} }

View file

@ -23,5 +23,6 @@ data class SelectUsersState(
val searchQuery: String, val searchQuery: String,
val searchResults: ImmutableList<MatrixUser>, val searchResults: ImmutableList<MatrixUser>,
val selectedUsers: ImmutableList<MatrixUser>, val selectedUsers: ImmutableList<MatrixUser>,
val isSearchActive: Boolean,
val eventSink: (SelectUsersEvents) -> Unit, val eventSink: (SelectUsersEvents) -> Unit,
) )

View file

@ -17,7 +17,6 @@
package io.element.android.features.selectusers.api package io.element.android.features.selectusers.api
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -25,24 +24,38 @@ import kotlinx.collections.immutable.persistentListOf
open class SelectUsersStateProvider : PreviewParameterProvider<SelectUsersState> { open class SelectUsersStateProvider : PreviewParameterProvider<SelectUsersState> {
override val values: Sequence<SelectUsersState> override val values: Sequence<SelectUsersState>
get() = sequenceOf( get() = sequenceOf(
// TODO add states with selectedUsers
aSelectUsersState(), aSelectUsersState(),
aSelectUsersState().copy(isSearchActive = true),
aSelectUsersState().copy(isSearchActive = true, searchQuery = "someone"),
aSelectUsersState().copy( aSelectUsersState().copy(
selectedUsers = persistentListOf( isSearchActive = true,
aMatrixUser(userName = ""), searchQuery = "@someone:matrix.org",
aMatrixUser(userName = "User"), searchResults = persistentListOf(
aMatrixUser(userName = "User with long name"), 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 aSelectUsersState() = SelectUsersState( fun aSelectUsersState() = SelectUsersState(
isSearchActive = false,
searchQuery = "", searchQuery = "",
searchResults = persistentListOf(), searchResults = persistentListOf(),
selectedUsers = persistentListOf(), selectedUsers = persistentListOf(),
eventSink = {} eventSink = {}
) )
fun aMatrixUser(userName: String): MatrixUser {
return MatrixUser(id = UserId("@id"), username = userName, avatarData = AvatarData("@id", "U"))
}

View file

@ -22,11 +22,12 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
@ -35,10 +36,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.SearchBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
@ -55,9 +52,9 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.DockedSearchBar
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.SearchBar
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.components.MatrixUserRow import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.matrix.ui.model.MatrixUser import io.element.android.libraries.matrix.ui.model.MatrixUser
@ -70,14 +67,9 @@ import io.element.android.libraries.ui.strings.R as StringR
fun SelectUsersView( fun SelectUsersView(
state: SelectUsersState, state: SelectUsersState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onSearchActiveChanged: (Boolean) -> Unit = {},
onSelectionChanged: (ImmutableList<MatrixUser>) -> Unit = {},
) { ) {
var isSearchActive by rememberSaveable { mutableStateOf(false) }
val eventSink = state.eventSink val eventSink = state.eventSink
// TODO how to pass back the selection list?
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@ -86,11 +78,8 @@ fun SelectUsersView(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
query = state.searchQuery, query = state.searchQuery,
results = state.searchResults, results = state.searchResults,
active = isSearchActive, active = state.isSearchActive,
onActiveChanged = { onActiveChanged = { eventSink.invoke(SelectUsersEvents.OnSearchActiveChanged(it)) },
isSearchActive = it
onSearchActiveChanged(it)
},
onTextChanged = { state.eventSink(SelectUsersEvents.UpdateSearchQuery(it)) }, onTextChanged = { state.eventSink(SelectUsersEvents.UpdateSearchQuery(it)) },
onResultSelected = { state.eventSink(SelectUsersEvents.AddToSelection(it)) } onResultSelected = { state.eventSink(SelectUsersEvents.AddToSelection(it)) }
) )
@ -133,7 +122,7 @@ fun SelectedUser(
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Avatar(matrixUser.avatarData.copy(size = AvatarSize.Custom(56))) Avatar(matrixUser.avatarData.copy(size = AvatarSize.Custom(56.dp)))
Text( Text(
text = matrixUser.getBestName(), text = matrixUser.getBestName(),
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@ -177,7 +166,7 @@ fun SearchUserBar(
focusManager.clearFocus() focusManager.clearFocus()
} }
DockedSearchBar( SearchBar(
query = query, query = query,
onQueryChange = onTextChanged, onQueryChange = onTextChanged,
onSearch = { focusManager.clearFocus() }, onSearch = { focusManager.clearFocus() },
@ -193,7 +182,9 @@ fun SearchUserBar(
}, },
leadingIcon = if (active) { leadingIcon = if (active) {
{ BackButton(onClick = { onActiveChanged(false) }) } { BackButton(onClick = { onActiveChanged(false) }) }
} else null, } else {
null
},
trailingIcon = when { trailingIcon = when {
active && query.isNotEmpty() -> { active && query.isNotEmpty() -> {
{ {
@ -213,14 +204,15 @@ fun SearchUserBar(
} }
else -> null else -> null
}, },
shape = if (!active) SearchBarDefaults.dockedShape else SearchBarDefaults.fullScreenShape,
colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent), colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent),
content = { content = {
results.forEach { LazyColumn {
SearchUserResultItem( items(results) {
matrixUser = it, SearchUserResultItem(
onClick = { onResultSelected(it) } matrixUser = it,
) onClick = { onResultSelected(it) }
)
}
} }
}, },
) )
@ -233,9 +225,9 @@ fun SearchUserResultItem(
onClick: () -> Unit = {}, onClick: () -> Unit = {},
) { ) {
MatrixUserRow( MatrixUserRow(
modifier = modifier.heightIn(min = 56.dp), modifier = modifier,
matrixUser = matrixUser, matrixUser = matrixUser,
avatarSize = AvatarSize.Custom(36), avatarSize = AvatarSize.Custom(36.dp),
onClick = onClick, onClick = onClick,
) )
} }

View file

@ -58,11 +58,13 @@ class DefaultSelectUsersPresenter @Inject constructor() : SelectUsersPresenter {
} }
LaunchedEffect(searchQuery) { 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)) { searchResults.value = if (MatrixPatterns.isUserId(searchQuery)) {
persistentListOf(MatrixUser(UserId(searchQuery))) persistentListOf(MatrixUser(UserId(searchQuery)))
} else { } else {
persistentListOf() persistentListOf()
} }
// Perform the search asynchronously
if (searchQuery.isNotEmpty()) { if (searchQuery.isNotEmpty()) {
searchResults.value = performSearch(searchQuery) searchResults.value = performSearch(searchQuery)
} }

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.selectusers.impl
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.api.SelectUsersEvents
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class DefaultSelectUsersPresenterTests {
@Test
fun `present - initial state`() = runTest {
val presenter = DefaultSelectUsersPresenter()
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState)
}
}
@Test
fun `present - update search query`() = runTest {
val presenter = DefaultSelectUsersPresenter()
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val matrixIdQuery = "@name:matrix.org"
initialState.eventSink(SelectUsersEvents.UpdateSearchQuery(matrixIdQuery))
assertThat(awaitItem().searchQuery).isEqualTo(matrixIdQuery)
assertThat(awaitItem().searchResults).containsExactly(MatrixUser(UserId(matrixIdQuery)))
val notMatrixIdQuery = "name"
initialState.eventSink(SelectUsersEvents.UpdateSearchQuery(notMatrixIdQuery))
assertThat(awaitItem().searchQuery).isEqualTo(notMatrixIdQuery)
assertThat(awaitItem().searchResults).isEmpty()
}
}
}