Merge pull request #211 from vector-im/feature/fre/start_chat_search_matrixid
[Start chat] Show a single result when searching for a matrixId
This commit is contained in:
commit
02f2ace1e4
26 changed files with 246 additions and 64 deletions
1
changelog.d/211.wip
Normal file
1
changelog.d/211.wip
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[Create and join rooms] Show a single result when searching for a matrixId
|
||||||
|
|
@ -16,7 +16,12 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
|
||||||
sealed interface CreateRoomRootEvents {
|
sealed interface CreateRoomRootEvents {
|
||||||
|
data class UpdateSearchQuery(val query: String) : CreateRoomRootEvents
|
||||||
|
data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents
|
||||||
object CreateRoom : CreateRoomRootEvents
|
object CreateRoom : CreateRoomRootEvents
|
||||||
object InvitePeople : CreateRoomRootEvents
|
object InvitePeople : CreateRoomRootEvents
|
||||||
|
data class OnSearchActiveChanged(val active: Boolean) : CreateRoomRootEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,23 +17,76 @@
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
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.libraries.architecture.Presenter
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateRoomRootPresenter @Inject constructor() : Presenter<CreateRoomRootState> {
|
class CreateRoomRootPresenter @Inject constructor() : Presenter<CreateRoomRootState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): CreateRoomRootState {
|
override fun present(): CreateRoomRootState {
|
||||||
|
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
|
val searchResults: MutableState<ImmutableList<MatrixUser>> = remember {
|
||||||
|
mutableStateOf(persistentListOf())
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvents(event: CreateRoomRootEvents) {
|
fun handleEvents(event: CreateRoomRootEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
is CreateRoomRootEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
||||||
|
is CreateRoomRootEvents.UpdateSearchQuery -> searchQuery = event.query
|
||||||
|
is CreateRoomRootEvents.StartDM -> handleStartDM(event.matrixUser)
|
||||||
CreateRoomRootEvents.CreateRoom -> Unit // Todo Handle create room action
|
CreateRoomRootEvents.CreateRoom -> Unit // Todo Handle create room action
|
||||||
CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action
|
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(
|
return CreateRoomRootState(
|
||||||
eventSink = ::handleEvents
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,13 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
// TODO add your ui models. Remove the eventSink if you don't have events.
|
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.
|
// Do not use default value, so no member get forgotten in the presenters.
|
||||||
data class CreateRoomRootState(
|
data class CreateRoomRootState(
|
||||||
val eventSink: (CreateRoomRootEvents) -> Unit
|
val eventSink: (CreateRoomRootEvents) -> Unit,
|
||||||
|
val isSearchActive: Boolean,
|
||||||
|
val searchQuery: String,
|
||||||
|
val searchResults: ImmutableList<MatrixUser>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,43 @@
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
|
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
|
||||||
override val values: Sequence<CreateRoomRootState>
|
override val values: Sequence<CreateRoomRootState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aCreateRoomRootState(),
|
aCreateRoomRootState(),
|
||||||
// Add other state here
|
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(
|
fun aCreateRoomRootState() = CreateRoomRootState(
|
||||||
eventSink = {}
|
eventSink = {},
|
||||||
|
isSearchActive = false,
|
||||||
|
searchQuery = "",
|
||||||
|
searchResults = persistentListOf(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,16 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
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
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
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
|
||||||
|
|
@ -45,15 +43,19 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
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 androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
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.CenterAlignedTopAppBar
|
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
|
||||||
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.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
|
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.model.MatrixUser
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import io.element.android.libraries.designsystem.R as DrawableR
|
import io.element.android.libraries.designsystem.R as DrawableR
|
||||||
import io.element.android.libraries.ui.strings.R as StringR
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
|
|
@ -64,12 +66,10 @@ fun CreateRoomRootView(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClosePressed: () -> Unit = {},
|
onClosePressed: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
var searchText by rememberSaveable { mutableStateOf("") }
|
|
||||||
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
topBar = {
|
topBar = {
|
||||||
if (!isSearchActive) {
|
if (!state.isSearchActive) {
|
||||||
CreateRoomRootViewTopBar(onClosePressed = onClosePressed)
|
CreateRoomRootViewTopBar(onClosePressed = onClosePressed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -80,14 +80,16 @@ fun CreateRoomRootView(
|
||||||
) {
|
) {
|
||||||
CreateRoomSearchBar(
|
CreateRoomSearchBar(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = searchText,
|
query = state.searchQuery,
|
||||||
placeHolderTitle = stringResource(StringR.string.search_for_someone),
|
placeHolderTitle = stringResource(StringR.string.search_for_someone),
|
||||||
active = isSearchActive,
|
results = state.searchResults,
|
||||||
onActiveChanged = { isSearchActive = it },
|
active = state.isSearchActive,
|
||||||
onTextChanged = { searchText = it },
|
onActiveChanged = { state.eventSink(CreateRoomRootEvents.OnSearchActiveChanged(it)) },
|
||||||
|
onTextChanged = { state.eventSink(CreateRoomRootEvents.UpdateSearchQuery(it)) },
|
||||||
|
onResultSelected = { state.eventSink(CreateRoomRootEvents.StartDM(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isSearchActive) {
|
if (!state.isSearchActive) {
|
||||||
CreateRoomActionButtonsList(
|
CreateRoomActionButtonsList(
|
||||||
onNewRoomClicked = { state.eventSink(CreateRoomRootEvents.CreateRoom) },
|
onNewRoomClicked = { state.eventSink(CreateRoomRootEvents.CreateRoom) },
|
||||||
onInvitePeopleClicked = { state.eventSink(CreateRoomRootEvents.InvitePeople) },
|
onInvitePeopleClicked = { state.eventSink(CreateRoomRootEvents.InvitePeople) },
|
||||||
|
|
@ -123,12 +125,14 @@ fun CreateRoomRootViewTopBar(
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateRoomSearchBar(
|
fun CreateRoomSearchBar(
|
||||||
text: String,
|
query: String,
|
||||||
placeHolderTitle: String,
|
placeHolderTitle: String,
|
||||||
|
results: ImmutableList<MatrixUser>,
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onActiveChanged: (Boolean) -> Unit = {},
|
onActiveChanged: (Boolean) -> Unit = {},
|
||||||
onTextChanged: (String) -> Unit = {},
|
onTextChanged: (String) -> Unit = {},
|
||||||
|
onResultSelected: (MatrixUser) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
|
|
@ -137,8 +141,8 @@ fun CreateRoomSearchBar(
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
DockedSearchBar(
|
SearchBar(
|
||||||
query = text,
|
query = query,
|
||||||
onQueryChange = onTextChanged,
|
onQueryChange = onTextChanged,
|
||||||
onSearch = { focusManager.clearFocus() },
|
onSearch = { focusManager.clearFocus() },
|
||||||
active = active,
|
active = active,
|
||||||
|
|
@ -153,9 +157,11 @@ fun CreateRoomSearchBar(
|
||||||
},
|
},
|
||||||
leadingIcon = if (active) {
|
leadingIcon = if (active) {
|
||||||
{ BackButton(onClick = { onActiveChanged(false) }) }
|
{ BackButton(onClick = { onActiveChanged(false) }) }
|
||||||
} else null,
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
trailingIcon = when {
|
trailingIcon = when {
|
||||||
active && text.isNotEmpty() -> {
|
active && query.isNotEmpty() -> {
|
||||||
{
|
{
|
||||||
IconButton(onClick = { onTextChanged("") }) {
|
IconButton(onClick = { onTextChanged("") }) {
|
||||||
Icon(Icons.Default.Close, stringResource(StringR.string.a11y_clear))
|
Icon(Icons.Default.Close, stringResource(StringR.string.a11y_clear))
|
||||||
|
|
@ -173,9 +179,17 @@ fun CreateRoomSearchBar(
|
||||||
}
|
}
|
||||||
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 = {
|
||||||
|
LazyColumn {
|
||||||
|
items(results) {
|
||||||
|
CreateRoomSearchResultItem(
|
||||||
|
matrixUser = it,
|
||||||
|
onClick = { onResultSelected(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,6 +213,20 @@ 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
|
@Composable
|
||||||
fun CreateRoomActionButton(
|
fun CreateRoomActionButton(
|
||||||
@DrawableRes iconRes: Int,
|
@DrawableRes iconRes: Int,
|
||||||
|
|
@ -209,7 +237,7 @@ fun CreateRoomActionButton(
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(min = 56.dp)
|
.height(56.dp)
|
||||||
.clickable { onClick() }
|
.clickable { onClick() }
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import app.cash.molecule.RecompositionClock
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.createroom.impl.root.CreateRoomRootEvents
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.features.createroom.impl.root.CreateRoomRootPresenter
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -42,7 +42,7 @@ class CreateRoomRootPresenterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - send event`() = runTest {
|
fun `present - trigger action buttons`() = runTest {
|
||||||
val presenter = CreateRoomRootPresenter()
|
val presenter = CreateRoomRootPresenter()
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -52,4 +52,42 @@ class CreateRoomRootPresenterTests {
|
||||||
initialState.eventSink(CreateRoomRootEvents.InvitePeople) // 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()
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val matrixUser = MatrixUser(UserId("@name:matrix.org"))
|
||||||
|
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ private fun InitialsAvatar(
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
text = avatarData.getInitial(),
|
text = avatarData.getInitial(),
|
||||||
fontSize = (avatarData.size.value / 2).sp,
|
fontSize = (avatarData.size.dp / 2).value.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,16 @@
|
||||||
|
|
||||||
package io.element.android.libraries.designsystem.components.avatar
|
package io.element.android.libraries.designsystem.components.avatar
|
||||||
|
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
enum class AvatarSize(val value: Int) {
|
sealed class AvatarSize(open val dp: Dp) {
|
||||||
SMALL(32),
|
|
||||||
MEDIUM(40),
|
|
||||||
BIG(48),
|
|
||||||
HUGE(96);
|
|
||||||
|
|
||||||
val dp = value.dp
|
object SMALL : AvatarSize(32.dp)
|
||||||
|
object MEDIUM : AvatarSize(40.dp)
|
||||||
|
object BIG : AvatarSize(48.dp)
|
||||||
|
object HUGE : AvatarSize(96.dp)
|
||||||
|
|
||||||
|
// FIXME maybe remove this field and switch back to an enum (or remove this class) when design system will be integrated
|
||||||
|
data class Custom(override val dp: Dp) : AvatarSize(dp)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ package io.element.android.libraries.designsystem.theme.components
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SearchBarColors
|
import androidx.compose.material3.SearchBarColors
|
||||||
import androidx.compose.material3.SearchBarDefaults
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
|
|
@ -34,7 +35,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DockedSearchBar(
|
fun SearchBar(
|
||||||
query: String,
|
query: String,
|
||||||
onQueryChange: (String) -> Unit,
|
onQueryChange: (String) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
|
@ -45,13 +46,14 @@ fun DockedSearchBar(
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
leadingIcon: @Composable (() -> Unit)? = null,
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
shape: Shape = SearchBarDefaults.dockedShape,
|
shape: Shape = SearchBarDefaults.inputFieldShape,
|
||||||
colors: SearchBarColors = SearchBarDefaults.colors(),
|
colors: SearchBarColors = SearchBarDefaults.colors(),
|
||||||
tonalElevation: Dp = SearchBarDefaults.Elevation,
|
tonalElevation: Dp = SearchBarDefaults.Elevation,
|
||||||
|
windowInsets: WindowInsets = SearchBarDefaults.windowInsets,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
content: @Composable ColumnScope.() -> Unit,
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.DockedSearchBar(
|
androidx.compose.material3.SearchBar(
|
||||||
query = query,
|
query = query,
|
||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
|
@ -65,6 +67,7 @@ fun DockedSearchBar(
|
||||||
shape = shape,
|
shape = shape,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
tonalElevation = tonalElevation,
|
tonalElevation = tonalElevation,
|
||||||
|
windowInsets = windowInsets,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
|
|
@ -80,7 +83,7 @@ internal fun DockedSearchBarDarkPreview() = ElementPreviewDark { ContentToPrevie
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview() {
|
private fun ContentToPreview() {
|
||||||
DockedSearchBar(
|
SearchBar(
|
||||||
query = "Some text",
|
query = "Some text",
|
||||||
onQueryChange = {},
|
onQueryChange = {},
|
||||||
onSearch = {},
|
onSearch = {},
|
||||||
|
|
@ -34,35 +34,34 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
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.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.model.getBestName
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
|
|
||||||
// FIXME Row are not the same height if there is a name or not.
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MatrixUserRow(
|
fun MatrixUserRow(
|
||||||
matrixUser: MatrixUser,
|
matrixUser: MatrixUser,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
avatarSize: AvatarSize = matrixUser.avatarData.size,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp)
|
||||||
.height(IntrinsicSize.Min),
|
.height(IntrinsicSize.Min),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Avatar(
|
Avatar(
|
||||||
matrixUser.avatarData,
|
matrixUser.avatarData.copy(size = avatarSize),
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp)
|
.padding(start = 12.dp),
|
||||||
.alignByBaseline()
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
) {
|
||||||
// Name
|
// Name
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ package io.element.android.libraries.matrix.ui.media
|
||||||
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
fun AvatarData.toMetadata(): MediaResolver.Meta {
|
fun AvatarData.toMetadata(): MediaResolver.Meta {
|
||||||
return MediaResolver.Meta(url = url, kind = MediaResolver.Kind.Thumbnail(size.value))
|
return MediaResolver.Meta(url = url, kind = MediaResolver.Kind.Thumbnail(size.dp.value.roundToInt()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5451d8fb06cef9a346c92db43fa5994e39f3079b67b99d867a31d9b8a645cb0d
|
oid sha256:0161ce815927b9a7f21060d94a9e98cc67b832c945d7437848b118731aace5c4
|
||||||
size 19198
|
size 19221
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:89ea65099fb4981bbeb24e49afb7400b0e8da79e3b783e198519ba1e970404a8
|
||||||
|
size 8317
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9a8ba207cf61c56b64c6855d04c8a30def0b3daf325adf57edd45b40981ed745
|
||||||
|
size 7733
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b6703f2cba99aa60bdf3ecd380a46846f0e1b95a0b2718ed707348b6bc1713b1
|
||||||
|
size 107508
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:611b1efa9cf46316f96738130f326061595d2648ac7fb059df299e7c95f4e2a8
|
oid sha256:07e26a6494155ad18acc5da1a9151ffb02910185a3d0a516884848d7b045e640
|
||||||
size 18480
|
size 18434
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b8de2f28cf9918a7eddcc1311c34ba0376242a4708724b57689df000b7480524
|
||||||
|
size 8197
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:687133e4729ea42382ac0b76af6ceaf8b20cbc65923412fb97b077d2475c70f7
|
||||||
|
size 7514
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d8e1ca8a115c3e46c7f24fe41dc6c9448ffecc65a05c6be1945b84f35d60a099
|
||||||
|
size 103642
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e89807d72107f1f8337e8de803791249b56bde6c4961805520195ea55563749a
|
oid sha256:5ba2ec897834fa0db2751c2c8a7b4427e278cf4711bed3fecf75937c080d42ba
|
||||||
size 8470
|
size 8534
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6381c125b8eaf6b116218bc4b0efda4866f19514139f4b45604909e462931c1f
|
oid sha256:75923126de5b769aa04de9214af14e3aec23e91b85f5a4c8d57c21025850ec1b
|
||||||
size 8320
|
size 8358
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:73594765454707aa2894d86432639edac0b59adaf554b0fe2fb7fc7e9976c4f8
|
oid sha256:df97ab1093fd2c257f98b16fefeb28cc4a31747a7a9bda0725fa826a26c59906
|
||||||
size 14445
|
size 14299
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5e6ceaf1f7f7d8cd59327d7b88ba2136468de76542e84504a6ef689f0e58d896
|
oid sha256:3b3649fa2543fd138246ac3281f491f2a0a1c9f38c977e57a840cd560038af2f
|
||||||
size 13695
|
size 13782
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3ce4773eed490ebeb3ed46d534181afb90331ba1cbaab8ead7b192739beb29a6
|
oid sha256:38556398b29c5230751f86b35b9e0b233c1ef4a84d1aa37928cc4d10a8062c9b
|
||||||
size 13681
|
size 13567
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c8a4a16c343f295a5270bb746024578dea8f3015ffe35a57b0ed667727d976e2
|
oid sha256:e7a0cf84208247fe5a5a118a8e8133d79b875859a65788139b7c4459c575c5d8
|
||||||
size 13182
|
size 13226
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue