Merge pull request #5493 from element-hq/feature/fga/space_description
feature(space): make sure to handle topic properly
This commit is contained in:
commit
be8455b54d
24 changed files with 239 additions and 57 deletions
|
|
@ -15,4 +15,7 @@ sealed interface SpaceEvents {
|
|||
data object ClearFailures : SpaceEvents
|
||||
data class AcceptInvite(val spaceRoom: SpaceRoom) : SpaceEvents
|
||||
data class DeclineInvite(val spaceRoom: SpaceRoom) : SpaceEvents
|
||||
|
||||
data class ShowTopicViewer(val topic: String) : SpaceEvents
|
||||
data object HideTopicViewer : SpaceEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ class SpacePresenter(
|
|||
val currentSpace by spaceRoomList.currentSpaceFlow.collectAsState()
|
||||
val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap<RoomId, AsyncAction<Unit>>()) }
|
||||
|
||||
var topicViewerState: TopicViewerState by remember { mutableStateOf(TopicViewerState.Hidden) }
|
||||
|
||||
LaunchedEffect(children) {
|
||||
// Remove joined children from the join actions
|
||||
val joinedChildren = children
|
||||
|
|
@ -113,6 +115,8 @@ class SpacePresenter(
|
|||
AcceptDeclineInviteEvents.DeclineInvite(invite = event.spaceRoom.toInviteData(), shouldConfirm = true, blockUser = false)
|
||||
)
|
||||
}
|
||||
SpaceEvents.HideTopicViewer -> topicViewerState = TopicViewerState.Hidden
|
||||
is SpaceEvents.ShowTopicViewer -> topicViewerState = TopicViewerState.Shown(event.topic)
|
||||
}
|
||||
}
|
||||
return SpaceState(
|
||||
|
|
@ -123,6 +127,7 @@ class SpacePresenter(
|
|||
hasMoreToLoad = hasMoreToLoad,
|
||||
joinActions = joinActions.toImmutableMap(),
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
topicViewerState = topicViewerState,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.features.space.impl.root
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -23,6 +24,7 @@ data class SpaceState(
|
|||
val hasMoreToLoad: Boolean,
|
||||
val joinActions: ImmutableMap<RoomId, AsyncAction<Unit>>,
|
||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||
val topicViewerState: TopicViewerState,
|
||||
val eventSink: (SpaceEvents) -> Unit
|
||||
) {
|
||||
fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading
|
||||
|
|
@ -30,3 +32,9 @@ data class SpaceState(
|
|||
it is AsyncAction.Failure
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface TopicViewerState {
|
||||
data object Hidden : TopicViewerState
|
||||
data class Shown(val topic: String) : TopicViewerState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.space.impl.root
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
|
@ -25,51 +26,32 @@ open class SpaceStateProvider : PreviewParameterProvider<SpaceState> {
|
|||
override val values: Sequence<SpaceState>
|
||||
get() = sequenceOf(
|
||||
aSpaceState(),
|
||||
aSpaceState(parentSpace = aParentSpace(joinRule = JoinRule.Public)),
|
||||
aSpaceState(parentSpace = aParentSpace(joinRule = JoinRule.Restricted(persistentListOf()))),
|
||||
aSpaceState(children = aListOfSpaceRooms()),
|
||||
aSpaceState(
|
||||
parentSpace = aSpaceRoom(
|
||||
joinRule = JoinRule.Public
|
||||
)
|
||||
),
|
||||
aSpaceState(
|
||||
parentSpace = aSpaceRoom(
|
||||
joinRule = JoinRule.Restricted(persistentListOf())
|
||||
)
|
||||
),
|
||||
aSpaceState(
|
||||
parentSpace = aSpaceRoom(
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
),
|
||||
hasMoreToLoad = true,
|
||||
),
|
||||
aSpaceState(
|
||||
hasMoreToLoad = true,
|
||||
children = aListOfSpaceRooms(),
|
||||
),
|
||||
aSpaceState(
|
||||
hasMoreToLoad = false,
|
||||
parentSpace = aParentSpace(),
|
||||
children = aListOfSpaceRooms(),
|
||||
joiningRooms = setOf(RoomId("!spaceId0:example.com")),
|
||||
)
|
||||
hasMoreToLoad = false
|
||||
),
|
||||
aSpaceState(
|
||||
topicViewerState = TopicViewerState.Shown(topic = "Space description goes here." + LoremIpsum(20).values.first()),
|
||||
),
|
||||
// Add other states here
|
||||
)
|
||||
}
|
||||
|
||||
fun aSpaceState(
|
||||
parentSpace: SpaceRoom? = aSpaceRoom(
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
roomId = RoomId("!spaceId0:example.com"),
|
||||
),
|
||||
parentSpace: SpaceRoom? = aParentSpace(),
|
||||
children: List<SpaceRoom> = emptyList(),
|
||||
seenSpaceInvites: Set<RoomId> = emptySet(),
|
||||
joiningRooms: Set<RoomId> = emptySet(),
|
||||
joinActions: Map<RoomId, AsyncAction<Unit>> = joiningRooms.associateWith { AsyncAction.Loading },
|
||||
hideInvitesAvatar: Boolean = false,
|
||||
hasMoreToLoad: Boolean = false,
|
||||
hasMoreToLoad: Boolean = true,
|
||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
topicViewerState: TopicViewerState = TopicViewerState.Hidden,
|
||||
eventSink: (SpaceEvents) -> Unit = { },
|
||||
) = SpaceState(
|
||||
currentSpace = parentSpace,
|
||||
|
|
@ -79,9 +61,23 @@ fun aSpaceState(
|
|||
hasMoreToLoad = hasMoreToLoad,
|
||||
joinActions = joinActions.toImmutableMap(),
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
topicViewerState = topicViewerState,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
private fun aParentSpace(
|
||||
joinRule: JoinRule? = null,
|
||||
): SpaceRoom {
|
||||
return aSpaceRoom(
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
joinRule = joinRule,
|
||||
roomId = RoomId("!spaceId0:example.com"),
|
||||
topic = "Space description goes here. " + LoremIpsum(20).values.first(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun aListOfSpaceRooms(): List<SpaceRoom> {
|
||||
return listOf(
|
||||
aSpaceRoom(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.features.space.impl.root
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -33,6 +34,8 @@ import androidx.compose.ui.unit.dp
|
|||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule
|
||||
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||
import io.element.android.libraries.designsystem.components.SimpleModalBottomSheet
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
||||
|
|
@ -61,6 +64,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SpaceView(
|
||||
state: SpaceState,
|
||||
|
|
@ -87,7 +91,10 @@ fun SpaceView(
|
|||
) {
|
||||
SpaceViewContent(
|
||||
state = state,
|
||||
onRoomClick = onRoomClick
|
||||
onRoomClick = onRoomClick,
|
||||
onTopicClick = { topic ->
|
||||
state.eventSink(SpaceEvents.ShowTopicViewer(topic))
|
||||
}
|
||||
)
|
||||
JoinRoomFailureEffect(
|
||||
hasAnyFailure = state.hasAnyFailure,
|
||||
|
|
@ -97,6 +104,14 @@ fun SpaceView(
|
|||
}
|
||||
},
|
||||
)
|
||||
if (state.topicViewerState is TopicViewerState.Shown) {
|
||||
TopicViewerBottomSheet(
|
||||
topicViewerState = state.topicViewerState,
|
||||
onDismiss = {
|
||||
state.eventSink(SpaceEvents.HideTopicViewer)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -120,10 +135,31 @@ private fun JoinRoomFailureEffect(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopicViewerBottomSheet(
|
||||
topicViewerState: TopicViewerState.Shown,
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SimpleModalBottomSheet(
|
||||
title = stringResource(CommonStrings.common_description),
|
||||
onDismiss = onDismiss,
|
||||
modifier = modifier
|
||||
) {
|
||||
ClickableLinkText(
|
||||
text = topicViewerState.topic,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceViewContent(
|
||||
state: SpaceState,
|
||||
onRoomClick: (spaceRoom: SpaceRoom) -> Unit,
|
||||
onTopicClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier.fillMaxSize()) {
|
||||
|
|
@ -134,9 +170,11 @@ private fun SpaceViewContent(
|
|||
avatarData = currentSpace.getAvatarData(AvatarSize.SpaceHeader),
|
||||
name = currentSpace.displayName,
|
||||
topic = currentSpace.topic,
|
||||
topicMaxLines = 2,
|
||||
visibility = currentSpace.visibility,
|
||||
heroes = currentSpace.heroes.toImmutableList(),
|
||||
numberOfMembers = currentSpace.numJoinedMembers,
|
||||
onTopicClick = onTopicClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class SpacePresenterTest {
|
|||
assertThat(state.hasMoreToLoad).isTrue()
|
||||
assertThat(state.joinActions).isEmpty()
|
||||
assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState())
|
||||
assertThat(state.topicViewerState).isEqualTo(TopicViewerState.Hidden)
|
||||
advanceUntilIdle()
|
||||
paginateResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
|
@ -236,6 +237,24 @@ class SpacePresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - topic viewer state`() = runTest {
|
||||
val paginateResult = lambdaRecorder<Result<Unit>> {
|
||||
Result.success(Unit)
|
||||
}
|
||||
val spaceRoomList = FakeSpaceRoomList(paginateResult = paginateResult)
|
||||
val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(state.topicViewerState).isEqualTo(TopicViewerState.Hidden)
|
||||
advanceUntilIdle()
|
||||
state.eventSink(SpaceEvents.ShowTopicViewer("topic"))
|
||||
assertThat(awaitItem().topicViewerState).isEqualTo(TopicViewerState.Shown("topic"))
|
||||
state.eventSink(SpaceEvents.HideTopicViewer)
|
||||
assertThat(awaitItem().topicViewerState).isEqualTo(TopicViewerState.Hidden)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - accept invite is transmitted to acceptDeclineInviteState`() {
|
||||
`invite action is transmitted to acceptDeclineInviteState`(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
|||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
|
|
@ -31,6 +32,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SpaceViewTest {
|
||||
|
|
@ -42,6 +44,7 @@ class SpaceViewTest {
|
|||
ensureCalledOnce {
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
hasMoreToLoad = false,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackClick = it,
|
||||
|
|
@ -58,6 +61,7 @@ class SpaceViewTest {
|
|||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
children = listOf(aSpaceRoom),
|
||||
hasMoreToLoad = false,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onRoomClick = it,
|
||||
|
|
@ -73,6 +77,7 @@ class SpaceViewTest {
|
|||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
children = listOf(aSpaceRoom),
|
||||
hasMoreToLoad = false,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
|
@ -80,12 +85,14 @@ class SpaceViewTest {
|
|||
eventsRecorder.assertSingle(SpaceEvents.Join(aSpaceRoom))
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on accept invite emits the expected Event`() {
|
||||
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED)
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
hasMoreToLoad = false,
|
||||
children = listOf(aSpaceRoom),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
|
|
@ -94,12 +101,14 @@ class SpaceViewTest {
|
|||
eventsRecorder.assertSingle(SpaceEvents.AcceptInvite(aSpaceRoom))
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on decline invite emits the expected Event`() {
|
||||
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED)
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
hasMoreToLoad = false,
|
||||
children = listOf(aSpaceRoom),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
|
|
@ -107,6 +116,21 @@ class SpaceViewTest {
|
|||
rule.clickOn(CommonStrings.action_decline)
|
||||
eventsRecorder.assertSingle(SpaceEvents.DeclineInvite(aSpaceRoom))
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on topic emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
parentSpace = aSpaceRoom(topic = A_ROOM_TOPIC),
|
||||
hasMoreToLoad = false,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
rule.onNodeWithText(A_ROOM_TOPIC).performClick()
|
||||
eventsRecorder.assertSingle(SpaceEvents.ShowTopicViewer(A_ROOM_TOPIC))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceView(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue