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