Browse Source

Merge pull request #211 from vector-im/feature/fre/start_chat_search_matrixid

[Start chat] Show a single result when searching for a matrixId
test/jme/compound-poc
Florian Renaud 2 years ago committed by GitHub
parent
commit
62a65f7507
  1. 1
      changelog.d/211.wip
  2. 5
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt
  3. 55
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt
  4. 9
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt
  5. 32
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt
  6. 72
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt
  7. 44
      features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt
  8. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt
  9. 15
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt
  10. 11
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt
  11. 11
      libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt
  12. 3
      libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt
  13. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
  14. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
  15. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
  16. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
  17. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
  18. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
  19. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
  20. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
  21. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DockedSearchBarDarkPreview_0_null,NEXUS_5,1.0,en].png
  22. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DockedSearchBarLightPreview_0_null,NEXUS_5,1.0,en].png
  23. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowDarkPreview_0_null_0,NEXUS_5,1.0,en].png
  24. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowDarkPreview_0_null_1,NEXUS_5,1.0,en].png
  25. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowLightPreview_0_null_0,NEXUS_5,1.0,en].png
  26. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowLightPreview_0_null_1,NEXUS_5,1.0,en].png

1
changelog.d/211.wip

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

5
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt

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

55
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt

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

9
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt

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

32
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt

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

72
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt

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

44
features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt

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

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt

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

15
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt

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

11
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DockedSearchBar.kt → libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt

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

11
libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt

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

3
libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt

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

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_2,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_3,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DockedSearchBarDarkPreview_0_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DockedSearchBarLightPreview_0_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowDarkPreview_0_null_0,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowDarkPreview_0_null_1,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowLightPreview_0_null_0,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.matrix.ui.components_null_DefaultGroup_MatrixUserRowLightPreview_0_null_1,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save