Browse Source

Timeline Action : refactor how it's computed and align with iOS

pull/3255/head
ganfra 2 months ago
parent
commit
0149007dd4
  1. 31
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt
  2. 5
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt
  3. 24
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
  4. 7
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
  5. 28
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/UserEventPermissions.kt
  6. 6
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt
  7. 201
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
  8. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
  9. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt
  10. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
  11. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
  12. 30
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt
  13. 23
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
  14. 29
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt
  15. 218
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt
  16. 2
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt
  17. 1
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt
  18. 1
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt
  19. 1
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt
  20. 1
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt
  21. 2
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt

31
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

@ -20,6 +20,7 @@ import android.os.Build @@ -20,6 +20,7 @@ import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@ -131,10 +132,9 @@ class MessagesPresenter @AssistedInject constructor( @@ -131,10 +132,9 @@ class MessagesPresenter @AssistedInject constructor(
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
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 userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = syncUpdateFlow.value)
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION, updateKey = syncUpdateFlow.value)
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
val roomName: AsyncData<String> by remember {
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
}
@ -211,11 +211,8 @@ class MessagesPresenter @AssistedInject constructor( @@ -211,11 +211,8 @@ class MessagesPresenter @AssistedInject constructor(
roomName = roomName,
roomAvatar = roomAvatar,
heroes = heroes,
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
composerState = composerState,
userEventPermissions = userEventPermissions,
voiceMessageComposerState = voiceMessageComposerState,
timelineState = timelineState,
typingNotificationState = typingNotificationState,
@ -235,6 +232,24 @@ class MessagesPresenter @AssistedInject constructor( @@ -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 {
return AvatarData(
id = id.value,

5
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt

@ -37,10 +37,7 @@ data class MessagesState( @@ -37,10 +37,7 @@ data class MessagesState(
val roomName: AsyncData<String>,
val roomAvatar: AsyncData<AvatarData>,
val heroes: ImmutableList<AvatarData>,
val userHasPermissionToSendMessage: Boolean,
val userHasPermissionToRedactOwn: Boolean,
val userHasPermissionToRedactOther: Boolean,
val userHasPermissionToSendReaction: Boolean,
val userEventPermissions: UserEventPermissions,
val composerState: MessageComposerState,
val voiceMessageComposerState: VoiceMessageComposerState,
val timelineState: TimelineState,

24
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

@ -53,7 +53,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> { @@ -53,7 +53,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
aMessagesState(),
aMessagesState(hasNetworkConnection = false),
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
aMessagesState(userHasPermissionToSendMessage = false),
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
aMessagesState(showReinvitePrompt = true),
aMessagesState(
roomName = AsyncData.Uninitialized,
@ -93,10 +93,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> { @@ -93,10 +93,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
fun aMessagesState(
roomName: AsyncData<String> = AsyncData.Success("Room name"),
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
userHasPermissionToSendMessage: Boolean = true,
userHasPermissionToRedactOwn: Boolean = false,
userHasPermissionToRedactOther: Boolean = false,
userHasPermissionToSendReaction: Boolean = true,
userEventPermissions: UserEventPermissions = aUserEventPermissions(),
composerState: MessageComposerState = aMessageComposerState(
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
isFullScreen = false,
@ -122,10 +119,7 @@ fun aMessagesState( @@ -122,10 +119,7 @@ fun aMessagesState(
roomName = roomName,
roomAvatar = roomAvatar,
heroes = persistentListOf(),
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
userEventPermissions = userEventPermissions,
composerState = composerState,
voiceMessageComposerState = voiceMessageComposerState,
typingNotificationState = aTypingNotificationState(),
@ -145,6 +139,18 @@ fun aMessagesState( @@ -145,6 +139,18 @@ fun aMessagesState(
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(
target: ReactionSummaryState.Summary? = null,
eventSink: (ReactionSummaryEvents) -> Unit = {}

7
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

@ -154,10 +154,7 @@ fun MessagesView( @@ -154,10 +154,7 @@ fun MessagesView(
state.actionListState.eventSink(
ActionListEvents.ComputeForMessage(
event = event,
canRedactOwn = state.userHasPermissionToRedactOwn,
canRedactOther = state.userHasPermissionToRedactOther,
canSendMessage = state.userHasPermissionToSendMessage,
canSendReaction = state.userHasPermissionToSendReaction,
userEventPermissions = state.userEventPermissions,
)
)
}
@ -408,7 +405,7 @@ private fun MessagesViewComposerBottomSheetContents( @@ -408,7 +405,7 @@ private fun MessagesViewComposerBottomSheetContents(
subcomposing: Boolean,
state: MessagesState,
) {
if (state.userHasPermissionToSendMessage) {
if (state.userEventPermissions.canSendMessage) {
Column(modifier = Modifier.fillMaxWidth()) {
MentionSuggestionsPickerView(
modifier = Modifier

28
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/UserEventPermissions.kt

@ -0,0 +1,28 @@ @@ -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,
)

6
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt

@ -16,15 +16,13 @@ @@ -16,15 +16,13 @@
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
sealed interface ActionListEvents {
data object Clear : ActionListEvents
data class ComputeForMessage(
val event: TimelineItem.Event,
val canRedactOwn: Boolean,
val canRedactOther: Boolean,
val canSendMessage: Boolean,
val canSendReaction: Boolean,
val userEventPermissions: UserEventPermissions,
) : ActionListEvents
}

201
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt

@ -23,15 +23,17 @@ import androidx.compose.runtime.getValue @@ -23,15 +23,17 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.timeline.model.TimelineItem
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.TimelineItemPollContent
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.TimelineItemVoiceContent
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.libraries.architecture.Presenter
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@ -58,10 +60,7 @@ class ActionListPresenter @Inject constructor( @@ -58,10 +60,7 @@ class ActionListPresenter @Inject constructor(
ActionListEvents.Clear -> target.value = ActionListState.Target.None
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
timelineItem = event.event,
userCanRedactOwn = event.canRedactOwn,
userCanRedactOther = event.canRedactOther,
userCanSendMessage = event.canSendMessage,
userCanSendReaction = event.canSendReaction,
usersEventPermissions = event.userEventPermissions,
isDeveloperModeEnabled = isDeveloperModeEnabled,
target = target,
)
@ -76,136 +75,18 @@ class ActionListPresenter @Inject constructor( @@ -76,136 +75,18 @@ class ActionListPresenter @Inject constructor(
private fun CoroutineScope.computeForMessage(
timelineItem: TimelineItem.Event,
userCanRedactOwn: Boolean,
userCanRedactOther: Boolean,
userCanSendMessage: Boolean,
userCanSendReaction: Boolean,
usersEventPermissions: UserEventPermissions,
isDeveloperModeEnabled: Boolean,
target: MutableState<ActionListState.Target>
) = launch {
target.value = ActionListState.Target.Loading(timelineItem)
val canRedact = timelineItem.isMine && userCanRedactOwn || !timelineItem.isMine && userCanRedactOther
val actions =
when (timelineItem.content) {
is TimelineItemCallNotifyContent -> {
if (isDeveloperModeEnabled) {
listOf(TimelineItemAction.ViewSource)
} else {
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 &&
val actions = buildActions(
timelineItem = timelineItem,
usersEventPermissions = usersEventPermissions,
isDeveloperModeEnabled = isDeveloperModeEnabled,
)
val displayEmojiReactions = usersEventPermissions.canSendReaction &&
timelineItem.isRemote &&
timelineItem.content.canReact()
if (actions.isNotEmpty() || displayEmojiReactions) {
@ -219,3 +100,61 @@ class ActionListPresenter @Inject constructor( @@ -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
}
}
}

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt

@ -127,6 +127,7 @@ internal fun aTimelineItemEvent( @@ -127,6 +127,7 @@ internal fun aTimelineItemEvent(
transactionId: TransactionId? = null,
isMine: Boolean = false,
isEditable: Boolean = false,
canBeRepliedTo: Boolean = false,
senderDisplayName: String = "Sender",
displayNameAmbiguous: Boolean = false,
content: TimelineItemEventContent = aTimelineItemTextContent(),
@ -150,6 +151,7 @@ internal fun aTimelineItemEvent( @@ -150,6 +151,7 @@ internal fun aTimelineItemEvent(
sentTime = "12:34",
isMine = isMine,
isEditable = isEditable,
canBeRepliedTo = canBeRepliedTo,
senderProfile = aProfileTimelineDetailsReady(
displayName = senderDisplayName,
displayNameAmbiguous = displayNameAmbiguous,

3
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt

@ -77,7 +77,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt @@ -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.aTimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
import io.element.android.libraries.designsystem.components.EqualWidthColumn
import io.element.android.libraries.designsystem.components.avatar.Avatar
@ -148,7 +147,7 @@ fun TimelineItemEventRow( @@ -148,7 +147,7 @@ fun TimelineItemEventRow(
} else {
Spacer(modifier = Modifier.height(2.dp))
}
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.content.canBeRepliedTo()
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo
if (canReply) {
val state: SwipeableActionsState = rememberSwipeableActionsState()
val offset = state.offset.floatValue

1
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt

@ -76,6 +76,7 @@ class TimelineItemEventFactory @Inject constructor( @@ -76,6 +76,7 @@ class TimelineItemEventFactory @Inject constructor(
content = contentFactory.create(currentTimelineItem.event),
isMine = currentTimelineItem.event.isOwn,
isEditable = currentTimelineItem.event.isEditable,
canBeRepliedTo = currentTimelineItem.event.canBeRepliedTo,
sentTime = sentTime,
groupPosition = groupPosition,
reactionsState = currentTimelineItem.computeReactionsState(),

3
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt

@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model
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.TimelineItemRedactedContent
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.virtual.TimelineItemVirtualModel
@ -74,6 +75,7 @@ sealed interface TimelineItem { @@ -74,6 +75,7 @@ sealed interface TimelineItem {
val sentTime: String = "",
val isMine: Boolean = false,
val isEditable: Boolean,
val canBeRepliedTo: Boolean,
val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
val reactionsState: TimelineItemReactions,
val readReceiptState: TimelineItemReadReceipts,
@ -94,6 +96,7 @@ sealed interface TimelineItem { @@ -94,6 +96,7 @@ sealed interface TimelineItem {
val isSticker: Boolean = content is TimelineItemStickerContent
val isRemote = eventId != null
}
@Immutable

30
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt

@ -24,27 +24,27 @@ sealed interface TimelineItemEventContent { @@ -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 =
when (this) {
is TimelineItemTextBasedContent,
is TimelineItemStateContent,
is TimelineItemRedactedContent -> true
else -> false
}
this is TimelineItemTextBasedContent
/**
* Determine if the event content can be replied to.
* Note: it should match the logic in [io.element.android.features.messages.impl.actionlist.ActionListPresenter].
* Returns true if the event content can be forwarded.
*/
fun TimelineItemEventContent.canBeRepliedTo(): Boolean =
fun TimelineItemEventContent.canBeForwarded(): Boolean =
when (this) {
is TimelineItemRedactedContent,
is TimelineItemLegacyCallInviteContent,
is TimelineItemCallNotifyContent,
is TimelineItemStateContent -> false
else -> true
is TimelineItemTextBasedContent,
is TimelineItemImageContent,
is TimelineItemFileContent,
is TimelineItemAudioContent,
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
}
/**

23
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt

@ -137,8 +137,8 @@ class MessagesPresenterTest { @@ -137,8 +137,8 @@ class MessagesPresenterTest {
assertThat(initialState.roomName).isEqualTo(AsyncData.Success(""))
assertThat(initialState.roomAvatar)
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
assertThat(initialState.userEventPermissions.canSendMessage).isTrue()
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
assertThat(initialState.hasNetworkConnection).isTrue()
assertThat(initialState.snackbarMessage).isNull()
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
@ -787,7 +787,7 @@ class MessagesPresenterTest { @@ -787,7 +787,7 @@ class MessagesPresenterTest {
presenter.present()
}.test {
val state = awaitFirstItem()
assertThat(state.userHasPermissionToSendMessage).isTrue()
assertThat(state.userEventPermissions.canSendMessage).isTrue()
}
}
@ -811,9 +811,9 @@ class MessagesPresenterTest { @@ -811,9 +811,9 @@ class MessagesPresenterTest {
presenter.present()
}.test {
// Default value
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
assertThat(awaitItem().userEventPermissions.canSendMessage).isTrue()
skipItems(1)
assertThat(awaitItem().userHasPermissionToSendMessage).isFalse()
assertThat(awaitItem().userEventPermissions.canSendMessage).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@ -831,9 +831,9 @@ class MessagesPresenterTest { @@ -831,9 +831,9 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOwn }.last()
assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
assertThat(initialState.userHasPermissionToRedactOther).isFalse()
val initialState = consumeItemsUntilPredicate { it.userEventPermissions.canRedactOwn }.last()
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
assertThat(initialState.userEventPermissions.canRedactOther).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@ -851,9 +851,9 @@ class MessagesPresenterTest { @@ -851,9 +851,9 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOther }.last()
assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
assertThat(initialState.userHasPermissionToRedactOther).isTrue()
val initialState = consumeItemsUntilPredicate { it.userEventPermissions.canRedactOther }.last()
assertThat(initialState.userEventPermissions.canRedactOwn).isFalse()
assertThat(initialState.userEventPermissions.canRedactOther).isTrue()
cancelAndIgnoreRemainingEvents()
}
}
@ -963,6 +963,7 @@ class MessagesPresenterTest { @@ -963,6 +963,7 @@ class MessagesPresenterTest {
room = matrixRoom,
sessionPreferencesStore = sessionPreferencesStore,
)
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)

29
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt

@ -26,6 +26,7 @@ import androidx.compose.ui.test.onAllNodesWithContentDescription @@ -26,6 +26,7 @@ import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onLast
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
@ -43,7 +44,6 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc @@ -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.messagecomposer.aMessageComposerState
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.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineState
@ -175,10 +175,12 @@ class MessagesViewTest { @@ -175,10 +175,12 @@ class MessagesViewTest {
actionListState = anActionListState(
eventSink = eventsRecorder
),
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
userEventPermissions = UserEventPermissions(
canSendMessage = userHasPermissionToSendMessage,
canRedactOwn = userHasPermissionToRedactOwn,
canRedactOther = userHasPermissionToRedactOther,
canSendReaction = userHasPermissionToSendReaction,
),
)
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
rule.setMessagesView(
@ -189,10 +191,7 @@ class MessagesViewTest { @@ -189,10 +191,7 @@ class MessagesViewTest {
eventsRecorder.assertSingle(
ActionListEvents.ComputeForMessage(
event = timelineItem,
canRedactOwn = state.userHasPermissionToRedactOwn,
canRedactOther = state.userHasPermissionToRedactOther,
canSendMessage = state.userHasPermissionToSendMessage,
canSendReaction = state.userHasPermissionToSendReaction,
userEventPermissions = state.userEventPermissions,
)
)
}
@ -237,9 +236,11 @@ class MessagesViewTest { @@ -237,9 +236,11 @@ class MessagesViewTest {
private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
val eventsRecorder = EventsRecorder<MessagesEvents>()
val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true)
val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false)
val state = aMessagesState(
timelineState = aTimelineState(
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
timelineItems = persistentListOf(canBeRepliedEvent, cannotBeRepliedEvent),
timelineRoomInfo = aTimelineRoomInfo(
userHasPermissionToSendMessage = userHasPermissionToSendMessage
),
@ -249,10 +250,12 @@ class MessagesViewTest { @@ -249,10 +250,12 @@ class MessagesViewTest {
rule.setMessagesView(
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) {
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, timelineItem))
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, canBeRepliedEvent))
} else {
eventsRecorder.assertEmpty()
}

218
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt

@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode @@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
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.fixtures.aMessageEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
@ -31,6 +32,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI @@ -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.aTimelineItemVoiceContent
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.preferences.test.InMemoryAppPreferencesStore
import io.element.android.tests.testutils.WarmUpRule
@ -66,10 +68,12 @@ class ActionListPresenterTest { @@ -66,10 +68,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
@ -104,10 +108,12 @@ class ActionListPresenterTest { @@ -104,10 +108,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
@ -142,10 +148,12 @@ class ActionListPresenterTest { @@ -142,10 +148,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
@ -185,10 +193,12 @@ class ActionListPresenterTest { @@ -185,10 +193,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = false,
canSendReaction = true
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = false,
canSendReaction = true
)
)
)
// val loadingState = awaitItem()
@ -227,10 +237,12 @@ class ActionListPresenterTest { @@ -227,10 +237,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = false,
canRedactOther = true,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = true,
canSendMessage = true,
canSendReaction = true,
)
)
)
val successState = awaitItem()
@ -269,10 +281,12 @@ class ActionListPresenterTest { @@ -269,10 +281,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = false,
canRedactOther = true,
canSendMessage = true,
canSendReaction = false
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = true,
canSendMessage = true,
canSendReaction = false
)
)
)
val successState = awaitItem()
@ -310,10 +324,12 @@ class ActionListPresenterTest { @@ -310,10 +324,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
@ -353,10 +369,12 @@ class ActionListPresenterTest { @@ -353,10 +369,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
@ -396,10 +414,12 @@ class ActionListPresenterTest { @@ -396,10 +414,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
),
)
)
// val loadingState = awaitItem()
@ -437,10 +457,12 @@ class ActionListPresenterTest { @@ -437,10 +457,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = stateEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
@ -451,8 +473,6 @@ class ActionListPresenterTest { @@ -451,8 +473,6 @@ class ActionListPresenterTest {
event = stateEvent,
displayEmojiReactions = false,
actions = persistentListOf(
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
)
)
@ -476,26 +496,16 @@ class ActionListPresenterTest { @@ -476,26 +496,16 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = stateEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = stateEvent,
displayEmojiReactions = false,
actions = persistentListOf(
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
)
)
)
initialState.eventSink.invoke(ActionListEvents.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
}
}
@ -514,10 +524,12 @@ class ActionListPresenterTest { @@ -514,10 +524,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
// val loadingState = awaitItem()
@ -561,10 +573,12 @@ class ActionListPresenterTest { @@ -561,10 +573,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
@ -572,10 +586,12 @@ class ActionListPresenterTest { @@ -572,10 +586,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = redactedEvent,
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = false,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
awaitItem().run {
@ -595,16 +611,19 @@ class ActionListPresenterTest { @@ -595,16 +611,19 @@ class ActionListPresenterTest {
// No event id, so it's not sent yet
eventId = null,
isMine = true,
canBeRepliedTo = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null),
)
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
val successState = awaitItem()
@ -637,10 +656,12 @@ class ActionListPresenterTest { @@ -637,10 +656,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
val successState = awaitItem()
@ -675,10 +696,12 @@ class ActionListPresenterTest { @@ -675,10 +696,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
val successState = awaitItem()
@ -712,10 +735,12 @@ class ActionListPresenterTest { @@ -712,10 +735,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
val successState = awaitItem()
@ -742,15 +767,18 @@ class ActionListPresenterTest { @@ -742,15 +767,18 @@ class ActionListPresenterTest {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
isMine = true,
isEditable = false,
content = aTimelineItemVoiceContent(),
)
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
val successState = awaitItem()
@ -783,10 +811,12 @@ class ActionListPresenterTest { @@ -783,10 +811,12 @@ class ActionListPresenterTest {
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
)
val successState = awaitItem()

2
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt

@ -42,6 +42,7 @@ internal fun aMessageEvent( @@ -42,6 +42,7 @@ internal fun aMessageEvent(
transactionId: TransactionId? = null,
isMine: Boolean = true,
isEditable: Boolean = true,
canBeRepliedTo: Boolean = true,
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = null, isEdited = false),
inReplyTo: InReplyToDetails? = null,
isThreaded: Boolean = false,
@ -58,6 +59,7 @@ internal fun aMessageEvent( @@ -58,6 +59,7 @@ internal fun aMessageEvent(
sentTime = "",
isMine = isMine,
isEditable = isEditable,
canBeRepliedTo = canBeRepliedTo,
reactionsState = aTimelineItemReactions(count = 0),
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
localSendState = sendState,

1
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt

@ -46,6 +46,7 @@ class TimelineItemGrouperTest { @@ -46,6 +46,7 @@ class TimelineItemGrouperTest {
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
isEditable = false,
canBeRepliedTo = false,
inReplyTo = null,
isThreaded = false,
debugInfo = aTimelineItemDebugInfo(),

1
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt

@ -85,6 +85,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>( @@ -85,6 +85,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>(
eventId = eventId,
transactionId = null,
isEditable = false,
canBeRepliedTo = false,
isLocal = false,
isOwn = false,
isRemote = false,

1
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt

@ -26,6 +26,7 @@ data class EventTimelineItem( @@ -26,6 +26,7 @@ data class EventTimelineItem(
val eventId: EventId?,
val transactionId: TransactionId?,
val isEditable: Boolean,
val canBeRepliedTo: Boolean,
val isLocal: Boolean,
val isOwn: Boolean,
val isRemote: Boolean,

1
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt

@ -44,6 +44,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap @@ -44,6 +44,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
eventId = it.eventId()?.let(::EventId),
transactionId = it.transactionId()?.let(::TransactionId),
isEditable = it.isEditable(),
canBeRepliedTo = it.canBeRepliedTo(),
isLocal = it.isLocal(),
isOwn = it.isOwn(),
isRemote = it.isRemote(),

2
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt

@ -46,6 +46,7 @@ fun anEventTimelineItem( @@ -46,6 +46,7 @@ fun anEventTimelineItem(
eventId: EventId = AN_EVENT_ID,
transactionId: TransactionId? = null,
isEditable: Boolean = false,
canBeRepliedTo: Boolean = false,
isLocal: Boolean = false,
isOwn: Boolean = false,
isRemote: Boolean = false,
@ -61,6 +62,7 @@ fun anEventTimelineItem( @@ -61,6 +62,7 @@ fun anEventTimelineItem(
eventId = eventId,
transactionId = transactionId,
isEditable = isEditable,
canBeRepliedTo = canBeRepliedTo,
isLocal = isLocal,
isOwn = isOwn,
isRemote = isRemote,

Loading…
Cancel
Save