Add threadInfo field to message like timeline events (#5930)
* Add `threadInfo` field to message like timeline events: - Polls - Stickers - UTDs * Add missing cases for `EventTimeline.threadInfo()`
This commit is contained in:
parent
602498a36b
commit
0b5c4fc8bb
10 changed files with 87 additions and 44 deletions
|
|
@ -220,6 +220,7 @@ class PollContentStateFactoryTest {
|
||||||
votes = votes,
|
votes = votes,
|
||||||
endTime = endTime,
|
endTime = endTime,
|
||||||
isEdited = false,
|
isEdited = false,
|
||||||
|
threadInfo = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun aPollContentState(
|
private fun aPollContentState(
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,11 @@ class DefaultPinnedMessagesBannerFormatterTest {
|
||||||
fun `Unable to decrypt content`() {
|
fun `Unable to decrypt content`() {
|
||||||
val expected = "Waiting for this message"
|
val expected = "Waiting for this message"
|
||||||
val senderName = "Someone"
|
val senderName = "Someone"
|
||||||
val message = createRoomEvent(false, senderName, UnableToDecryptContent(UnableToDecryptContent.Data.Unknown))
|
val message = createRoomEvent(
|
||||||
|
sentByYou = false,
|
||||||
|
senderDisplayName = senderName,
|
||||||
|
content = UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null)
|
||||||
|
)
|
||||||
val result = formatter.format(message)
|
val result = formatter.format(message)
|
||||||
assertThat(result).isEqualTo(expected)
|
assertThat(result).isEqualTo(expected)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,11 @@ class DefaultRoomLatestEventFormatterTest {
|
||||||
val expected = "Waiting for this message"
|
val expected = "Waiting for this message"
|
||||||
val senderName = "Someone"
|
val senderName = "Someone"
|
||||||
sequenceOf(false, true).forEach { isDm ->
|
sequenceOf(false, true).forEach { isDm ->
|
||||||
val message = createLatestEvent(false, senderName, UnableToDecryptContent(UnableToDecryptContent.Data.Unknown))
|
val message = createLatestEvent(
|
||||||
|
sentByYou = false,
|
||||||
|
senderDisplayName = senderName,
|
||||||
|
content = UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null),
|
||||||
|
)
|
||||||
val result = formatter.format(message, isDm)
|
val result = formatter.format(message, isDm)
|
||||||
if (isDm) {
|
if (isDm) {
|
||||||
assertThat(result).isEqualTo(expected)
|
assertThat(result).isEqualTo(expected)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ data class MessageContent(
|
||||||
val inReplyTo: InReplyTo?,
|
val inReplyTo: InReplyTo?,
|
||||||
val isEdited: Boolean,
|
val isEdited: Boolean,
|
||||||
val threadInfo: EventThreadInfo?,
|
val threadInfo: EventThreadInfo?,
|
||||||
val type: MessageType
|
val type: MessageType,
|
||||||
) : EventContent
|
) : EventContent
|
||||||
|
|
||||||
data object RedactedContent : EventContent
|
data object RedactedContent : EventContent
|
||||||
|
|
@ -36,6 +36,7 @@ data class StickerContent(
|
||||||
val body: String?,
|
val body: String?,
|
||||||
val info: ImageInfo,
|
val info: ImageInfo,
|
||||||
val source: MediaSource,
|
val source: MediaSource,
|
||||||
|
val threadInfo: EventThreadInfo?,
|
||||||
) : EventContent {
|
) : EventContent {
|
||||||
val bestDescription: String
|
val bestDescription: String
|
||||||
get() = body ?: filename
|
get() = body ?: filename
|
||||||
|
|
@ -49,10 +50,12 @@ data class PollContent(
|
||||||
val votes: ImmutableMap<String, ImmutableList<UserId>>,
|
val votes: ImmutableMap<String, ImmutableList<UserId>>,
|
||||||
val endTime: ULong?,
|
val endTime: ULong?,
|
||||||
val isEdited: Boolean,
|
val isEdited: Boolean,
|
||||||
|
val threadInfo: EventThreadInfo?,
|
||||||
) : EventContent
|
) : EventContent
|
||||||
|
|
||||||
data class UnableToDecryptContent(
|
data class UnableToDecryptContent(
|
||||||
val data: Data
|
val data: Data,
|
||||||
|
val threadInfo: EventThreadInfo?,
|
||||||
) : EventContent {
|
) : EventContent {
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed interface Data {
|
sealed interface Data {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,13 @@ data class EventTimelineItem(
|
||||||
return (content as? MessageContent)?.inReplyTo
|
return (content as? MessageContent)?.inReplyTo
|
||||||
}
|
}
|
||||||
|
|
||||||
fun threadInfo(): EventThreadInfo? = (content as? MessageContent)?.threadInfo
|
fun threadInfo(): EventThreadInfo? = when (content) {
|
||||||
|
is MessageContent -> content.threadInfo
|
||||||
|
is PollContent -> content.threadInfo
|
||||||
|
is StickerContent -> content.threadInfo
|
||||||
|
is UnableToDecryptContent -> content.threadInfo
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
fun hasNotLoadedInReplyTo(): Boolean {
|
fun hasNotLoadedInReplyTo(): Boolean {
|
||||||
val details = inReplyTo()
|
val details = inReplyTo()
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.impl.room.join.map
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import org.matrix.rustcomponents.sdk.EmbeddedEventDetails
|
import org.matrix.rustcomponents.sdk.EmbeddedEventDetails
|
||||||
|
import org.matrix.rustcomponents.sdk.MsgLikeContent
|
||||||
import org.matrix.rustcomponents.sdk.MsgLikeKind
|
import org.matrix.rustcomponents.sdk.MsgLikeKind
|
||||||
import org.matrix.rustcomponents.sdk.TimelineItemContent
|
import org.matrix.rustcomponents.sdk.TimelineItemContent
|
||||||
import org.matrix.rustcomponents.sdk.use
|
import org.matrix.rustcomponents.sdk.use
|
||||||
|
|
@ -68,37 +69,11 @@ class TimelineEventContentMapper(
|
||||||
when (val kind = it.content.kind) {
|
when (val kind = it.content.kind) {
|
||||||
is MsgLikeKind.Message -> {
|
is MsgLikeKind.Message -> {
|
||||||
val inReplyTo = it.content.inReplyTo
|
val inReplyTo = it.content.inReplyTo
|
||||||
val threadSummary = it.content.threadSummary?.use { summary ->
|
eventMessageMapper.map(
|
||||||
val numberOfReplies = summary.numReplies().toLong()
|
message = kind,
|
||||||
val latestEvent = summary.latestEvent()
|
inReplyTo = inReplyTo,
|
||||||
val details = when (latestEvent) {
|
threadInfo = extractThreadInfo(it.content)
|
||||||
is EmbeddedEventDetails.Unavailable -> AsyncData.Uninitialized
|
)
|
||||||
is EmbeddedEventDetails.Pending -> AsyncData.Loading()
|
|
||||||
is EmbeddedEventDetails.Error -> AsyncData.Failure(IllegalStateException(latestEvent.message))
|
|
||||||
is EmbeddedEventDetails.Ready -> {
|
|
||||||
AsyncData.Success(
|
|
||||||
EmbeddedEventInfo(
|
|
||||||
eventOrTransactionId = latestEvent.eventOrTransactionId.map(),
|
|
||||||
content = map(latestEvent.content),
|
|
||||||
senderId = UserId(latestEvent.sender),
|
|
||||||
senderProfile = latestEvent.senderProfile.map(),
|
|
||||||
timestamp = latestEvent.timestamp.toLong(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ThreadSummary(
|
|
||||||
latestEvent = details,
|
|
||||||
numberOfReplies = numberOfReplies,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val threadRootId = it.content.threadRoot?.let(::ThreadId)
|
|
||||||
val threadInfo = when {
|
|
||||||
threadSummary != null -> EventThreadInfo.ThreadRoot(threadSummary)
|
|
||||||
threadRootId != null -> EventThreadInfo.ThreadResponse(threadRootId)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
eventMessageMapper.map(kind, inReplyTo, threadInfo)
|
|
||||||
}
|
}
|
||||||
is MsgLikeKind.Redacted -> {
|
is MsgLikeKind.Redacted -> {
|
||||||
RedactedContent
|
RedactedContent
|
||||||
|
|
@ -114,11 +89,13 @@ class TimelineEventContentMapper(
|
||||||
}.toImmutableMap(),
|
}.toImmutableMap(),
|
||||||
endTime = kind.endTime,
|
endTime = kind.endTime,
|
||||||
isEdited = kind.hasBeenEdited,
|
isEdited = kind.hasBeenEdited,
|
||||||
|
threadInfo = extractThreadInfo(it.content),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MsgLikeKind.UnableToDecrypt -> {
|
is MsgLikeKind.UnableToDecrypt -> {
|
||||||
UnableToDecryptContent(
|
UnableToDecryptContent(
|
||||||
data = kind.msg.map()
|
data = kind.msg.map(),
|
||||||
|
threadInfo = extractThreadInfo(it.content),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MsgLikeKind.Sticker -> {
|
is MsgLikeKind.Sticker -> {
|
||||||
|
|
@ -127,6 +104,7 @@ class TimelineEventContentMapper(
|
||||||
body = null,
|
body = null,
|
||||||
info = kind.info.map(),
|
info = kind.info.map(),
|
||||||
source = kind.source.map(),
|
source = kind.source.map(),
|
||||||
|
threadInfo = extractThreadInfo(it.content),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MsgLikeKind.Other -> UnknownContent
|
is MsgLikeKind.Other -> UnknownContent
|
||||||
|
|
@ -159,6 +137,43 @@ class TimelineEventContentMapper(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun extractThreadInfo(content: MsgLikeContent): EventThreadInfo? {
|
||||||
|
val threadSummary = extractThreadSummary(content.threadSummary)
|
||||||
|
val threadRootId = content.threadRoot?.let(::ThreadId)
|
||||||
|
return when {
|
||||||
|
threadSummary != null -> EventThreadInfo.ThreadRoot(threadSummary)
|
||||||
|
threadRootId != null -> EventThreadInfo.ThreadResponse(threadRootId)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractThreadSummary(threadSummary: org.matrix.rustcomponents.sdk.ThreadSummary?): ThreadSummary? {
|
||||||
|
return threadSummary?.use { summary ->
|
||||||
|
val numberOfReplies = summary.numReplies().toLong()
|
||||||
|
val latestEvent = summary.latestEvent()
|
||||||
|
val details = when (latestEvent) {
|
||||||
|
is EmbeddedEventDetails.Unavailable -> AsyncData.Uninitialized
|
||||||
|
is EmbeddedEventDetails.Pending -> AsyncData.Loading()
|
||||||
|
is EmbeddedEventDetails.Error -> AsyncData.Failure(IllegalStateException(latestEvent.message))
|
||||||
|
is EmbeddedEventDetails.Ready -> {
|
||||||
|
AsyncData.Success(
|
||||||
|
EmbeddedEventInfo(
|
||||||
|
eventOrTransactionId = latestEvent.eventOrTransactionId.map(),
|
||||||
|
content = map(latestEvent.content),
|
||||||
|
senderId = UserId(latestEvent.sender),
|
||||||
|
senderProfile = latestEvent.senderProfile.map(),
|
||||||
|
timestamp = latestEvent.timestamp.toLong(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ThreadSummary(
|
||||||
|
latestEvent = details,
|
||||||
|
numberOfReplies = numberOfReplies,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RustMembershipChange.map(): MembershipChange {
|
private fun RustMembershipChange.map(): MembershipChange {
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,13 @@ fun aStickerContent(
|
||||||
info: ImageInfo,
|
info: ImageInfo,
|
||||||
mediaSource: MediaSource,
|
mediaSource: MediaSource,
|
||||||
body: String? = null,
|
body: String? = null,
|
||||||
|
threadInfo: EventThreadInfo? = null,
|
||||||
) = StickerContent(
|
) = StickerContent(
|
||||||
filename = filename,
|
filename = filename,
|
||||||
body = body,
|
body = body,
|
||||||
info = info,
|
info = info,
|
||||||
source = mediaSource,
|
source = mediaSource,
|
||||||
|
threadInfo = threadInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aTimelineItemDebugInfo(
|
fun aTimelineItemDebugInfo(
|
||||||
|
|
@ -148,6 +150,7 @@ fun aPollContent(
|
||||||
votes: ImmutableMap<String, ImmutableList<UserId>> = persistentMapOf(),
|
votes: ImmutableMap<String, ImmutableList<UserId>> = persistentMapOf(),
|
||||||
endTime: ULong? = null,
|
endTime: ULong? = null,
|
||||||
isEdited: Boolean = false,
|
isEdited: Boolean = false,
|
||||||
|
threadInfo: EventThreadInfo? = null,
|
||||||
) = PollContent(
|
) = PollContent(
|
||||||
question = question,
|
question = question,
|
||||||
kind = kind,
|
kind = kind,
|
||||||
|
|
@ -156,4 +159,5 @@ fun aPollContent(
|
||||||
votes = votes,
|
votes = votes,
|
||||||
endTime = endTime,
|
endTime = endTime,
|
||||||
isEdited = isEdited,
|
isEdited = isEdited,
|
||||||
|
threadInfo = threadInfo,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ open class InReplyToDetailsProvider : PreviewParameterProvider<InReplyToDetails>
|
||||||
votes = persistentMapOf(),
|
votes = persistentMapOf(),
|
||||||
endTime = null,
|
endTime = null,
|
||||||
isEdited = false,
|
isEdited = false,
|
||||||
|
threadInfo = null,
|
||||||
),
|
),
|
||||||
).map {
|
).map {
|
||||||
aInReplyToDetails(
|
aInReplyToDetails(
|
||||||
|
|
@ -116,7 +117,7 @@ class InReplyToDetailsInformativeProvider : InReplyToDetailsProvider() {
|
||||||
override val values: Sequence<InReplyToDetails>
|
override val values: Sequence<InReplyToDetails>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
RedactedContent,
|
RedactedContent,
|
||||||
UnableToDecryptContent(UnableToDecryptContent.Data.Unknown),
|
UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null),
|
||||||
).map {
|
).map {
|
||||||
aInReplyToDetails(
|
aInReplyToDetails(
|
||||||
eventContent = it,
|
eventContent = it,
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,8 @@ class InReplyToMetadataKtTest {
|
||||||
filename = "filename",
|
filename = "filename",
|
||||||
body = "body",
|
body = "body",
|
||||||
info = anImageInfo(),
|
info = anImageInfo(),
|
||||||
source = aMediaSource(url = "url")
|
source = aMediaSource(url = "url"),
|
||||||
|
threadInfo = null,
|
||||||
)
|
)
|
||||||
).metadata(hideImage = false)
|
).metadata(hideImage = false)
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -161,7 +162,8 @@ class InReplyToMetadataKtTest {
|
||||||
filename = "filename",
|
filename = "filename",
|
||||||
body = "body",
|
body = "body",
|
||||||
info = anImageInfo(),
|
info = anImageInfo(),
|
||||||
source = aMediaSource(url = "url")
|
source = aMediaSource(url = "url"),
|
||||||
|
threadInfo = null,
|
||||||
)
|
)
|
||||||
).metadata(hideImage = true)
|
).metadata(hideImage = true)
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -445,7 +447,10 @@ class InReplyToMetadataKtTest {
|
||||||
fun `unable to decrypt content`() = runTest {
|
fun `unable to decrypt content`() = runTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
anInReplyToDetailsReady(
|
anInReplyToDetailsReady(
|
||||||
eventContent = UnableToDecryptContent(UnableToDecryptContent.Data.Unknown)
|
eventContent = UnableToDecryptContent(
|
||||||
|
data = UnableToDecryptContent.Data.Unknown,
|
||||||
|
threadInfo = null,
|
||||||
|
),
|
||||||
).metadata(hideImage = false)
|
).metadata(hideImage = false)
|
||||||
}.test {
|
}.test {
|
||||||
awaitItem().let {
|
awaitItem().let {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ class DefaultEventItemFactoryTest {
|
||||||
),
|
),
|
||||||
mediaSource = MediaSource("")
|
mediaSource = MediaSource("")
|
||||||
),
|
),
|
||||||
UnableToDecryptContent(UnableToDecryptContent.Data.Unknown),
|
UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null),
|
||||||
UnknownContent,
|
UnknownContent,
|
||||||
)
|
)
|
||||||
contents.forEach {
|
contents.forEach {
|
||||||
|
|
@ -397,8 +397,8 @@ class DefaultEventItemFactoryTest {
|
||||||
height = 1L,
|
height = 1L,
|
||||||
width = 2L,
|
width = 2L,
|
||||||
blurhash = null,
|
blurhash = null,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue