Merge pull request #2115 from element-hq/feature/bma/reactionsPowerLevel
Reactions power level
This commit is contained in:
commit
e38799c4db
22 changed files with 173 additions and 102 deletions
1
changelog.d/2093.bugfix
Normal file
1
changelog.d/2093.bugfix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Disable ability to send reaction if the user does not have the permission to.
|
||||
|
|
@ -139,6 +139,7 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
|
||||
val userHasPermissionToRedact by room.canRedactAsState(updateKey = syncUpdateFlow.value)
|
||||
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value)
|
||||
val roomName: Async<String> by remember {
|
||||
derivedStateOf { roomInfo?.name?.let { Async.Success(it) } ?: Async.Uninitialized }
|
||||
}
|
||||
|
|
@ -219,6 +220,7 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
roomAvatar = roomAvatar,
|
||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
||||
userHasPermissionToRedact = userHasPermissionToRedact,
|
||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
||||
composerState = composerState,
|
||||
voiceMessageComposerState = voiceMessageComposerState,
|
||||
timelineState = timelineState,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ data class MessagesState(
|
|||
val roomAvatar: Async<AvatarData>,
|
||||
val userHasPermissionToSendMessage: Boolean,
|
||||
val userHasPermissionToRedact: Boolean,
|
||||
val userHasPermissionToSendReaction: Boolean,
|
||||
val composerState: MessageComposerState,
|
||||
val voiceMessageComposerState: VoiceMessageComposerState,
|
||||
val timelineState: TimelineState,
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ fun aMessagesState() = MessagesState(
|
|||
roomAvatar = Async.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
|
||||
userHasPermissionToSendMessage = true,
|
||||
userHasPermissionToRedact = false,
|
||||
userHasPermissionToSendReaction = true,
|
||||
composerState = aMessageComposerState().copy(
|
||||
richTextEditorState = RichTextEditorState("Hello", initialFocus = true),
|
||||
isFullScreen = false,
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ fun MessagesView(
|
|||
event = event,
|
||||
canRedact = state.userHasPermissionToRedact,
|
||||
canSendMessage = state.userHasPermissionToSendMessage,
|
||||
canSendReaction = state.userHasPermissionToSendReaction,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,5 +24,6 @@ sealed interface ActionListEvents {
|
|||
val event: TimelineItem.Event,
|
||||
val canRedact: Boolean,
|
||||
val canSendMessage: Boolean,
|
||||
val canSendReaction: Boolean,
|
||||
) : ActionListEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package io.element.android.features.messages.impl.actionlist
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -53,13 +52,6 @@ class ActionListPresenter @Inject constructor(
|
|||
|
||||
val isDeveloperModeEnabled by preferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false)
|
||||
|
||||
val displayEmojiReactions by remember {
|
||||
derivedStateOf {
|
||||
val event = (target.value as? ActionListState.Target.Success)?.event
|
||||
event?.isRemote == true && event.content.canReact()
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvents(event: ActionListEvents) {
|
||||
when (event) {
|
||||
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
||||
|
|
@ -67,6 +59,7 @@ class ActionListPresenter @Inject constructor(
|
|||
timelineItem = event.event,
|
||||
userCanRedact = event.canRedact,
|
||||
userCanSendMessage = event.canSendMessage,
|
||||
userCanSendReaction = event.canSendReaction,
|
||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||
target = target,
|
||||
)
|
||||
|
|
@ -75,7 +68,6 @@ class ActionListPresenter @Inject constructor(
|
|||
|
||||
return ActionListState(
|
||||
target = target.value,
|
||||
displayEmojiReactions = displayEmojiReactions,
|
||||
eventSink = { handleEvents(it) }
|
||||
)
|
||||
}
|
||||
|
|
@ -84,6 +76,7 @@ class ActionListPresenter @Inject constructor(
|
|||
timelineItem: TimelineItem.Event,
|
||||
userCanRedact: Boolean,
|
||||
userCanSendMessage: Boolean,
|
||||
userCanSendReaction: Boolean,
|
||||
isDeveloperModeEnabled: Boolean,
|
||||
target: MutableState<ActionListState.Target>
|
||||
) = launch {
|
||||
|
|
@ -178,8 +171,15 @@ class ActionListPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (actions.isNotEmpty()) {
|
||||
target.value = ActionListState.Target.Success(timelineItem, actions.toImmutableList())
|
||||
val displayEmojiReactions = userCanSendReaction &&
|
||||
timelineItem.isRemote &&
|
||||
timelineItem.content.canReact()
|
||||
if (actions.isNotEmpty() || displayEmojiReactions) {
|
||||
target.value = ActionListState.Target.Success(
|
||||
event = timelineItem,
|
||||
displayEmojiReactions = displayEmojiReactions,
|
||||
actions = actions.toImmutableList()
|
||||
)
|
||||
} else {
|
||||
target.value = ActionListState.Target.None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
@Immutable
|
||||
data class ActionListState(
|
||||
val target: Target,
|
||||
val displayEmojiReactions: Boolean,
|
||||
val eventSink: (ActionListEvents) -> Unit,
|
||||
) {
|
||||
sealed interface Target {
|
||||
|
|
@ -32,6 +31,7 @@ data class ActionListState(
|
|||
data class Loading(val event: TimelineItem.Event) : Target
|
||||
data class Success(
|
||||
val event: TimelineItem.Event,
|
||||
val displayEmojiReactions: Boolean,
|
||||
val actions: ImmutableList<TimelineItemAction>,
|
||||
) : Target
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent().copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
|
@ -50,6 +51,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent(content = aTimelineItemImageContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
|
@ -58,6 +60,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent(content = aTimelineItemVideoContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
|
@ -66,6 +69,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent(content = aTimelineItemFileContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
|
@ -74,6 +78,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent(content = aTimelineItemAudioContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
|
@ -82,6 +87,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent(content = aTimelineItemVoiceContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
|
@ -90,6 +96,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent(content = aTimelineItemLocationContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
|
@ -98,18 +105,18 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
event = aTimelineItemEvent(content = aTimelineItemLocationContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = false,
|
||||
actions = aTimelineItemActionList(),
|
||||
),
|
||||
displayEmojiReactions = false,
|
||||
),
|
||||
anActionListState().copy(
|
||||
target = ActionListState.Target.Success(
|
||||
event = aTimelineItemEvent(content = aTimelineItemPollContent()).copy(
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = false,
|
||||
actions = aTimelineItemPollActionList(),
|
||||
),
|
||||
displayEmojiReactions = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -117,7 +124,6 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
|
||||
fun anActionListState() = ActionListState(
|
||||
target = ActionListState.Target.None,
|
||||
displayEmojiReactions = true,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ private fun SheetContent(
|
|||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
if (state.displayEmojiReactions) {
|
||||
if (target.displayEmojiReactions) {
|
||||
item {
|
||||
EmojiReactionsRow(
|
||||
highlightedEmojis = target.event.reactionsState.highlightedKeys,
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ class TimelinePresenter @AssistedInject constructor(
|
|||
val paginationState by timeline.paginationState.collectAsState()
|
||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
|
||||
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value)
|
||||
|
||||
val prevMostRecentItemId = rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val newItemState = remember { mutableStateOf(NewEventState.None) }
|
||||
|
|
@ -175,12 +176,18 @@ class TimelinePresenter @AssistedInject constructor(
|
|||
.launchIn(this)
|
||||
}
|
||||
|
||||
val timelineRoomInfo by remember {
|
||||
derivedStateOf {
|
||||
TimelineRoomInfo(
|
||||
isDirect = room.isDirect,
|
||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
||||
)
|
||||
}
|
||||
}
|
||||
return TimelineState(
|
||||
timelineRoomInfo = TimelineRoomInfo(
|
||||
isDirect = room.isDirect
|
||||
),
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
highlightedEventId = highlightedEventId.value,
|
||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
||||
paginationState = paginationState,
|
||||
timelineItems = timelineItems,
|
||||
showReadReceipts = readReceiptsEnabled,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ data class TimelineState(
|
|||
val timelineRoomInfo: TimelineRoomInfo,
|
||||
val showReadReceipts: Boolean,
|
||||
val highlightedEventId: EventId?,
|
||||
val userHasPermissionToSendMessage: Boolean,
|
||||
val paginationState: MatrixTimeline.PaginationState,
|
||||
val newEventState: NewEventState,
|
||||
val sessionState: SessionState,
|
||||
|
|
@ -40,4 +39,6 @@ data class TimelineState(
|
|||
@Immutable
|
||||
data class TimelineRoomInfo(
|
||||
val isDirect: Boolean,
|
||||
val userHasPermissionToSendMessage: Boolean,
|
||||
val userHasPermissionToSendReaction: Boolean,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ fun aTimelineState(timelineItems: ImmutableList<TimelineItem> = persistentListOf
|
|||
beginningOfRoomReached = false,
|
||||
),
|
||||
highlightedEventId = null,
|
||||
userHasPermissionToSendMessage = true,
|
||||
newEventState = NewEventState.None,
|
||||
sessionState = aSessionState(
|
||||
isSessionVerified = true,
|
||||
|
|
@ -218,4 +217,6 @@ internal fun aTimelineRoomInfo(
|
|||
isDirect: Boolean = false,
|
||||
) = TimelineRoomInfo(
|
||||
isDirect = isDirect,
|
||||
userHasPermissionToSendMessage = true,
|
||||
userHasPermissionToSendReaction = true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@ fun TimelineView(
|
|||
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true
|
||||
&& state.timelineItems.first().identifier() == timelineItem.identifier(),
|
||||
highlightedItem = state.highlightedEventId?.value,
|
||||
userHasPermissionToSendMessage = state.userHasPermissionToSendMessage,
|
||||
onClick = onMessageClicked,
|
||||
onLongClick = onMessageLongClicked,
|
||||
onUserDataClick = onUserDataClicked,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ internal fun ATimelineItemEventRow(
|
|||
showReadReceipts = showReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = isHighlighted,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
onLongClick = {},
|
||||
onUserDataClick = {},
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo
|
||||
import io.element.android.features.messages.impl.timeline.model.metadata
|
||||
import io.element.android.libraries.androidutils.system.openUrlInExternalApp
|
||||
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
||||
|
|
@ -112,7 +113,6 @@ fun TimelineItemEventRow(
|
|||
showReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
isHighlighted: Boolean,
|
||||
canReply: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
|
|
@ -151,6 +151,7 @@ fun TimelineItemEventRow(
|
|||
} else {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
}
|
||||
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.content.canBeRepliedTo()
|
||||
if (canReply) {
|
||||
val state: SwipeableActionsState = rememberSwipeableActionsState()
|
||||
val offset = state.offset.floatValue
|
||||
|
|
@ -335,6 +336,7 @@ private fun TimelineItemEventRowContent(
|
|||
if (event.reactionsState.reactions.isNotEmpty()) {
|
||||
TimelineItemReactionsView(
|
||||
reactionsState = event.reactionsState,
|
||||
userCanSendReaction = timelineRoomInfo.userHasPermissionToSendReaction,
|
||||
isOutgoing = event.isMine,
|
||||
onReactionClicked = onReactionClicked,
|
||||
onReactionLongClicked = onReactionLongClicked,
|
||||
|
|
|
|||
|
|
@ -131,7 +131,6 @@ private fun TimelineItemGroupedEventsRowContent(
|
|||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
highlightedItem = highlightedItem,
|
||||
sessionState = sessionState,
|
||||
userHasPermissionToSendMessage = false,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
inReplyToClick = inReplyToClick,
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.messages.impl.R
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
|
||||
/**
|
||||
|
|
@ -46,7 +46,7 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables
|
|||
@Composable
|
||||
fun TimelineItemReactionsLayout(
|
||||
expandButton: @Composable () -> Unit,
|
||||
addMoreButton: @Composable () -> Unit,
|
||||
addMoreButton: (@Composable () -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
itemSpacing: Dp = 0.dp,
|
||||
rowSpacing: Dp = 0.dp,
|
||||
|
|
@ -82,21 +82,21 @@ fun TimelineItemReactionsLayout(
|
|||
|
||||
// Used to render the collapsed state, this takes the rows inputted and adds the extra button to the last row,
|
||||
// removing only as many trailing reactions as needed to make space for it.
|
||||
fun replaceTrailingItemsWithButtons(rowsIn: List<List<Placeable>>, expandButton: Placeable, addMoreButton: Placeable): List<List<Placeable>> {
|
||||
fun replaceTrailingItemsWithButtons(rowsIn: List<List<Placeable>>, expandButton: Placeable, addMoreButton: Placeable?): List<List<Placeable>> {
|
||||
val rows = rowsIn.toMutableList()
|
||||
val lastRow = rows.last()
|
||||
val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + addMoreButton.width
|
||||
val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + (addMoreButton?.width ?: 0)
|
||||
var rowX = 0
|
||||
lastRow.forEachIndexed { i, placeable ->
|
||||
val horizontalSpacing = if (i == 0) 0 else itemSpacing.toPx().toInt()
|
||||
rowX += placeable.width + horizontalSpacing
|
||||
if (rowX > constraints.maxWidth - (buttonsWidth + horizontalSpacing)) {
|
||||
val lastRowWithButton = lastRow.take(i) + listOf(expandButton, addMoreButton)
|
||||
val lastRowWithButton = lastRow.take(i) + listOfNotNull(expandButton, addMoreButton)
|
||||
rows[rows.size - 1] = lastRowWithButton
|
||||
return rows
|
||||
}
|
||||
}
|
||||
val lastRowWithButton = lastRow + listOf(expandButton, addMoreButton)
|
||||
val lastRowWithButton = lastRow + listOfNotNull(expandButton, addMoreButton)
|
||||
rows[rows.size - 1] = lastRowWithButton
|
||||
return rows
|
||||
}
|
||||
|
|
@ -155,16 +155,15 @@ fun TimelineItemReactionsLayout(
|
|||
val newConstrains = constraints.copy(minHeight = maxHeight)
|
||||
reactionsPlaceables = subcompose(2, reactions).map { it.measure(newConstrains) }
|
||||
expandPlaceable = subcompose(3, expandButton).first().measure(newConstrains)
|
||||
val addMorePlaceable = subcompose(4, addMoreButton).first().measure(newConstrains)
|
||||
|
||||
val addMorePlaceable = addMoreButton?.let { subcompose(4, addMoreButton).first().measure(newConstrains) }
|
||||
|
||||
// Calculate the layout of the rows with the reactions button and add more button
|
||||
val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOf(addMorePlaceable))
|
||||
val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOfNotNull(addMorePlaceable))
|
||||
// If we have extended beyond the defined number of rows we are showing the expand/collapse ui
|
||||
if (rowsBeforeCollapsible?.let { reactionsAndAddMore.size > it } == true) {
|
||||
if (expanded) {
|
||||
// Show all subviews with the add more button at the end
|
||||
var reactionsAndButtons = calculateRows(reactionsPlaceables + listOf(expandPlaceable, addMorePlaceable))
|
||||
var reactionsAndButtons = calculateRows(reactionsPlaceables + listOfNotNull(expandPlaceable, addMorePlaceable))
|
||||
reactionsAndButtons = ensureCollapseAndAddMoreButtonsAreOnTheSameRow(reactionsAndButtons)
|
||||
layoutRows(reactionsAndButtons)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ import io.element.android.features.messages.impl.R
|
|||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -40,6 +40,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
fun TimelineItemReactionsView(
|
||||
reactionsState: TimelineItemReactions,
|
||||
isOutgoing: Boolean,
|
||||
userCanSendReaction: Boolean,
|
||||
onReactionClicked: (emoji: String) -> Unit,
|
||||
onReactionLongClicked: (emoji: String) -> Unit,
|
||||
onMoreReactionsClicked: () -> Unit,
|
||||
|
|
@ -49,6 +50,7 @@ fun TimelineItemReactionsView(
|
|||
TimelineItemReactionsView(
|
||||
modifier = modifier,
|
||||
reactions = reactionsState.reactions,
|
||||
userCanSendReaction = userCanSendReaction,
|
||||
expanded = expanded,
|
||||
isOutgoing = isOutgoing,
|
||||
onReactionClick = onReactionClicked,
|
||||
|
|
@ -61,6 +63,7 @@ fun TimelineItemReactionsView(
|
|||
@Composable
|
||||
private fun TimelineItemReactionsView(
|
||||
reactions: ImmutableList<AggregatedReaction>,
|
||||
userCanSendReaction: Boolean,
|
||||
isOutgoing: Boolean,
|
||||
expanded: Boolean,
|
||||
onReactionClick: (emoji: String) -> Unit,
|
||||
|
|
@ -93,19 +96,26 @@ private fun TimelineItemReactionsView(
|
|||
onLongClick = {}
|
||||
)
|
||||
},
|
||||
addMoreButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction),
|
||||
onClick = onMoreReactionsClick,
|
||||
onLongClick = {}
|
||||
)
|
||||
},
|
||||
addMoreButton = if (userCanSendReaction) {
|
||||
{
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction),
|
||||
onClick = onMoreReactionsClick,
|
||||
onLongClick = {}
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
reactions = {
|
||||
reactions.forEach { reaction ->
|
||||
CompositionLocalProvider(LocalLayoutDirection provides currentLayout) {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Reaction(reaction = reaction),
|
||||
onClick = { onReactionClick(reaction.key) },
|
||||
onClick = {
|
||||
// Always allow user to redact their own reactions
|
||||
if (reaction.isHighlighted || userCanSendReaction) {
|
||||
onReactionClick(reaction.key)
|
||||
}
|
||||
},
|
||||
onLongClick = { onReactionLongClick(reaction.key) }
|
||||
)
|
||||
}
|
||||
|
|
@ -157,6 +167,7 @@ private fun ContentToPreview(
|
|||
reactionsState = TimelineItemReactions(
|
||||
reactions
|
||||
),
|
||||
userCanSendReaction = true,
|
||||
isOutgoing = isOutgoing,
|
||||
onReactionClicked = {},
|
||||
onReactionLongClicked = {},
|
||||
|
|
|
|||
|
|
@ -18,11 +18,10 @@ package io.element.android.features.messages.impl.timeline.components
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo
|
||||
import io.element.android.features.messages.impl.timeline.session.SessionState
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
|
@ -34,7 +33,6 @@ internal fun TimelineItemRow(
|
|||
showReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
highlightedItem: String?,
|
||||
userHasPermissionToSendMessage: Boolean,
|
||||
sessionState: SessionState,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onClick: (TimelineItem.Event) -> Unit,
|
||||
|
|
@ -77,7 +75,6 @@ internal fun TimelineItemRow(
|
|||
showReadReceipts = showReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
||||
canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(),
|
||||
onClick = { onClick(timelineItem) },
|
||||
onLongClick = { onLongClick(timelineItem) },
|
||||
onUserDataClick = onUserDataClick,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ fun TimelineItemEventContent.canBeRepliedTo(): Boolean =
|
|||
|
||||
/**
|
||||
* Return true if user can react (i.e. send a reaction) on the event content.
|
||||
* This does not take into account the power level of the user.
|
||||
*/
|
||||
fun TimelineItemEventContent.canReact(): Boolean =
|
||||
when (this) {
|
||||
|
|
|
|||
|
|
@ -62,14 +62,15 @@ class ActionListPresenterTest {
|
|||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(isMine = true, content = TimelineItemRedactedContent)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = false,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.ViewSource,
|
||||
)
|
||||
)
|
||||
|
|
@ -87,14 +88,15 @@ class ActionListPresenterTest {
|
|||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(isMine = false, content = TimelineItemRedactedContent)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = false,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.ViewSource,
|
||||
)
|
||||
)
|
||||
|
|
@ -115,14 +117,15 @@ class ActionListPresenterTest {
|
|||
isMine = false,
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Copy,
|
||||
|
|
@ -147,14 +150,15 @@ class ActionListPresenterTest {
|
|||
isMine = false,
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = false))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = false, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.ViewSource,
|
||||
|
|
@ -178,12 +182,45 @@ class ActionListPresenterTest {
|
|||
isMine = false,
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = true))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.Clear)
|
||||
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for others message and cannot send reaction`() = runTest {
|
||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(
|
||||
isMine = false,
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = false))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = false,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Copy,
|
||||
|
|
@ -209,14 +246,15 @@ class ActionListPresenterTest {
|
|||
isMine = true,
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
|
|
@ -242,14 +280,15 @@ class ActionListPresenterTest {
|
|||
isMine = true,
|
||||
content = aTimelineItemImageContent(),
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.ViewSource,
|
||||
|
|
@ -273,14 +312,15 @@ class ActionListPresenterTest {
|
|||
isMine = true,
|
||||
content = aTimelineItemStateEventContent(),
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
stateEvent,
|
||||
persistentListOf(
|
||||
event = stateEvent,
|
||||
displayEmojiReactions = false,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.ViewSource,
|
||||
)
|
||||
|
|
@ -302,14 +342,15 @@ class ActionListPresenterTest {
|
|||
isMine = true,
|
||||
content = aTimelineItemStateEventContent(),
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
stateEvent,
|
||||
persistentListOf(
|
||||
event = stateEvent,
|
||||
displayEmojiReactions = false,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Copy,
|
||||
)
|
||||
)
|
||||
|
|
@ -330,14 +371,15 @@ class ActionListPresenterTest {
|
|||
isMine = true,
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
// val loadingState = awaitItem()
|
||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
|
|
@ -367,13 +409,12 @@ class ActionListPresenterTest {
|
|||
content = TimelineItemRedactedContent,
|
||||
)
|
||||
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
|
||||
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
awaitItem().run {
|
||||
assertThat(target).isEqualTo(ActionListState.Target.None)
|
||||
assertThat(displayEmojiReactions).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -391,19 +432,19 @@ class ActionListPresenterTest {
|
|||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null),
|
||||
)
|
||||
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = false,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
assertThat(successState.displayEmojiReactions).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -419,12 +460,13 @@ class ActionListPresenterTest {
|
|||
isEditable = true,
|
||||
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)),
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.EndPoll,
|
||||
|
|
@ -432,9 +474,9 @@ class ActionListPresenterTest {
|
|||
)
|
||||
)
|
||||
)
|
||||
assertThat(successState.displayEmojiReactions).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for non-editable poll message`() = runTest {
|
||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
||||
|
|
@ -447,19 +489,19 @@ class ActionListPresenterTest {
|
|||
isEditable = false,
|
||||
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)),
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.EndPoll,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
assertThat(successState.displayEmojiReactions).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -475,18 +517,18 @@ class ActionListPresenterTest {
|
|||
isEditable = false,
|
||||
content = aTimelineItemPollContent(isEnded = true),
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
assertThat(successState.displayEmojiReactions).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -501,19 +543,19 @@ class ActionListPresenterTest {
|
|||
isMine = true,
|
||||
content = aTimelineItemVoiceContent(),
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true))
|
||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
assertThat(successState.displayEmojiReactions).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue