Merge pull request #2115 from element-hq/feature/bma/reactionsPowerLevel

Reactions power level
This commit is contained in:
Benoit Marty 2023-12-26 16:11:22 +01:00 committed by GitHub
commit e38799c4db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 173 additions and 102 deletions

1
changelog.d/2093.bugfix Normal file
View file

@ -0,0 +1 @@
Disable ability to send reaction if the user does not have the permission to.

View file

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

View file

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

View file

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

View file

@ -159,6 +159,7 @@ fun MessagesView(
event = event,
canRedact = state.userHasPermissionToRedact,
canSendMessage = state.userHasPermissionToSendMessage,
canSendReaction = state.userHasPermissionToSendReaction,
)
)
}

View file

@ -24,5 +24,6 @@ sealed interface ActionListEvents {
val event: TimelineItem.Event,
val canRedact: Boolean,
val canSendMessage: Boolean,
val canSendReaction: Boolean,
) : ActionListEvents
}

View file

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

View file

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

View file

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

View file

@ -179,7 +179,7 @@ private fun SheetContent(
HorizontalDivider()
}
}
if (state.displayEmojiReactions) {
if (target.displayEmojiReactions) {
item {
EmojiReactionsRow(
highlightedEmojis = target.event.reactionsState.highlightedKeys,

View file

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

View file

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

View file

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

View file

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

View file

@ -35,7 +35,6 @@ internal fun ATimelineItemEventRow(
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = isHighlighted,
canReply = true,
onClick = {},
onLongClick = {},
onUserDataClick = {},

View file

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

View file

@ -131,7 +131,6 @@ private fun TimelineItemGroupedEventsRowContent(
isLastOutgoingMessage = isLastOutgoingMessage,
highlightedItem = highlightedItem,
sessionState = sessionState,
userHasPermissionToSendMessage = false,
onClick = onClick,
onLongClick = onLongClick,
inReplyToClick = inReplyToClick,

View file

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

View file

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

View file

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

View file

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

View file

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