Timeline Action : refactor how it's computed and align with iOS
This commit is contained in:
parent
c59cfd707c
commit
3adc6d0be8
21 changed files with 323 additions and 296 deletions
|
|
@ -20,6 +20,7 @@ import android.os.Build
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -131,10 +132,9 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
|
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
|
||||||
|
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||||
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
|
|
||||||
val userHasPermissionToRedactOwn by room.canRedactOwnAsState(updateKey = syncUpdateFlow.value)
|
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
|
||||||
val userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = syncUpdateFlow.value)
|
|
||||||
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION, updateKey = syncUpdateFlow.value)
|
|
||||||
val roomName: AsyncData<String> by remember {
|
val roomName: AsyncData<String> by remember {
|
||||||
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
|
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
|
||||||
}
|
}
|
||||||
|
|
@ -211,11 +211,8 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
heroes = heroes,
|
heroes = heroes,
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
|
||||||
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
|
|
||||||
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
|
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
|
||||||
composerState = composerState,
|
composerState = composerState,
|
||||||
|
userEventPermissions = userEventPermissions,
|
||||||
voiceMessageComposerState = voiceMessageComposerState,
|
voiceMessageComposerState = voiceMessageComposerState,
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
typingNotificationState = typingNotificationState,
|
typingNotificationState = typingNotificationState,
|
||||||
|
|
@ -235,6 +232,24 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun userEventPermissions(updateKey: Long): State<UserEventPermissions> {
|
||||||
|
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = updateKey)
|
||||||
|
val userHasPermissionToRedactOwn by room.canRedactOwnAsState(updateKey = updateKey)
|
||||||
|
val userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = updateKey)
|
||||||
|
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION, updateKey = updateKey)
|
||||||
|
return remember {
|
||||||
|
derivedStateOf {
|
||||||
|
UserEventPermissions(
|
||||||
|
canSendMessage = userHasPermissionToSendMessage,
|
||||||
|
canRedactOwn = userHasPermissionToRedactOwn,
|
||||||
|
canRedactOther = userHasPermissionToRedactOther,
|
||||||
|
canSendReaction = userHasPermissionToSendReaction,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun MatrixRoomInfo.avatarData(): AvatarData {
|
private fun MatrixRoomInfo.avatarData(): AvatarData {
|
||||||
return AvatarData(
|
return AvatarData(
|
||||||
id = id.value,
|
id = id.value,
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,7 @@ data class MessagesState(
|
||||||
val roomName: AsyncData<String>,
|
val roomName: AsyncData<String>,
|
||||||
val roomAvatar: AsyncData<AvatarData>,
|
val roomAvatar: AsyncData<AvatarData>,
|
||||||
val heroes: ImmutableList<AvatarData>,
|
val heroes: ImmutableList<AvatarData>,
|
||||||
val userHasPermissionToSendMessage: Boolean,
|
val userEventPermissions: UserEventPermissions,
|
||||||
val userHasPermissionToRedactOwn: Boolean,
|
|
||||||
val userHasPermissionToRedactOther: Boolean,
|
|
||||||
val userHasPermissionToSendReaction: Boolean,
|
|
||||||
val composerState: MessageComposerState,
|
val composerState: MessageComposerState,
|
||||||
val voiceMessageComposerState: VoiceMessageComposerState,
|
val voiceMessageComposerState: VoiceMessageComposerState,
|
||||||
val timelineState: TimelineState,
|
val timelineState: TimelineState,
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||||
aMessagesState(),
|
aMessagesState(),
|
||||||
aMessagesState(hasNetworkConnection = false),
|
aMessagesState(hasNetworkConnection = false),
|
||||||
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
|
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
|
||||||
aMessagesState(userHasPermissionToSendMessage = false),
|
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
|
||||||
aMessagesState(showReinvitePrompt = true),
|
aMessagesState(showReinvitePrompt = true),
|
||||||
aMessagesState(
|
aMessagesState(
|
||||||
roomName = AsyncData.Uninitialized,
|
roomName = AsyncData.Uninitialized,
|
||||||
|
|
@ -93,10 +93,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||||
fun aMessagesState(
|
fun aMessagesState(
|
||||||
roomName: AsyncData<String> = AsyncData.Success("Room name"),
|
roomName: AsyncData<String> = AsyncData.Success("Room name"),
|
||||||
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
|
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
|
||||||
userHasPermissionToSendMessage: Boolean = true,
|
userEventPermissions: UserEventPermissions = aUserEventPermissions(),
|
||||||
userHasPermissionToRedactOwn: Boolean = false,
|
|
||||||
userHasPermissionToRedactOther: Boolean = false,
|
|
||||||
userHasPermissionToSendReaction: Boolean = true,
|
|
||||||
composerState: MessageComposerState = aMessageComposerState(
|
composerState: MessageComposerState = aMessageComposerState(
|
||||||
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
|
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
|
||||||
isFullScreen = false,
|
isFullScreen = false,
|
||||||
|
|
@ -122,10 +119,7 @@ fun aMessagesState(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
heroes = persistentListOf(),
|
heroes = persistentListOf(),
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
userEventPermissions = userEventPermissions,
|
||||||
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
|
|
||||||
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
|
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
|
||||||
composerState = composerState,
|
composerState = composerState,
|
||||||
voiceMessageComposerState = voiceMessageComposerState,
|
voiceMessageComposerState = voiceMessageComposerState,
|
||||||
typingNotificationState = aTypingNotificationState(),
|
typingNotificationState = aTypingNotificationState(),
|
||||||
|
|
@ -145,6 +139,18 @@ fun aMessagesState(
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun aUserEventPermissions(
|
||||||
|
canRedactOwn: Boolean = false,
|
||||||
|
canRedactOther: Boolean = false,
|
||||||
|
canSendMessage: Boolean = true,
|
||||||
|
canSendReaction: Boolean = true,
|
||||||
|
) = UserEventPermissions(
|
||||||
|
canRedactOwn = canRedactOwn,
|
||||||
|
canRedactOther = canRedactOther,
|
||||||
|
canSendMessage = canSendMessage,
|
||||||
|
canSendReaction = canSendReaction,
|
||||||
|
)
|
||||||
|
|
||||||
fun aReactionSummaryState(
|
fun aReactionSummaryState(
|
||||||
target: ReactionSummaryState.Summary? = null,
|
target: ReactionSummaryState.Summary? = null,
|
||||||
eventSink: (ReactionSummaryEvents) -> Unit = {}
|
eventSink: (ReactionSummaryEvents) -> Unit = {}
|
||||||
|
|
|
||||||
|
|
@ -154,10 +154,7 @@ fun MessagesView(
|
||||||
state.actionListState.eventSink(
|
state.actionListState.eventSink(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = event,
|
event = event,
|
||||||
canRedactOwn = state.userHasPermissionToRedactOwn,
|
userEventPermissions = state.userEventPermissions,
|
||||||
canRedactOther = state.userHasPermissionToRedactOther,
|
|
||||||
canSendMessage = state.userHasPermissionToSendMessage,
|
|
||||||
canSendReaction = state.userHasPermissionToSendReaction,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +405,7 @@ private fun MessagesViewComposerBottomSheetContents(
|
||||||
subcomposing: Boolean,
|
subcomposing: Boolean,
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
) {
|
) {
|
||||||
if (state.userHasPermissionToSendMessage) {
|
if (state.userEventPermissions.canSendMessage) {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
MentionSuggestionsPickerView(
|
MentionSuggestionsPickerView(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.messages.impl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the permissions a user has in a room.
|
||||||
|
* It's dependent of the user's power level in the room.
|
||||||
|
*/
|
||||||
|
data class UserEventPermissions(
|
||||||
|
val canRedactOwn: Boolean,
|
||||||
|
val canRedactOther: Boolean,
|
||||||
|
val canSendMessage: Boolean,
|
||||||
|
val canSendReaction: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -16,15 +16,13 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.actionlist
|
package io.element.android.features.messages.impl.actionlist
|
||||||
|
|
||||||
|
import io.element.android.features.messages.impl.UserEventPermissions
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
|
||||||
sealed interface ActionListEvents {
|
sealed interface ActionListEvents {
|
||||||
data object Clear : ActionListEvents
|
data object Clear : ActionListEvents
|
||||||
data class ComputeForMessage(
|
data class ComputeForMessage(
|
||||||
val event: TimelineItem.Event,
|
val event: TimelineItem.Event,
|
||||||
val canRedactOwn: Boolean,
|
val userEventPermissions: UserEventPermissions,
|
||||||
val canRedactOther: Boolean,
|
|
||||||
val canSendMessage: Boolean,
|
|
||||||
val canSendReaction: Boolean,
|
|
||||||
) : ActionListEvents
|
) : ActionListEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,17 @@ 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
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import io.element.android.features.messages.impl.UserEventPermissions
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
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.TimelineItemCallNotifyContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||||
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.TimelineItemVoiceContent
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
|
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.canReact
|
import io.element.android.features.messages.impl.timeline.model.event.canReact
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
|
|
@ -58,10 +60,7 @@ class ActionListPresenter @Inject constructor(
|
||||||
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
||||||
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
|
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
|
||||||
timelineItem = event.event,
|
timelineItem = event.event,
|
||||||
userCanRedactOwn = event.canRedactOwn,
|
usersEventPermissions = event.userEventPermissions,
|
||||||
userCanRedactOther = event.canRedactOther,
|
|
||||||
userCanSendMessage = event.canSendMessage,
|
|
||||||
userCanSendReaction = event.canSendReaction,
|
|
||||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||||
target = target,
|
target = target,
|
||||||
)
|
)
|
||||||
|
|
@ -76,136 +75,18 @@ class ActionListPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.computeForMessage(
|
private fun CoroutineScope.computeForMessage(
|
||||||
timelineItem: TimelineItem.Event,
|
timelineItem: TimelineItem.Event,
|
||||||
userCanRedactOwn: Boolean,
|
usersEventPermissions: UserEventPermissions,
|
||||||
userCanRedactOther: Boolean,
|
|
||||||
userCanSendMessage: Boolean,
|
|
||||||
userCanSendReaction: Boolean,
|
|
||||||
isDeveloperModeEnabled: Boolean,
|
isDeveloperModeEnabled: Boolean,
|
||||||
target: MutableState<ActionListState.Target>
|
target: MutableState<ActionListState.Target>
|
||||||
) = launch {
|
) = launch {
|
||||||
target.value = ActionListState.Target.Loading(timelineItem)
|
target.value = ActionListState.Target.Loading(timelineItem)
|
||||||
val canRedact = timelineItem.isMine && userCanRedactOwn || !timelineItem.isMine && userCanRedactOther
|
|
||||||
val actions =
|
val actions = buildActions(
|
||||||
when (timelineItem.content) {
|
timelineItem = timelineItem,
|
||||||
is TimelineItemCallNotifyContent -> {
|
usersEventPermissions = usersEventPermissions,
|
||||||
if (isDeveloperModeEnabled) {
|
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||||
listOf(TimelineItemAction.ViewSource)
|
)
|
||||||
} else {
|
val displayEmojiReactions = usersEventPermissions.canSendReaction &&
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemRedactedContent -> {
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
listOf(TimelineItemAction.ViewSource)
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemStateContent -> {
|
|
||||||
buildList {
|
|
||||||
add(TimelineItemAction.Copy)
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemPollContent -> {
|
|
||||||
val canEndPoll = timelineItem.isRemote &&
|
|
||||||
!timelineItem.content.isEnded &&
|
|
||||||
(timelineItem.isMine || canRedact)
|
|
||||||
buildList {
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
// Can only reply or forward messages already uploaded to the server
|
|
||||||
add(TimelineItemAction.Reply)
|
|
||||||
}
|
|
||||||
if (timelineItem.isRemote && timelineItem.isEditable) {
|
|
||||||
add(TimelineItemAction.Edit)
|
|
||||||
}
|
|
||||||
if (canEndPoll) {
|
|
||||||
add(TimelineItemAction.EndPoll)
|
|
||||||
}
|
|
||||||
if (timelineItem.content.canBeCopied()) {
|
|
||||||
add(TimelineItemAction.Copy)
|
|
||||||
}
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
if (!timelineItem.isMine) {
|
|
||||||
add(TimelineItemAction.ReportContent)
|
|
||||||
}
|
|
||||||
if (canRedact) {
|
|
||||||
add(TimelineItemAction.Redact)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemVoiceContent -> {
|
|
||||||
buildList {
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.Reply)
|
|
||||||
add(TimelineItemAction.Forward)
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
if (!timelineItem.isMine) {
|
|
||||||
add(TimelineItemAction.ReportContent)
|
|
||||||
}
|
|
||||||
if (canRedact) {
|
|
||||||
add(TimelineItemAction.Redact)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemLegacyCallInviteContent -> {
|
|
||||||
buildList {
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> buildList<TimelineItemAction> {
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
// Can only reply or forward messages already uploaded to the server
|
|
||||||
if (userCanSendMessage) {
|
|
||||||
if (timelineItem.isThreaded) {
|
|
||||||
add(TimelineItemAction.ReplyInThread)
|
|
||||||
} else {
|
|
||||||
add(TimelineItemAction.Reply)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Stickers can't be forwarded (yet) so we don't show the option
|
|
||||||
// See https://github.com/element-hq/element-x-android/issues/2161
|
|
||||||
if (!timelineItem.isSticker) {
|
|
||||||
add(TimelineItemAction.Forward)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (timelineItem.isEditable) {
|
|
||||||
add(TimelineItemAction.Edit)
|
|
||||||
}
|
|
||||||
if (timelineItem.content.canBeCopied()) {
|
|
||||||
add(TimelineItemAction.Copy)
|
|
||||||
}
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
if (!timelineItem.isMine) {
|
|
||||||
add(TimelineItemAction.ReportContent)
|
|
||||||
}
|
|
||||||
if (canRedact) {
|
|
||||||
add(TimelineItemAction.Redact)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val displayEmojiReactions = userCanSendReaction &&
|
|
||||||
timelineItem.isRemote &&
|
timelineItem.isRemote &&
|
||||||
timelineItem.content.canReact()
|
timelineItem.content.canReact()
|
||||||
if (actions.isNotEmpty() || displayEmojiReactions) {
|
if (actions.isNotEmpty() || displayEmojiReactions) {
|
||||||
|
|
@ -219,3 +100,61 @@ class ActionListPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildActions(
|
||||||
|
timelineItem: TimelineItem.Event,
|
||||||
|
usersEventPermissions: UserEventPermissions,
|
||||||
|
isDeveloperModeEnabled: Boolean,
|
||||||
|
): List<TimelineItemAction> {
|
||||||
|
val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther
|
||||||
|
return buildList {
|
||||||
|
if (timelineItem.canBeRepliedTo && usersEventPermissions.canSendMessage) {
|
||||||
|
if (timelineItem.isThreaded) {
|
||||||
|
add(TimelineItemAction.ReplyInThread)
|
||||||
|
} else {
|
||||||
|
add(TimelineItemAction.Reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timelineItem.isRemote && timelineItem.content.canBeForwarded()) {
|
||||||
|
add(TimelineItemAction.Forward)
|
||||||
|
}
|
||||||
|
if (timelineItem.isEditable) {
|
||||||
|
add(TimelineItemAction.Edit)
|
||||||
|
}
|
||||||
|
if (canRedact && timelineItem.content is TimelineItemPollContent && !timelineItem.content.isEnded) {
|
||||||
|
add(TimelineItemAction.EndPoll)
|
||||||
|
}
|
||||||
|
if (timelineItem.content.canBeCopied()) {
|
||||||
|
add(TimelineItemAction.Copy)
|
||||||
|
}
|
||||||
|
if (timelineItem.isRemote) {
|
||||||
|
add(TimelineItemAction.CopyLink)
|
||||||
|
}
|
||||||
|
if (isDeveloperModeEnabled) {
|
||||||
|
add(TimelineItemAction.ViewSource)
|
||||||
|
}
|
||||||
|
if (!timelineItem.isMine) {
|
||||||
|
add(TimelineItemAction.ReportContent)
|
||||||
|
}
|
||||||
|
if (canRedact) {
|
||||||
|
add(TimelineItemAction.Redact)
|
||||||
|
}
|
||||||
|
}.postFilter(timelineItem.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post filter the actions based on the content of the event.
|
||||||
|
*/
|
||||||
|
private fun List<TimelineItemAction>.postFilter(content: TimelineItemEventContent): List<TimelineItemAction> {
|
||||||
|
return filter { action ->
|
||||||
|
when (content) {
|
||||||
|
is TimelineItemCallNotifyContent,
|
||||||
|
is TimelineItemLegacyCallInviteContent,
|
||||||
|
is TimelineItemStateContent,
|
||||||
|
is TimelineItemRedactedContent -> {
|
||||||
|
action == TimelineItemAction.ViewSource
|
||||||
|
}
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ internal fun aTimelineItemEvent(
|
||||||
transactionId: TransactionId? = null,
|
transactionId: TransactionId? = null,
|
||||||
isMine: Boolean = false,
|
isMine: Boolean = false,
|
||||||
isEditable: Boolean = false,
|
isEditable: Boolean = false,
|
||||||
|
canBeRepliedTo: Boolean = false,
|
||||||
senderDisplayName: String = "Sender",
|
senderDisplayName: String = "Sender",
|
||||||
displayNameAmbiguous: Boolean = false,
|
displayNameAmbiguous: Boolean = false,
|
||||||
content: TimelineItemEventContent = aTimelineItemTextContent(),
|
content: TimelineItemEventContent = aTimelineItemTextContent(),
|
||||||
|
|
@ -150,6 +151,7 @@ internal fun aTimelineItemEvent(
|
||||||
sentTime = "12:34",
|
sentTime = "12:34",
|
||||||
isMine = isMine,
|
isMine = isMine,
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
|
canBeRepliedTo = canBeRepliedTo,
|
||||||
senderProfile = aProfileTimelineDetailsReady(
|
senderProfile = aProfileTimelineDetailsReady(
|
||||||
displayName = senderDisplayName,
|
displayName = senderDisplayName,
|
||||||
displayNameAmbiguous = displayNameAmbiguous,
|
displayNameAmbiguous = displayNameAmbiguous,
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||||
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.libraries.designsystem.colors.AvatarColorsProvider
|
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
||||||
import io.element.android.libraries.designsystem.components.EqualWidthColumn
|
import io.element.android.libraries.designsystem.components.EqualWidthColumn
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
|
@ -148,7 +147,7 @@ fun TimelineItemEventRow(
|
||||||
} else {
|
} else {
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
}
|
}
|
||||||
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.content.canBeRepliedTo()
|
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo
|
||||||
if (canReply) {
|
if (canReply) {
|
||||||
val state: SwipeableActionsState = rememberSwipeableActionsState()
|
val state: SwipeableActionsState = rememberSwipeableActionsState()
|
||||||
val offset = state.offset.floatValue
|
val offset = state.offset.floatValue
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ class TimelineItemEventFactory @Inject constructor(
|
||||||
content = contentFactory.create(currentTimelineItem.event),
|
content = contentFactory.create(currentTimelineItem.event),
|
||||||
isMine = currentTimelineItem.event.isOwn,
|
isMine = currentTimelineItem.event.isOwn,
|
||||||
isEditable = currentTimelineItem.event.isEditable,
|
isEditable = currentTimelineItem.event.isEditable,
|
||||||
|
canBeRepliedTo = currentTimelineItem.event.canBeRepliedTo,
|
||||||
sentTime = sentTime,
|
sentTime = sentTime,
|
||||||
groupPosition = groupPosition,
|
groupPosition = groupPosition,
|
||||||
reactionsState = currentTimelineItem.computeReactionsState(),
|
reactionsState = currentTimelineItem.computeReactionsState(),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||||
|
|
@ -74,6 +75,7 @@ sealed interface TimelineItem {
|
||||||
val sentTime: String = "",
|
val sentTime: String = "",
|
||||||
val isMine: Boolean = false,
|
val isMine: Boolean = false,
|
||||||
val isEditable: Boolean,
|
val isEditable: Boolean,
|
||||||
|
val canBeRepliedTo: Boolean,
|
||||||
val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
|
val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
|
||||||
val reactionsState: TimelineItemReactions,
|
val reactionsState: TimelineItemReactions,
|
||||||
val readReceiptState: TimelineItemReadReceipts,
|
val readReceiptState: TimelineItemReadReceipts,
|
||||||
|
|
@ -94,6 +96,7 @@ sealed interface TimelineItem {
|
||||||
val isSticker: Boolean = content is TimelineItemStickerContent
|
val isSticker: Boolean = content is TimelineItemStickerContent
|
||||||
|
|
||||||
val isRemote = eventId != null
|
val isRemote = eventId != null
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -24,27 +24,27 @@ sealed interface TimelineItemEventContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only text based content and states can be copied.
|
* Only text based content can be copied.
|
||||||
*/
|
*/
|
||||||
fun TimelineItemEventContent.canBeCopied(): Boolean =
|
fun TimelineItemEventContent.canBeCopied(): Boolean =
|
||||||
when (this) {
|
this is TimelineItemTextBasedContent
|
||||||
is TimelineItemTextBasedContent,
|
|
||||||
is TimelineItemStateContent,
|
|
||||||
is TimelineItemRedactedContent -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the event content can be replied to.
|
* Returns true if the event content can be forwarded.
|
||||||
* Note: it should match the logic in [io.element.android.features.messages.impl.actionlist.ActionListPresenter].
|
|
||||||
*/
|
*/
|
||||||
fun TimelineItemEventContent.canBeRepliedTo(): Boolean =
|
fun TimelineItemEventContent.canBeForwarded(): Boolean =
|
||||||
when (this) {
|
when (this) {
|
||||||
is TimelineItemRedactedContent,
|
is TimelineItemTextBasedContent,
|
||||||
is TimelineItemLegacyCallInviteContent,
|
is TimelineItemImageContent,
|
||||||
is TimelineItemCallNotifyContent,
|
is TimelineItemFileContent,
|
||||||
is TimelineItemStateContent -> false
|
is TimelineItemAudioContent,
|
||||||
else -> true
|
is TimelineItemVideoContent,
|
||||||
|
is TimelineItemLocationContent,
|
||||||
|
is TimelineItemVoiceContent -> true
|
||||||
|
// Stickers can't be forwarded (yet) so we don't show the option
|
||||||
|
// See https://github.com/element-hq/element-x-android/issues/2161
|
||||||
|
is TimelineItemStickerContent -> false
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,8 @@ class MessagesPresenterTest {
|
||||||
assertThat(initialState.roomName).isEqualTo(AsyncData.Success(""))
|
assertThat(initialState.roomName).isEqualTo(AsyncData.Success(""))
|
||||||
assertThat(initialState.roomAvatar)
|
assertThat(initialState.roomAvatar)
|
||||||
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
|
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
|
||||||
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
|
assertThat(initialState.userEventPermissions.canSendMessage).isTrue()
|
||||||
assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
|
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
|
||||||
assertThat(initialState.hasNetworkConnection).isTrue()
|
assertThat(initialState.hasNetworkConnection).isTrue()
|
||||||
assertThat(initialState.snackbarMessage).isNull()
|
assertThat(initialState.snackbarMessage).isNull()
|
||||||
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
|
||||||
|
|
@ -787,7 +787,7 @@ class MessagesPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitFirstItem()
|
val state = awaitFirstItem()
|
||||||
assertThat(state.userHasPermissionToSendMessage).isTrue()
|
assertThat(state.userEventPermissions.canSendMessage).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -811,9 +811,9 @@ class MessagesPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Default value
|
// Default value
|
||||||
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
|
assertThat(awaitItem().userEventPermissions.canSendMessage).isTrue()
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
assertThat(awaitItem().userHasPermissionToSendMessage).isFalse()
|
assertThat(awaitItem().userEventPermissions.canSendMessage).isFalse()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -831,9 +831,9 @@ class MessagesPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOwn }.last()
|
val initialState = consumeItemsUntilPredicate { it.userEventPermissions.canRedactOwn }.last()
|
||||||
assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
|
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
|
||||||
assertThat(initialState.userHasPermissionToRedactOther).isFalse()
|
assertThat(initialState.userEventPermissions.canRedactOther).isFalse()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -851,9 +851,9 @@ class MessagesPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOther }.last()
|
val initialState = consumeItemsUntilPredicate { it.userEventPermissions.canRedactOther }.last()
|
||||||
assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
|
assertThat(initialState.userEventPermissions.canRedactOwn).isFalse()
|
||||||
assertThat(initialState.userHasPermissionToRedactOther).isTrue()
|
assertThat(initialState.userEventPermissions.canRedactOther).isTrue()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -963,6 +963,7 @@ class MessagesPresenterTest {
|
||||||
room = matrixRoom,
|
room = matrixRoom,
|
||||||
sessionPreferencesStore = sessionPreferencesStore,
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
||||||
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
||||||
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||||
import androidx.compose.ui.test.onAllNodesWithTag
|
import androidx.compose.ui.test.onAllNodesWithTag
|
||||||
import androidx.compose.ui.test.onAllNodesWithText
|
import androidx.compose.ui.test.onAllNodesWithText
|
||||||
import androidx.compose.ui.test.onFirst
|
import androidx.compose.ui.test.onFirst
|
||||||
|
import androidx.compose.ui.test.onLast
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
|
@ -43,7 +44,6 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
|
||||||
import io.element.android.features.messages.impl.attachments.Attachment
|
import io.element.android.features.messages.impl.attachments.Attachment
|
||||||
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||||
|
|
@ -175,10 +175,12 @@ class MessagesViewTest {
|
||||||
actionListState = anActionListState(
|
actionListState = anActionListState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
userEventPermissions = UserEventPermissions(
|
||||||
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
|
canSendMessage = userHasPermissionToSendMessage,
|
||||||
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
|
canRedactOwn = userHasPermissionToRedactOwn,
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
canRedactOther = userHasPermissionToRedactOther,
|
||||||
|
canSendReaction = userHasPermissionToSendReaction,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
|
|
@ -189,10 +191,7 @@ class MessagesViewTest {
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
canRedactOwn = state.userHasPermissionToRedactOwn,
|
userEventPermissions = state.userEventPermissions,
|
||||||
canRedactOther = state.userHasPermissionToRedactOther,
|
|
||||||
canSendMessage = state.userHasPermissionToSendMessage,
|
|
||||||
canSendReaction = state.userHasPermissionToSendReaction,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -237,9 +236,11 @@ class MessagesViewTest {
|
||||||
|
|
||||||
private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
|
private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
||||||
|
val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true)
|
||||||
|
val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
timelineItems = persistentListOf(canBeRepliedEvent, cannotBeRepliedEvent),
|
||||||
timelineRoomInfo = aTimelineRoomInfo(
|
timelineRoomInfo = aTimelineRoomInfo(
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage
|
userHasPermissionToSendMessage = userHasPermissionToSendMessage
|
||||||
),
|
),
|
||||||
|
|
@ -249,10 +250,12 @@ class MessagesViewTest {
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { swipeRight(endX = 200f) }
|
rule.onAllNodesWithTag(TestTags.messageBubble.value).apply {
|
||||||
|
onFirst().performTouchInput { swipeRight(endX = 200f) }
|
||||||
|
onLast().performTouchInput { swipeRight(endX = 200f) }
|
||||||
|
}
|
||||||
if (userHasPermissionToSendMessage) {
|
if (userHasPermissionToSendMessage) {
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, canBeRepliedEvent))
|
||||||
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, timelineItem))
|
|
||||||
} else {
|
} else {
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.messages.impl.aUserEventPermissions
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
|
|
@ -31,6 +32,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||||
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
|
@ -66,10 +68,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -104,10 +108,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -142,10 +148,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -185,10 +193,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = false,
|
canRedactOther = false,
|
||||||
canSendReaction = true
|
canSendMessage = false,
|
||||||
|
canSendReaction = true
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -227,10 +237,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = true,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = true,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -269,10 +281,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = true,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = true,
|
||||||
canSendReaction = false
|
canSendMessage = true,
|
||||||
|
canSendReaction = false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -310,10 +324,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -353,10 +369,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -396,10 +414,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -437,10 +457,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = stateEvent,
|
event = stateEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -451,8 +473,6 @@ class ActionListPresenterTest {
|
||||||
event = stateEvent,
|
event = stateEvent,
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -476,26 +496,16 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = stateEvent,
|
event = stateEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
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()
|
|
||||||
assertThat(successState.target).isEqualTo(
|
|
||||||
ActionListState.Target.Success(
|
|
||||||
event = stateEvent,
|
|
||||||
displayEmojiReactions = false,
|
|
||||||
actions = persistentListOf(
|
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
initialState.eventSink.invoke(ActionListEvents.Clear)
|
|
||||||
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -514,10 +524,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -561,10 +573,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
|
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
|
||||||
|
|
@ -572,10 +586,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = redactedEvent,
|
event = redactedEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
|
|
@ -595,16 +611,19 @@ class ActionListPresenterTest {
|
||||||
// No event id, so it's not sent yet
|
// No event id, so it's not sent yet
|
||||||
eventId = null,
|
eventId = null,
|
||||||
isMine = true,
|
isMine = true,
|
||||||
|
canBeRepliedTo = 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(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -637,10 +656,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -675,10 +696,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -712,10 +735,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -742,15 +767,18 @@ class ActionListPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val messageEvent = aMessageEvent(
|
val messageEvent = aMessageEvent(
|
||||||
isMine = true,
|
isMine = true,
|
||||||
|
isEditable = false,
|
||||||
content = aTimelineItemVoiceContent(),
|
content = aTimelineItemVoiceContent(),
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -783,10 +811,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ internal fun aMessageEvent(
|
||||||
transactionId: TransactionId? = null,
|
transactionId: TransactionId? = null,
|
||||||
isMine: Boolean = true,
|
isMine: Boolean = true,
|
||||||
isEditable: Boolean = true,
|
isEditable: Boolean = true,
|
||||||
|
canBeRepliedTo: Boolean = true,
|
||||||
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = null, isEdited = false),
|
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = null, isEdited = false),
|
||||||
inReplyTo: InReplyToDetails? = null,
|
inReplyTo: InReplyToDetails? = null,
|
||||||
isThreaded: Boolean = false,
|
isThreaded: Boolean = false,
|
||||||
|
|
@ -58,6 +59,7 @@ internal fun aMessageEvent(
|
||||||
sentTime = "",
|
sentTime = "",
|
||||||
isMine = isMine,
|
isMine = isMine,
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
|
canBeRepliedTo = canBeRepliedTo,
|
||||||
reactionsState = aTimelineItemReactions(count = 0),
|
reactionsState = aTimelineItemReactions(count = 0),
|
||||||
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||||
localSendState = sendState,
|
localSendState = sendState,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class TimelineItemGrouperTest {
|
||||||
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||||
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
|
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
|
canBeRepliedTo = false,
|
||||||
inReplyTo = null,
|
inReplyTo = null,
|
||||||
isThreaded = false,
|
isThreaded = false,
|
||||||
debugInfo = aTimelineItemDebugInfo(),
|
debugInfo = aTimelineItemDebugInfo(),
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>(
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
transactionId = null,
|
transactionId = null,
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
|
canBeRepliedTo = false,
|
||||||
isLocal = false,
|
isLocal = false,
|
||||||
isOwn = false,
|
isOwn = false,
|
||||||
isRemote = false,
|
isRemote = false,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ data class EventTimelineItem(
|
||||||
val eventId: EventId?,
|
val eventId: EventId?,
|
||||||
val transactionId: TransactionId?,
|
val transactionId: TransactionId?,
|
||||||
val isEditable: Boolean,
|
val isEditable: Boolean,
|
||||||
|
val canBeRepliedTo: Boolean,
|
||||||
val isLocal: Boolean,
|
val isLocal: Boolean,
|
||||||
val isOwn: Boolean,
|
val isOwn: Boolean,
|
||||||
val isRemote: Boolean,
|
val isRemote: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
|
||||||
eventId = it.eventId()?.let(::EventId),
|
eventId = it.eventId()?.let(::EventId),
|
||||||
transactionId = it.transactionId()?.let(::TransactionId),
|
transactionId = it.transactionId()?.let(::TransactionId),
|
||||||
isEditable = it.isEditable(),
|
isEditable = it.isEditable(),
|
||||||
|
canBeRepliedTo = it.canBeRepliedTo(),
|
||||||
isLocal = it.isLocal(),
|
isLocal = it.isLocal(),
|
||||||
isOwn = it.isOwn(),
|
isOwn = it.isOwn(),
|
||||||
isRemote = it.isRemote(),
|
isRemote = it.isRemote(),
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ fun anEventTimelineItem(
|
||||||
eventId: EventId = AN_EVENT_ID,
|
eventId: EventId = AN_EVENT_ID,
|
||||||
transactionId: TransactionId? = null,
|
transactionId: TransactionId? = null,
|
||||||
isEditable: Boolean = false,
|
isEditable: Boolean = false,
|
||||||
|
canBeRepliedTo: Boolean = false,
|
||||||
isLocal: Boolean = false,
|
isLocal: Boolean = false,
|
||||||
isOwn: Boolean = false,
|
isOwn: Boolean = false,
|
||||||
isRemote: Boolean = false,
|
isRemote: Boolean = false,
|
||||||
|
|
@ -61,6 +62,7 @@ fun anEventTimelineItem(
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
transactionId = transactionId,
|
transactionId = transactionId,
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
|
canBeRepliedTo = canBeRepliedTo,
|
||||||
isLocal = isLocal,
|
isLocal = isLocal,
|
||||||
isOwn = isOwn,
|
isOwn = isOwn,
|
||||||
isRemote = isRemote,
|
isRemote = isRemote,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue