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:
Florian Renaud 2023-03-21 13:58:24 +01:00 committed by GitHub
commit 02f2ace1e4
26 changed files with 246 additions and 64 deletions

1
changelog.d/211.wip Normal file
View file

@ -0,0 +1 @@
[Create and join rooms] Show a single result when searching for a matrixId

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = {},

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89ea65099fb4981bbeb24e49afb7400b0e8da79e3b783e198519ba1e970404a8
size 8317

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9a8ba207cf61c56b64c6855d04c8a30def0b3daf325adf57edd45b40981ed745
size 7733

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6703f2cba99aa60bdf3ecd380a46846f0e1b95a0b2718ed707348b6bc1713b1
size 107508

View file

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

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b8de2f28cf9918a7eddcc1311c34ba0376242a4708724b57689df000b7480524
size 8197

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:687133e4729ea42382ac0b76af6ceaf8b20cbc65923412fb97b077d2475c70f7
size 7514

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8e1ca8a115c3e46c7f24fe41dc6c9448ffecc65a05c6be1945b84f35d60a099
size 103642

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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