Merge pull request #2695 from element-hq/feature/fga/room_navigation
Room navigation
This commit is contained in:
commit
1dcdc713a4
133 changed files with 2824 additions and 934 deletions
|
|
@ -25,6 +25,7 @@ import com.bumble.appyx.core.plugin.plugins
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -35,9 +36,15 @@ class RoomDirectoryNode @AssistedInject constructor(
|
|||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: RoomDirectoryPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
private fun onResultClicked(roomDescription: RoomDescription) {
|
||||
plugins<RoomDirectoryEntryPoint.Callback>().forEach {
|
||||
it.onResultClicked(roomDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRoomJoined(roomId: RoomId) {
|
||||
plugins<RoomDirectoryEntryPoint.Callback>().forEach {
|
||||
it.onOpenRoom(roomId)
|
||||
it.onRoomJoined(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +54,7 @@ class RoomDirectoryNode @AssistedInject constructor(
|
|||
RoomDirectoryView(
|
||||
state = state,
|
||||
onRoomJoined = ::onRoomJoined,
|
||||
onResultClicked = ::onResultClicked,
|
||||
onBackPressed = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ package io.element.android.features.roomdirectory.impl.root
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -70,26 +68,20 @@ fun aRoomDescriptionList(): ImmutableList<RoomDescription> {
|
|||
RoomDescription(
|
||||
roomId = RoomId("!exa:matrix.org"),
|
||||
name = "Element X Android",
|
||||
description = "Element X is a secure, private and decentralized messenger.",
|
||||
avatarData = AvatarData(
|
||||
id = "!exa:matrix.org",
|
||||
name = "Element X Android",
|
||||
url = null,
|
||||
size = AvatarSize.RoomDirectoryItem
|
||||
),
|
||||
canBeJoined = true,
|
||||
topic = "Element X is a secure, private and decentralized messenger.",
|
||||
alias = "#element-x-android:matrix.org",
|
||||
avatarUrl = null,
|
||||
joinRule = RoomDescription.JoinRule.PUBLIC,
|
||||
numberOfMembers = 2765,
|
||||
),
|
||||
RoomDescription(
|
||||
roomId = RoomId("!exi:matrix.org"),
|
||||
name = "Element X iOS",
|
||||
description = "Element X is a secure, private and decentralized messenger.",
|
||||
avatarData = AvatarData(
|
||||
id = "!exi:matrix.org",
|
||||
name = "Element X iOS",
|
||||
url = null,
|
||||
size = AvatarSize.RoomDirectoryItem
|
||||
),
|
||||
canBeJoined = false,
|
||||
topic = "Element X is a secure, private and decentralized messenger.",
|
||||
alias = "#element-x-ios:matrix.org",
|
||||
avatarUrl = null,
|
||||
joinRule = RoomDescription.JoinRule.UNKNOWN,
|
||||
numberOfMembers = 356,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
|||
import io.element.android.features.roomdirectory.impl.R
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -70,12 +71,13 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
@Composable
|
||||
fun RoomDirectoryView(
|
||||
state: RoomDirectoryState,
|
||||
onResultClicked: (RoomDescription) -> Unit,
|
||||
onRoomJoined: (RoomId) -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun joinRoom(roomId: RoomId) {
|
||||
state.eventSink(RoomDirectoryEvents.JoinRoom(roomId))
|
||||
fun joinRoom(roomDescription: RoomDescription) {
|
||||
state.eventSink(RoomDirectoryEvents.JoinRoom(roomDescription.roomId))
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
|
|
@ -86,10 +88,11 @@ fun RoomDirectoryView(
|
|||
content = { padding ->
|
||||
RoomDirectoryContent(
|
||||
state = state,
|
||||
onResultClicked = ::joinRoom,
|
||||
onResultClicked = onResultClicked,
|
||||
onJoinClicked = ::joinRoom,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -128,7 +131,8 @@ private fun RoomDirectoryTopBar(
|
|||
@Composable
|
||||
private fun RoomDirectoryContent(
|
||||
state: RoomDirectoryState,
|
||||
onResultClicked: (RoomId) -> Unit,
|
||||
onResultClicked: (RoomDescription) -> Unit,
|
||||
onJoinClicked: (RoomDescription) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -143,6 +147,7 @@ private fun RoomDirectoryContent(
|
|||
displayLoadMoreIndicator = state.displayLoadMoreIndicator,
|
||||
displayEmptyState = state.displayEmptyState,
|
||||
onResultClicked = onResultClicked,
|
||||
onJoinClicked = onJoinClicked,
|
||||
onReachedLoadMore = { state.eventSink(RoomDirectoryEvents.LoadMore) },
|
||||
)
|
||||
}
|
||||
|
|
@ -153,7 +158,8 @@ private fun RoomDirectoryRoomList(
|
|||
roomDescriptions: ImmutableList<RoomDescription>,
|
||||
displayLoadMoreIndicator: Boolean,
|
||||
displayEmptyState: Boolean,
|
||||
onResultClicked: (RoomId) -> Unit,
|
||||
onResultClicked: (RoomDescription) -> Unit,
|
||||
onJoinClicked: (RoomDescription) -> Unit,
|
||||
onReachedLoadMore: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -161,7 +167,12 @@ private fun RoomDirectoryRoomList(
|
|||
items(roomDescriptions) { roomDescription ->
|
||||
RoomDirectoryRoomRow(
|
||||
roomDescription = roomDescription,
|
||||
onClick = onResultClicked,
|
||||
onClick = {
|
||||
onResultClicked(roomDescription)
|
||||
},
|
||||
onJoinClick = {
|
||||
onJoinClicked(roomDescription)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (displayEmptyState) {
|
||||
|
|
@ -188,10 +199,10 @@ private fun RoomDirectoryRoomList(
|
|||
@Composable
|
||||
private fun LoadMoreIndicator(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(24.dp),
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
|
|
@ -256,53 +267,54 @@ private fun SearchTextField(
|
|||
@Composable
|
||||
private fun RoomDirectoryRoomRow(
|
||||
roomDescription: RoomDescription,
|
||||
onClick: (RoomId) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onJoinClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = roomDescription.canBeJoined) {
|
||||
onClick(roomDescription.roomId)
|
||||
}
|
||||
.padding(
|
||||
top = 12.dp,
|
||||
bottom = 12.dp,
|
||||
start = 16.dp,
|
||||
)
|
||||
.height(IntrinsicSize.Min),
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(
|
||||
top = 12.dp,
|
||||
bottom = 12.dp,
|
||||
start = 16.dp,
|
||||
)
|
||||
.height(IntrinsicSize.Min),
|
||||
) {
|
||||
Avatar(
|
||||
avatarData = roomDescription.avatarData,
|
||||
avatarData = roomDescription.avatarData(AvatarSize.RoomDirectoryItem),
|
||||
modifier = Modifier.align(Alignment.CenterVertically)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 16.dp)
|
||||
.weight(1f)
|
||||
.padding(start = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = roomDescription.name,
|
||||
text = roomDescription.computedName,
|
||||
maxLines = 1,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = roomDescription.description,
|
||||
text = roomDescription.computedDescription,
|
||||
maxLines = 1,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
if (roomDescription.canBeJoined) {
|
||||
if (roomDescription.canJoinOrKnock) {
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.action_join),
|
||||
color = ElementTheme.colors.textSuccessPrimary,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(start = 4.dp, end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.clickable(onClick = onJoinClick)
|
||||
.padding(start = 4.dp, end = 12.dp)
|
||||
.testTag(TestTags.callToAction.value)
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.width(24.dp))
|
||||
|
|
@ -315,6 +327,7 @@ private fun RoomDirectoryRoomRow(
|
|||
internal fun RoomDirectoryViewPreview(@PreviewParameter(RoomDirectoryStateProvider::class) state: RoomDirectoryState) = ElementPreview {
|
||||
RoomDirectoryView(
|
||||
state = state,
|
||||
onResultClicked = {},
|
||||
onRoomJoined = {},
|
||||
onBackPressed = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,37 +17,20 @@
|
|||
package io.element.android.features.roomdirectory.impl.root.model
|
||||
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription as MatrixRoomDescription
|
||||
|
||||
fun MatrixRoomDescription.toFeatureModel(): RoomDescription {
|
||||
fun name(): String {
|
||||
return name ?: alias ?: roomId.value
|
||||
}
|
||||
|
||||
fun description(): String {
|
||||
val topic = topic
|
||||
val alias = alias
|
||||
val name = name
|
||||
return when {
|
||||
topic != null -> topic
|
||||
name != null && alias != null -> alias
|
||||
name == null && alias == null -> ""
|
||||
else -> roomId.value
|
||||
}
|
||||
}
|
||||
|
||||
return RoomDescription(
|
||||
roomId = roomId,
|
||||
name = name(),
|
||||
description = description(),
|
||||
avatarData = AvatarData(
|
||||
id = roomId.value,
|
||||
name = name,
|
||||
url = avatarUrl,
|
||||
size = AvatarSize.RoomDirectoryItem,
|
||||
),
|
||||
canBeJoined = joinRule == MatrixRoomDescription.JoinRule.PUBLIC,
|
||||
name = name,
|
||||
alias = alias,
|
||||
topic = topic,
|
||||
avatarUrl = avatarUrl,
|
||||
numberOfMembers = numberOfMembers,
|
||||
joinRule = when (joinRule) {
|
||||
MatrixRoomDescription.JoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC
|
||||
MatrixRoomDescription.JoinRule.KNOCK -> RoomDescription.JoinRule.KNOCK
|
||||
MatrixRoomDescription.JoinRule.UNKNOWN -> RoomDescription.JoinRule.UNKNOWN
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@ package io.element.android.features.roomdirectory.impl.root
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithTag
|
||||
import androidx.compose.ui.test.onFirst
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
|
|
@ -55,7 +58,24 @@ class RoomDirectoryViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on room item emits the expected Event`() {
|
||||
fun `clicking on room item then onResultClicked lambda is called once`() {
|
||||
val eventsRecorder = EventsRecorder<RoomDirectoryEvents>()
|
||||
val state = aRoomDirectoryState(
|
||||
roomDescriptions = aRoomDescriptionList(),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
val clickedRoom = state.roomDescriptions.first()
|
||||
ensureCalledOnceWithParam(clickedRoom) { callback ->
|
||||
rule.setRoomDirectoryView(
|
||||
state = state,
|
||||
onResultClicked = callback,
|
||||
)
|
||||
rule.onNodeWithText(clickedRoom.computedName).performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on room item join cta emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomDirectoryEvents>()
|
||||
val state = aRoomDirectoryState(
|
||||
roomDescriptions = aRoomDescriptionList(),
|
||||
|
|
@ -63,7 +83,7 @@ class RoomDirectoryViewTest {
|
|||
)
|
||||
rule.setRoomDirectoryView(state = state)
|
||||
val clickedRoom = state.roomDescriptions.first()
|
||||
rule.onNodeWithText(clickedRoom.name).performClick()
|
||||
rule.onAllNodesWithTag(TestTags.callToAction.value).onFirst().performClick()
|
||||
eventsRecorder.assertSingle(RoomDirectoryEvents.JoinRoom(clickedRoom.roomId))
|
||||
}
|
||||
|
||||
|
|
@ -100,11 +120,13 @@ class RoomDirectoryViewTest {
|
|||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomDirectoryView(
|
||||
state: RoomDirectoryState,
|
||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||
onResultClicked: (RoomDescription) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onRoomJoined: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
) {
|
||||
setContent {
|
||||
RoomDirectoryView(
|
||||
state = state,
|
||||
onResultClicked = onResultClicked,
|
||||
onRoomJoined = onRoomJoined,
|
||||
onBackPressed = onBackPressed,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue