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
3c9b322fa8
24 changed files with 239 additions and 57 deletions
|
|
@ -15,4 +15,7 @@ sealed interface SpaceEvents {
|
||||||
data object ClearFailures : SpaceEvents
|
data object ClearFailures : SpaceEvents
|
||||||
data class AcceptInvite(val spaceRoom: SpaceRoom) : SpaceEvents
|
data class AcceptInvite(val spaceRoom: SpaceRoom) : SpaceEvents
|
||||||
data class DeclineInvite(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 currentSpace by spaceRoomList.currentSpaceFlow.collectAsState()
|
||||||
val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap<RoomId, AsyncAction<Unit>>()) }
|
val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap<RoomId, AsyncAction<Unit>>()) }
|
||||||
|
|
||||||
|
var topicViewerState: TopicViewerState by remember { mutableStateOf(TopicViewerState.Hidden) }
|
||||||
|
|
||||||
LaunchedEffect(children) {
|
LaunchedEffect(children) {
|
||||||
// Remove joined children from the join actions
|
// Remove joined children from the join actions
|
||||||
val joinedChildren = children
|
val joinedChildren = children
|
||||||
|
|
@ -113,6 +115,8 @@ class SpacePresenter(
|
||||||
AcceptDeclineInviteEvents.DeclineInvite(invite = event.spaceRoom.toInviteData(), shouldConfirm = true, blockUser = false)
|
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(
|
return SpaceState(
|
||||||
|
|
@ -123,6 +127,7 @@ class SpacePresenter(
|
||||||
hasMoreToLoad = hasMoreToLoad,
|
hasMoreToLoad = hasMoreToLoad,
|
||||||
joinActions = joinActions.toImmutableMap(),
|
joinActions = joinActions.toImmutableMap(),
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
|
topicViewerState = topicViewerState,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.features.space.impl.root
|
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.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -23,6 +24,7 @@ data class SpaceState(
|
||||||
val hasMoreToLoad: Boolean,
|
val hasMoreToLoad: Boolean,
|
||||||
val joinActions: ImmutableMap<RoomId, AsyncAction<Unit>>,
|
val joinActions: ImmutableMap<RoomId, AsyncAction<Unit>>,
|
||||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||||
|
val topicViewerState: TopicViewerState,
|
||||||
val eventSink: (SpaceEvents) -> Unit
|
val eventSink: (SpaceEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading
|
fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading
|
||||||
|
|
@ -30,3 +32,9 @@ data class SpaceState(
|
||||||
it is AsyncAction.Failure
|
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
|
package io.element.android.features.space.impl.root
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
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.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -25,51 +26,32 @@ open class SpaceStateProvider : PreviewParameterProvider<SpaceState> {
|
||||||
override val values: Sequence<SpaceState>
|
override val values: Sequence<SpaceState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aSpaceState(),
|
aSpaceState(),
|
||||||
|
aSpaceState(parentSpace = aParentSpace(joinRule = JoinRule.Public)),
|
||||||
|
aSpaceState(parentSpace = aParentSpace(joinRule = JoinRule.Restricted(persistentListOf()))),
|
||||||
|
aSpaceState(children = aListOfSpaceRooms()),
|
||||||
aSpaceState(
|
aSpaceState(
|
||||||
parentSpace = aSpaceRoom(
|
parentSpace = aParentSpace(),
|
||||||
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,
|
|
||||||
children = aListOfSpaceRooms(),
|
children = aListOfSpaceRooms(),
|
||||||
joiningRooms = setOf(RoomId("!spaceId0:example.com")),
|
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
|
// Add other states here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aSpaceState(
|
fun aSpaceState(
|
||||||
parentSpace: SpaceRoom? = aSpaceRoom(
|
parentSpace: SpaceRoom? = aParentSpace(),
|
||||||
numJoinedMembers = 5,
|
|
||||||
childrenCount = 10,
|
|
||||||
worldReadable = true,
|
|
||||||
roomId = RoomId("!spaceId0:example.com"),
|
|
||||||
),
|
|
||||||
children: List<SpaceRoom> = emptyList(),
|
children: List<SpaceRoom> = emptyList(),
|
||||||
seenSpaceInvites: Set<RoomId> = emptySet(),
|
seenSpaceInvites: Set<RoomId> = emptySet(),
|
||||||
joiningRooms: Set<RoomId> = emptySet(),
|
joiningRooms: Set<RoomId> = emptySet(),
|
||||||
joinActions: Map<RoomId, AsyncAction<Unit>> = joiningRooms.associateWith { AsyncAction.Loading },
|
joinActions: Map<RoomId, AsyncAction<Unit>> = joiningRooms.associateWith { AsyncAction.Loading },
|
||||||
hideInvitesAvatar: Boolean = false,
|
hideInvitesAvatar: Boolean = false,
|
||||||
hasMoreToLoad: Boolean = false,
|
hasMoreToLoad: Boolean = true,
|
||||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||||
|
topicViewerState: TopicViewerState = TopicViewerState.Hidden,
|
||||||
eventSink: (SpaceEvents) -> Unit = { },
|
eventSink: (SpaceEvents) -> Unit = { },
|
||||||
) = SpaceState(
|
) = SpaceState(
|
||||||
currentSpace = parentSpace,
|
currentSpace = parentSpace,
|
||||||
|
|
@ -79,9 +61,23 @@ fun aSpaceState(
|
||||||
hasMoreToLoad = hasMoreToLoad,
|
hasMoreToLoad = hasMoreToLoad,
|
||||||
joinActions = joinActions.toImmutableMap(),
|
joinActions = joinActions.toImmutableMap(),
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
|
topicViewerState = topicViewerState,
|
||||||
eventSink = eventSink,
|
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> {
|
private fun aListOfSpaceRooms(): List<SpaceRoom> {
|
||||||
return listOf(
|
return listOf(
|
||||||
aSpaceRoom(
|
aSpaceRoom(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.features.space.impl.root
|
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.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule
|
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.AsyncIndicator
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||||
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
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.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SpaceView(
|
fun SpaceView(
|
||||||
state: SpaceState,
|
state: SpaceState,
|
||||||
|
|
@ -87,7 +91,10 @@ fun SpaceView(
|
||||||
) {
|
) {
|
||||||
SpaceViewContent(
|
SpaceViewContent(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomClick = onRoomClick
|
onRoomClick = onRoomClick,
|
||||||
|
onTopicClick = { topic ->
|
||||||
|
state.eventSink(SpaceEvents.ShowTopicViewer(topic))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
JoinRoomFailureEffect(
|
JoinRoomFailureEffect(
|
||||||
hasAnyFailure = state.hasAnyFailure,
|
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
|
@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
|
@Composable
|
||||||
private fun SpaceViewContent(
|
private fun SpaceViewContent(
|
||||||
state: SpaceState,
|
state: SpaceState,
|
||||||
onRoomClick: (spaceRoom: SpaceRoom) -> Unit,
|
onRoomClick: (spaceRoom: SpaceRoom) -> Unit,
|
||||||
|
onTopicClick: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LazyColumn(modifier.fillMaxSize()) {
|
LazyColumn(modifier.fillMaxSize()) {
|
||||||
|
|
@ -134,9 +170,11 @@ private fun SpaceViewContent(
|
||||||
avatarData = currentSpace.getAvatarData(AvatarSize.SpaceHeader),
|
avatarData = currentSpace.getAvatarData(AvatarSize.SpaceHeader),
|
||||||
name = currentSpace.displayName,
|
name = currentSpace.displayName,
|
||||||
topic = currentSpace.topic,
|
topic = currentSpace.topic,
|
||||||
|
topicMaxLines = 2,
|
||||||
visibility = currentSpace.visibility,
|
visibility = currentSpace.visibility,
|
||||||
heroes = currentSpace.heroes.toImmutableList(),
|
heroes = currentSpace.heroes.toImmutableList(),
|
||||||
numberOfMembers = currentSpace.numJoinedMembers,
|
numberOfMembers = currentSpace.numJoinedMembers,
|
||||||
|
onTopicClick = onTopicClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ class SpacePresenterTest {
|
||||||
assertThat(state.hasMoreToLoad).isTrue()
|
assertThat(state.hasMoreToLoad).isTrue()
|
||||||
assertThat(state.joinActions).isEmpty()
|
assertThat(state.joinActions).isEmpty()
|
||||||
assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState())
|
assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState())
|
||||||
|
assertThat(state.topicViewerState).isEqualTo(TopicViewerState.Hidden)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
paginateResult.assertions().isCalledOnce()
|
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
|
@Test
|
||||||
fun `present - accept invite is transmitted to acceptDeclineInviteState`() {
|
fun `present - accept invite is transmitted to acceptDeclineInviteState`() {
|
||||||
`invite action 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.api.spaces.SpaceRoom
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
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_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
|
||||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -31,6 +32,7 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class SpaceViewTest {
|
class SpaceViewTest {
|
||||||
|
|
@ -42,6 +44,7 @@ class SpaceViewTest {
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setSpaceView(
|
rule.setSpaceView(
|
||||||
aSpaceState(
|
aSpaceState(
|
||||||
|
hasMoreToLoad = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = it,
|
onBackClick = it,
|
||||||
|
|
@ -58,6 +61,7 @@ class SpaceViewTest {
|
||||||
rule.setSpaceView(
|
rule.setSpaceView(
|
||||||
aSpaceState(
|
aSpaceState(
|
||||||
children = listOf(aSpaceRoom),
|
children = listOf(aSpaceRoom),
|
||||||
|
hasMoreToLoad = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onRoomClick = it,
|
onRoomClick = it,
|
||||||
|
|
@ -73,6 +77,7 @@ class SpaceViewTest {
|
||||||
rule.setSpaceView(
|
rule.setSpaceView(
|
||||||
aSpaceState(
|
aSpaceState(
|
||||||
children = listOf(aSpaceRoom),
|
children = listOf(aSpaceRoom),
|
||||||
|
hasMoreToLoad = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -80,12 +85,14 @@ class SpaceViewTest {
|
||||||
eventsRecorder.assertSingle(SpaceEvents.Join(aSpaceRoom))
|
eventsRecorder.assertSingle(SpaceEvents.Join(aSpaceRoom))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on accept invite emits the expected Event`() {
|
fun `clicking on accept invite emits the expected Event`() {
|
||||||
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED)
|
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED)
|
||||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||||
rule.setSpaceView(
|
rule.setSpaceView(
|
||||||
aSpaceState(
|
aSpaceState(
|
||||||
|
hasMoreToLoad = false,
|
||||||
children = listOf(aSpaceRoom),
|
children = listOf(aSpaceRoom),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
|
|
@ -94,12 +101,14 @@ class SpaceViewTest {
|
||||||
eventsRecorder.assertSingle(SpaceEvents.AcceptInvite(aSpaceRoom))
|
eventsRecorder.assertSingle(SpaceEvents.AcceptInvite(aSpaceRoom))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline invite emits the expected Event`() {
|
fun `clicking on decline invite emits the expected Event`() {
|
||||||
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED)
|
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED)
|
||||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||||
rule.setSpaceView(
|
rule.setSpaceView(
|
||||||
aSpaceState(
|
aSpaceState(
|
||||||
|
hasMoreToLoad = false,
|
||||||
children = listOf(aSpaceRoom),
|
children = listOf(aSpaceRoom),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
|
|
@ -107,6 +116,21 @@ class SpaceViewTest {
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
rule.clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertSingle(SpaceEvents.DeclineInvite(aSpaceRoom))
|
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(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceView(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
|
@ -51,6 +52,7 @@ fun ClickableLinkText(
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: () -> Unit = {},
|
onLongClick: () -> Unit = {},
|
||||||
style: TextStyle = LocalTextStyle.current,
|
style: TextStyle = LocalTextStyle.current,
|
||||||
|
color: Color = Color.Unspecified,
|
||||||
inlineContent: ImmutableMap<String, InlineTextContent> = persistentMapOf(),
|
inlineContent: ImmutableMap<String, InlineTextContent> = persistentMapOf(),
|
||||||
) {
|
) {
|
||||||
ClickableLinkText(
|
ClickableLinkText(
|
||||||
|
|
@ -62,6 +64,7 @@ fun ClickableLinkText(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
style = style,
|
style = style,
|
||||||
|
color = color,
|
||||||
inlineContent = inlineContent,
|
inlineContent = inlineContent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +79,7 @@ fun ClickableLinkText(
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: () -> Unit = {},
|
onLongClick: () -> Unit = {},
|
||||||
style: TextStyle = LocalTextStyle.current,
|
style: TextStyle = LocalTextStyle.current,
|
||||||
|
color: Color = Color.Unspecified,
|
||||||
inlineContent: ImmutableMap<String, InlineTextContent> = persistentMapOf(),
|
inlineContent: ImmutableMap<String, InlineTextContent> = persistentMapOf(),
|
||||||
) {
|
) {
|
||||||
@Suppress("NAME_SHADOWING")
|
@Suppress("NAME_SHADOWING")
|
||||||
|
|
@ -126,6 +130,7 @@ fun ClickableLinkText(
|
||||||
text = annotatedString,
|
text = annotatedString,
|
||||||
modifier = modifier.then(pressIndicator),
|
modifier = modifier.then(pressIndicator),
|
||||||
style = style,
|
style = style,
|
||||||
|
color = color,
|
||||||
onTextLayout = {
|
onTextLayout = {
|
||||||
layoutResult.value = it
|
layoutResult.value = it
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SimpleModalBottomSheet(
|
||||||
|
title: String,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
modifier = modifier,
|
||||||
|
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun SimpleModalBottomSheetPreview() = ElementPreview {
|
||||||
|
SimpleModalBottomSheet(title = "A title", onDismiss = {}) {
|
||||||
|
Text(
|
||||||
|
text = LoremIpsum(20).values.first(),
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.ui.components
|
package io.element.android.libraries.matrix.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -43,6 +44,7 @@ fun SpaceHeaderView(
|
||||||
numberOfMembers: Int,
|
numberOfMembers: Int,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
topicMaxLines: Int = Int.MAX_VALUE,
|
topicMaxLines: Int = Int.MAX_VALUE,
|
||||||
|
onTopicClick: ((String) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
RoomPreviewOrganism(
|
RoomPreviewOrganism(
|
||||||
modifier = modifier.padding(24.dp),
|
modifier = modifier.padding(24.dp),
|
||||||
|
|
@ -68,7 +70,16 @@ fun SpaceHeaderView(
|
||||||
description = if (topic.isNullOrBlank()) {
|
description = if (topic.isNullOrBlank()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
{ RoomPreviewDescriptionAtom(description = topic, maxLines = topicMaxLines) }
|
{
|
||||||
|
RoomPreviewDescriptionAtom(
|
||||||
|
description = topic,
|
||||||
|
maxLines = topicMaxLines,
|
||||||
|
modifier = Modifier.clickable(
|
||||||
|
enabled = onTopicClick != null,
|
||||||
|
onClick = { onTopicClick?.invoke(topic) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
memberCount = {
|
memberCount = {
|
||||||
SpaceMembersView(
|
SpaceMembersView(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1251095a4bb43dc672c23b3b3780288f69a4ce26cd5ee249179e94b8a067d3c1
|
oid sha256:b9c9e6833668344ddde682f0dde551af1f862647c7a3d749a12813d711ef2ee8
|
||||||
size 19183
|
size 34609
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:546ea17f75c5f80282dfa9a021a68eb71e1436b371ff39b399ebe6f7aa5c5121
|
oid sha256:1bdd937cb84e29f1f05c1d718b4350694c65d0c75c0e599effd6467793e331a6
|
||||||
size 19216
|
size 34771
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7f58e4f6aa72ef41dd5af1e0d309e3909ea1a7d24875135d0d04dd84a3341be8
|
oid sha256:9d3ee4db00d7432da4808aeba2cd0a5409d0aefecf66e76366cf60f6096cd443
|
||||||
size 19572
|
size 35073
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:79d880a890173c6af166139e29b115d2bf658e75f18868137616af05f4db586d
|
oid sha256:4b095899fe3ff11e393923a8101c986eb3603778f1a4ded135c3619f6d83771b
|
||||||
size 20647
|
size 64666
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:db775b9af1c424150eb5682b3f30df88728a4d28ae598aba1c1a22bf44e5d6b0
|
oid sha256:91a3e568c74bd2b7a9d99c481504025290866d0335e38cacbe7340be7a4b61cc
|
||||||
size 52677
|
size 65360
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:bd2d09be9476f3ff9cb3710e5bbafc0f493f6afc4f77725cb67e23a7fc6f6e13
|
oid sha256:f19d84a37f3c570fb53ae28b185cbe73b4fe94f69ba57e6935ac8a078f20afd2
|
||||||
size 51864
|
size 59751
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:fb27bf8dbb2323da9c7ff2f3a77ed028e76d5b3ec8e2e95991a12feceb074aae
|
oid sha256:008f4e11e7bd9cc91e46381cb0509329e2299de22b7b485e3036e458033f9bb9
|
||||||
size 18816
|
size 34036
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e354f7346a10f75679427774ff96e9b13129b76c8b98944ad4e89b482b110a51
|
oid sha256:2270b4da2b1e55027c253422c04f31f6884cf8accf273ce08efba95fec40d599
|
||||||
size 18868
|
size 34177
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d80cd45594d19ecf6e7bf103bb374dd86013bd05e7c10cd3df31e66c2b2cda01
|
oid sha256:39afa43d6b9b8ba7d5ff83ba5a956fdfbb62510c57d34c81d65dd558e0501269
|
||||||
size 19223
|
size 34508
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c9f4963aa89b1f9811793f50033b004cb5659f7c91d6f16d5204e932c29b9d0a
|
oid sha256:e6396f10b0bfb904eec898b0085af56f802c0f5a5e50af36313a31fa20a1dd0c
|
||||||
size 20271
|
size 63449
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6701688f5a1b8e59f42bc6e429a561a120576c5d119a3df123d4ee5fc1acb887
|
oid sha256:c9425dfd394a554bdbf43c021ec1e26d0e7f273f4ba89df670170c73641b51a7
|
||||||
size 51384
|
size 64049
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6b51e599bbad12e70b116680b00cdd376c921769dbc7b2f918ba9a0919a88847
|
oid sha256:93ed6d05c199a886a53ecffb7a56a586f4fba874fbf18afb4975782eb2d10744
|
||||||
size 50509
|
size 57977
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8d27d23f9e54848b07eab23fb4b3a865a1e4036255c64fcf6cb944466e77fb4b
|
||||||
|
size 27306
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2f2e4d519448d1a5b0c67d7b808292f60ad6391a314b9946cff89aef8fc29313
|
||||||
|
size 25914
|
||||||
Loading…
Add table
Add a link
Reference in a new issue