[Message Actions] Display debug info for events in the timeline (#555)

* Display debug info for events in the timeline on debug builds.

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-06-09 08:13:22 +02:00 committed by GitHub
parent 6b19ad380b
commit 688ab3bd5e
20 changed files with 371 additions and 34 deletions

View file

@ -208,11 +208,11 @@ koverMerged {
name = "Global minimum code coverage."
target = kotlinx.kover.api.VerificationTarget.ALL
bound {
minValue = 50
minValue = 55
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
// minValue to 25 and maxValue to 35.
maxValue = 60
maxValue = 65
counter = kotlinx.kover.api.CounterType.INSTRUCTION
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
}

1
changelog.d/554.feature Normal file
View file

@ -0,0 +1 @@
Created debug info screen for events in the timeline, it can be used only in debug builds.

View file

@ -34,6 +34,7 @@ import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode
import io.element.android.features.messages.impl.media.local.MediaInfo
import io.element.android.features.messages.impl.media.viewer.MediaViewerNode
import io.element.android.features.messages.impl.timeline.debug.EventDebugInfoNode
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
@ -41,8 +42,10 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import kotlinx.collections.immutable.ImmutableList
import kotlinx.parcelize.Parcelize
@ -72,6 +75,9 @@ class MessagesFlowNode @AssistedInject constructor(
@Parcelize
data class AttachmentPreview(val attachment: Attachment) : NavTarget
@Parcelize
data class EventDebugInfo(val eventId: EventId, val debugInfo: TimelineItemDebugInfo) : NavTarget
}
private val callback = plugins<MessagesEntryPoint.Callback>().firstOrNull()
@ -95,6 +101,10 @@ class MessagesFlowNode @AssistedInject constructor(
override fun onUserDataClicked(userId: UserId) {
callback?.onUserDataClicked(userId)
}
override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
}
}
createNode<MessagesNode>(buildContext, listOf(callback))
}
@ -110,6 +120,10 @@ class MessagesFlowNode @AssistedInject constructor(
val inputs = AttachmentsPreviewNode.Inputs(navTarget.attachment)
createNode<AttachmentsPreviewNode>(buildContext, listOf(inputs))
}
is NavTarget.EventDebugInfo -> {
val inputs = EventDebugInfoNode.Inputs(navTarget.eventId, navTarget.debugInfo)
createNode<EventDebugInfoNode>(buildContext, listOf(inputs))
}
}
}

View file

@ -28,7 +28,9 @@ import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import kotlinx.collections.immutable.ImmutableList
@ContributesNode(RoomScope::class)
@ -45,6 +47,7 @@ class MessagesNode @AssistedInject constructor(
fun onEventClicked(event: TimelineItem.Event)
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
fun onUserDataClicked(userId: UserId)
fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo)
}
private fun onRoomDetailsClicked() {
@ -63,6 +66,10 @@ class MessagesNode @AssistedInject constructor(
callback?.onUserDataClicked(userId)
}
private fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) {
callback?.onShowEventDebugInfoClicked(eventId, debugInfo)
}
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
@ -73,6 +80,7 @@ class MessagesNode @AssistedInject constructor(
onEventClicked = this::onEventClicked,
onPreviewAttachments = this::onPreviewAttachments,
onUserDataClicked = this::onUserDataClicked,
onItemDebugInfoClicked = this::onShowEventDebugInfoClicked,
modifier = modifier,
)
}

View file

@ -129,7 +129,7 @@ class MessagesPresenter @Inject constructor(
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState)
TimelineItemAction.Reply -> handleActionReply(targetEvent, composerState)
TimelineItemAction.Developer -> notImplementedYet()
TimelineItemAction.Developer -> Unit // Handled at UI level
TimelineItemAction.ReportContent -> notImplementedYet()
}
}

View file

@ -77,7 +77,9 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.launch
import timber.log.Timber
@ -92,6 +94,7 @@ fun MessagesView(
onEventClicked: (event: TimelineItem.Event) -> Unit,
onUserDataClicked: (UserId) -> Unit,
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit,
onItemDebugInfoClicked: (EventId, TimelineItemDebugInfo) -> Unit,
modifier: Modifier = Modifier,
) {
LogCompositions(tag = "MessagesScreen", msg = "Root")
@ -120,7 +123,12 @@ fun MessagesView(
fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) {
isMessageActionsBottomSheetVisible = false
state.eventSink(MessagesEvents.HandleAction(action, event))
when (action) {
is TimelineItemAction.Developer -> if (event.eventId != null && event.debugInfo != null) {
onItemDebugInfoClicked(event.eventId, event.debugInfo)
}
else -> state.eventSink(MessagesEvents.HandleAction(action, event))
}
}
fun onDismissActionListBottomSheet() {
@ -275,5 +283,6 @@ private fun ContentToPreview(state: MessagesState) {
onEventClicked = {},
onPreviewAttachments = {},
onUserDataClicked = {},
onItemDebugInfoClicked = { _, _ -> },
)
}

View file

@ -27,6 +27,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import kotlinx.collections.immutable.ImmutableList
@ -98,6 +99,7 @@ internal fun aTimelineItemEvent(
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
sendState: EventSendState = EventSendState.Sent(eventId),
inReplyTo: InReplyTo? = null,
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
): TimelineItem.Event {
return TimelineItem.Event(
id = eventId.value,
@ -116,5 +118,14 @@ internal fun aTimelineItemEvent(
groupPosition = groupPosition,
sendState = sendState,
inReplyTo = inReplyTo,
debugInfo = debugInfo,
)
}
internal fun aTimelineItemDebugInfo(
model: String = "Rust(Model())",
originalJson: String? = null,
latestEditedJson: String? = null,
) = TimelineItemDebugInfo(
model, originalJson, latestEditedJson
)

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2023 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
*
* http://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.timeline.debug
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
@ContributesNode(RoomScope::class)
class EventDebugInfoNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
) : Node(buildContext, plugins = plugins) {
data class Inputs(
val eventId: EventId,
val timelineItemDebugInfo: TimelineItemDebugInfo,
) : NodeInputs
private val inputs = inputs<Inputs>()
private fun onBackPressed() {
navigateUp()
}
@Composable
override fun View(modifier: Modifier) = with(inputs) {
EventDebugInfoView(
eventId = eventId,
model = timelineItemDebugInfo.model,
originalJson = timelineItemDebugInfo.originalJson,
latestEditedJson = timelineItemDebugInfo.latestEditedJson,
onBackPressed = ::onBackPressed
)
}
}

View file

@ -0,0 +1,193 @@
/*
* Copyright (c) 2023 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
*
* http://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.timeline.debug
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.getSystemService
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.EventId
/**
* Screen used to display debug info for events.
* It will only be available in debug builds.
*/
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun EventDebugInfoView(
eventId: EventId,
model: String,
originalJson: String?,
latestEditedJson: String?,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
isTest: Boolean = false,
) {
val sectionsInitiallyExpanded = isTest || LocalInspectionMode.current
Scaffold(
topBar = {
TopAppBar(
title = {
Text("Debug event info")
},
navigationIcon = { BackButton(onClick = onBackPressed) }
)
},
modifier = modifier
) { padding ->
LazyColumn(modifier = Modifier
.fillMaxWidth()
.padding(padding) // Window insets
.consumeWindowInsets(padding)
.padding(horizontal = 16.dp) // Internal padding
) {
item {
Column(Modifier.padding(vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text(text = "Event ID:")
CopyableText(text = eventId.value)
}
}
item {
CollapsibleSection(title = "Model:", text = model, initiallyExpanded = sectionsInitiallyExpanded)
}
if (originalJson != null) {
item {
CollapsibleSection(title = "Original JSON:", text = originalJson, initiallyExpanded = sectionsInitiallyExpanded)
}
}
if (latestEditedJson != null) {
item {
CollapsibleSection(title = "Latest edited JSON:", text = latestEditedJson, initiallyExpanded = sectionsInitiallyExpanded)
}
}
}
}
}
@Composable
private fun CollapsibleSection(
title: String,
text: String,
modifier: Modifier = Modifier,
initiallyExpanded: Boolean = false,
) {
var isExpanded by remember { mutableStateOf(initiallyExpanded) }
Column(modifier = modifier.fillMaxWidth()) {
Row(
modifier = Modifier
.clickable { isExpanded = !isExpanded }
.fillMaxWidth()
.padding(vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(title, modifier = Modifier.weight(1f))
Icon(
imageVector = if (isExpanded) Icons.Filled.ArrowDropUp else Icons.Filled.ArrowDropDown,
contentDescription = null
)
}
AnimatedVisibility(visible = isExpanded, enter = expandVertically(), exit = shrinkVertically()) {
CopyableText(text = text)
}
}
}
@Composable
private fun CopyableText(
text: String,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val clipboardManager = remember { requireNotNull(context.getSystemService<ClipboardManager>()) }
Box(
modifier
.clip(RoundedCornerShape(4.dp))
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(6.dp)
.clickable { clipboardManager.setPrimaryClip(ClipData.newPlainText("JSON", text)) }
) {
Text(text = text, fontFamily = FontFamily.Monospace, fontSize = 14.sp, modifier = Modifier.padding(8.dp))
}
}
@Preview
@Composable
internal fun EventDebugInfoViewPreviewLight() {
ElementPreviewLight {
ContentToPreview()
}
}
@Preview
@Composable
internal fun EventDebugInfoViewPreviewDark() {
ElementPreviewDark {
ContentToPreview()
}
}
@Composable
private fun ContentToPreview() {
EventDebugInfoView(
eventId = EventId("\$some-event-id"),
model = "Rust(\n\tModel()\n)",
originalJson = "{\"name\": \"original\"}",
latestEditedJson = "{\"name\": \"edited\"}",
onBackPressed = { }
)
}

View file

@ -82,6 +82,7 @@ class TimelineItemEventFactory @Inject constructor(
reactionsState = currentTimelineItem.computeReactionsState(),
sendState = currentTimelineItem.event.localSendState ?: EventSendState.NotSentYet,
inReplyTo = currentTimelineItem.event.inReplyTo(),
debugInfo = currentTimelineItem.event.debugInfo,
)
}

View file

@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import kotlinx.collections.immutable.ImmutableList
@ -61,6 +62,7 @@ sealed interface TimelineItem {
val reactionsState: TimelineItemReactions,
val sendState: EventSendState,
val inReplyTo: InReplyTo?,
val debugInfo: TimelineItemDebugInfo,
) : TimelineItem {
val showSenderInformation = groupPosition.isNew() && !isMine

View file

@ -20,23 +20,16 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.fixtures.aMessageEvent
import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
import io.element.android.features.messages.impl.actionlist.ActionListState
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.TimelineItemReactions
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.TimelineItemTextContent
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -60,7 +53,7 @@ class ActionListPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(true, TimelineItemRedactedContent)
val messageEvent = aMessageEvent(isMine = true, content = TimelineItemRedactedContent)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
@ -87,7 +80,7 @@ class ActionListPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(false, TimelineItemRedactedContent)
val messageEvent = aMessageEvent(isMine = false, content = TimelineItemRedactedContent)
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
// val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
@ -232,19 +225,3 @@ private fun aBuildMeta(
private fun anActionListPresenter(isBuildDebuggable: Boolean) = ActionListPresenter(buildMeta = aBuildMeta(isDebuggable = isBuildDebuggable))
private fun aMessageEvent(
isMine: Boolean,
content: TimelineItemEventContent,
) = TimelineItem.Event(
id = AN_EVENT_ID.value,
eventId = AN_EVENT_ID,
senderId = A_USER_ID,
senderDisplayName = A_USER_NAME,
senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME),
content = content,
sentTime = "",
isMine = isMine,
reactionsState = TimelineItemReactions(persistentListOf()),
sendState = EventSendState.Sent(AN_EVENT_ID),
inReplyTo = null,
)

View file

@ -22,12 +22,14 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.room.aTimelineItemDebugInfo
import kotlinx.collections.immutable.persistentListOf
internal fun aMessageEvent(
@ -35,6 +37,7 @@ internal fun aMessageEvent(
isMine: Boolean = true,
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
inReplyTo: InReplyTo? = null,
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
) = TimelineItem.Event(
id = eventId?.value.orEmpty(),
eventId = eventId,
@ -47,4 +50,5 @@ internal fun aMessageEvent(
reactionsState = TimelineItemReactions(persistentListOf()),
sendState = EventSendState.Sent(AN_EVENT_ID),
inReplyTo = inReplyTo,
debugInfo = debugInfo,
)

View file

@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventSendStat
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.aTimelineItemDebugInfo
import kotlinx.collections.immutable.toImmutableList
import org.junit.Test
@ -44,6 +45,7 @@ class TimelineItemGrouperTest {
reactionsState = TimelineItemReactions(emptyList<AggregatedReaction>().toImmutableList()),
sendState = EventSendState.Sent(AN_EVENT_ID),
inReplyTo = null,
debugInfo = aTimelineItemDebugInfo(),
)
private val aNonGroupableItem = aMessageEvent()
private val aNonGroupableItemNoEvent = TimelineItem.Virtual("virtual", aTimelineItemDaySeparatorModel("Today"))

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 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
*
* http://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.libraries.matrix.api.timeline.item
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class TimelineItemDebugInfo(
val model: String,
val originalJson: String?,
val latestEditedJson: String?,
) : Parcelable

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
data class EventTimelineItem(
val uniqueIdentifier: String,
@ -31,7 +32,8 @@ data class EventTimelineItem(
val sender: UserId,
val senderProfile: ProfileTimelineDetails,
val timestamp: Long,
val content: EventContent
val content: EventContent,
val debugInfo: TimelineItemDebugInfo,
) {
fun inReplyTo(): InReplyTo? {
return (content as? MessageContent)?.inReplyTo

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
@ -25,6 +26,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimeli
import org.matrix.rustcomponents.sdk.Reaction
import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo
import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails
class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) {
@ -42,7 +44,8 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
sender = UserId(it.sender()),
senderProfile = it.senderProfile().map(),
timestamp = it.timestamp().toLong(),
content = contentMapper.map(it.content())
content = contentMapper.map(it.content()),
debugInfo = it.debugInfo().map(),
)
}
}
@ -77,3 +80,11 @@ private fun List<Reaction>?.map(): List<EventReaction> {
)
} ?: emptyList()
}
private fun RustEventTimelineItemDebugInfo.map(): TimelineItemDebugInfo {
return TimelineItemDebugInfo(
model = model,
originalJson = originalJson,
latestEditedJson = latestEditJson,
)
}

View file

@ -22,15 +22,14 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
@ -100,6 +99,7 @@ fun anEventTimelineItem(
senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(),
timestamp: Long = 0L,
content: EventContent = aProfileChangeMessageContent(),
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
) = EventTimelineItem(
uniqueIdentifier = uniqueIdentifier,
eventId = eventId,
@ -113,6 +113,7 @@ fun anEventTimelineItem(
senderProfile = senderProfile,
timestamp = timestamp,
content = content,
debugInfo = debugInfo,
)
fun aProfileTimelineDetails(
@ -136,3 +137,11 @@ fun aProfileChangeMessageContent(
avatarUrl = avatarUrl,
prevAvatarUrl = prevAvatarUrl,
)
fun aTimelineItemDebugInfo(
model: String = "Rust(Model())",
originalJson: String? = null,
latestEditedJson: String? = null,
) = TimelineItemDebugInfo(
model, originalJson, latestEditedJson
)

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:43777d9d5cd310d53ea4ae8c05f86a96fd18540d46af5ee05b707f28ca1bd74b
size 35561

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a11c22f8f593d6ae7dd297dc67b89898cdf0f4b4036b16625fa6f9c5043a28e5
size 34689