Add thread decoration with latest event details (#5355)
* Add thread decoration with latest event details * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
5cadd37fa6
commit
0a5c178fe8
106 changed files with 554 additions and 282 deletions
|
|
@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.components.customreact
|
|||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
||||
|
|
@ -328,7 +329,10 @@ class MessagesPresenter(
|
|||
val displayThreads = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
if (displayThreads) {
|
||||
// Get either the thread id this event is in, or the event id if it's not in a thread so we can start one
|
||||
val threadId = targetEvent.threadInfo.threadRootId ?: targetEvent.eventId!!.toThreadId()
|
||||
val threadId = when (targetEvent.threadInfo) {
|
||||
is TimelineItemThreadInfo.ThreadResponse -> targetEvent.threadInfo.threadRootId
|
||||
is TimelineItemThreadInfo.ThreadRoot, null -> targetEvent.eventId?.toThreadId()
|
||||
} ?: return@launch
|
||||
navigator.onOpenThread(threadId, null)
|
||||
} else {
|
||||
handleActionReply(targetEvent, composerState, timelineProtectionState)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
|
|||
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailure
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
||||
|
|
@ -174,7 +175,7 @@ class DefaultActionListPresenter(
|
|||
add(TimelineItemAction.ReplyInThread)
|
||||
add(TimelineItemAction.Reply)
|
||||
} else {
|
||||
if (!isThreadsEnabled && timelineItem.threadInfo.threadRootId != null) {
|
||||
if (!isThreadsEnabled && timelineItem.threadInfo is TimelineItemThreadInfo.ThreadResponse) {
|
||||
// If threads are not enabled, we can reply in a thread if the item is already in the thread
|
||||
add(TimelineItemAction.ReplyInThread)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
|||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.anAggregatedReaction
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||
|
|
@ -32,7 +33,6 @@ import io.element.android.libraries.matrix.api.core.UniqueId
|
|||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
|
|
@ -146,7 +146,7 @@ internal fun aTimelineItemEvent(
|
|||
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
|
||||
sendState: LocalEventSendState? = null,
|
||||
inReplyTo: InReplyToDetails? = null,
|
||||
threadInfo: EventThreadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo: TimelineItemThreadInfo? = null,
|
||||
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
|
||||
timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(),
|
||||
readReceiptState: TimelineItemReadReceipts = aTimelineItemReadReceipts(),
|
||||
|
|
|
|||
|
|
@ -53,8 +53,6 @@ import io.element.android.libraries.ui.utils.time.isTalkbackActive
|
|||
private val BUBBLE_RADIUS = 12.dp
|
||||
private val avatarRadius = AvatarSize.TimelineSender.dp / 2
|
||||
|
||||
// Design says: The maximum width of a bubble is still 3/4 of the screen width. But try with 78% now.
|
||||
private const val BUBBLE_WIDTH_RATIO = 0.78f
|
||||
private val MIN_BUBBLE_WIDTH = 80.dp
|
||||
|
||||
@Composable
|
||||
|
|
@ -66,34 +64,6 @@ fun MessageEventBubble(
|
|||
modifier: Modifier = Modifier,
|
||||
content: @Composable BoxScope.() -> Unit = {},
|
||||
) {
|
||||
fun bubbleShape(): Shape {
|
||||
val topLeftCorner = if (state.cutTopStart) 0.dp else BUBBLE_RADIUS
|
||||
return when (state.groupPosition) {
|
||||
TimelineItemGroupPosition.First -> if (state.isMine) {
|
||||
RoundedCornerShape(BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS)
|
||||
} else {
|
||||
RoundedCornerShape(topLeftCorner, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
|
||||
}
|
||||
TimelineItemGroupPosition.Middle -> if (state.isMine) {
|
||||
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, 0.dp, BUBBLE_RADIUS)
|
||||
} else {
|
||||
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
|
||||
}
|
||||
TimelineItemGroupPosition.Last -> if (state.isMine) {
|
||||
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS)
|
||||
} else {
|
||||
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, BUBBLE_RADIUS)
|
||||
}
|
||||
TimelineItemGroupPosition.None ->
|
||||
RoundedCornerShape(
|
||||
topLeftCorner,
|
||||
BUBBLE_RADIUS,
|
||||
BUBBLE_RADIUS,
|
||||
BUBBLE_RADIUS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val clickableModifier = if (isTalkbackActive()) {
|
||||
Modifier
|
||||
} else {
|
||||
|
|
@ -108,11 +78,8 @@ fun MessageEventBubble(
|
|||
}
|
||||
|
||||
// Ignore state.isHighlighted for now, we need a design decision on it.
|
||||
val backgroundBubbleColor = when {
|
||||
state.isMine -> ElementTheme.colors.messageFromMeBackground
|
||||
else -> ElementTheme.colors.messageFromOtherBackground
|
||||
}
|
||||
val bubbleShape = bubbleShape()
|
||||
val backgroundBubbleColor = MessageEventBubbleDefaults.backgroundBubbleColor(state.isMine)
|
||||
val bubbleShape = remember(state) { MessageEventBubbleDefaults.shape(state.cutTopStart, state.groupPosition, state.isMine) }
|
||||
val radiusPx = (avatarRadius + SENDER_AVATAR_BORDER_WIDTH).toPx()
|
||||
val yOffsetPx = -(NEGATIVE_MARGIN_FOR_BUBBLE + avatarRadius).toPx()
|
||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||
|
|
@ -147,7 +114,7 @@ fun MessageEventBubble(
|
|||
.testTag(TestTags.messageBubble)
|
||||
.widthIn(
|
||||
min = MIN_BUBBLE_WIDTH,
|
||||
max = (constraints.maxWidth * BUBBLE_WIDTH_RATIO)
|
||||
max = (constraints.maxWidth * MessageEventBubbleDefaults.BUBBLE_WIDTH_RATIO)
|
||||
.toInt()
|
||||
.toDp()
|
||||
)
|
||||
|
|
@ -157,6 +124,48 @@ fun MessageEventBubble(
|
|||
}
|
||||
}
|
||||
|
||||
object MessageEventBubbleDefaults {
|
||||
fun shape(cutTopStart: Boolean, groupPosition: TimelineItemGroupPosition, isMine: Boolean): Shape {
|
||||
val topLeftCorner = if (cutTopStart) 0.dp else BUBBLE_RADIUS
|
||||
return when (groupPosition) {
|
||||
TimelineItemGroupPosition.First -> if (isMine) {
|
||||
RoundedCornerShape(BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS)
|
||||
} else {
|
||||
RoundedCornerShape(topLeftCorner, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
|
||||
}
|
||||
TimelineItemGroupPosition.Middle -> if (isMine) {
|
||||
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, 0.dp, BUBBLE_RADIUS)
|
||||
} else {
|
||||
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
|
||||
}
|
||||
TimelineItemGroupPosition.Last -> if (isMine) {
|
||||
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS)
|
||||
} else {
|
||||
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, BUBBLE_RADIUS)
|
||||
}
|
||||
TimelineItemGroupPosition.None ->
|
||||
RoundedCornerShape(
|
||||
topLeftCorner,
|
||||
BUBBLE_RADIUS,
|
||||
BUBBLE_RADIUS,
|
||||
BUBBLE_RADIUS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun backgroundBubbleColor(isMine: Boolean): Color {
|
||||
return if (isMine) {
|
||||
ElementTheme.colors.messageFromMeBackground
|
||||
} else {
|
||||
ElementTheme.colors.messageFromOtherBackground
|
||||
}
|
||||
}
|
||||
|
||||
// Design says: The maximum width of a bubble is still 3/4 of the screen width. But try with 78% now.
|
||||
const val BUBBLE_WIDTH_RATIO = 0.78f
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessageEventBubblePreview(@PreviewParameter(BubbleStateProvider::class) state: BubbleState) = ElementPreview {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.foundation.gestures.draggable
|
|||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -23,6 +24,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -34,6 +37,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.platform.ViewConfiguration
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
|
|
@ -43,6 +47,7 @@ import androidx.compose.ui.semantics.hideFromAccessibility
|
|||
import androidx.compose.ui.semantics.isTraversalGroup
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.traversalIndex
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -61,6 +66,7 @@ import io.element.android.features.messages.impl.timeline.components.receipt.Rea
|
|||
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
|
||||
|
|
@ -78,25 +84,28 @@ 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
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.modifiers.niceClickable
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.swipe.SwipeableActionsState
|
||||
import io.element.android.libraries.designsystem.swipe.rememberSwipeableActionsState
|
||||
import io.element.android.libraries.designsystem.text.toPx
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toThreadId
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EmbeddedEventInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
|
|
@ -256,22 +265,22 @@ fun TimelineItemEventRow(
|
|||
)
|
||||
}
|
||||
|
||||
if (displayThreadSummaries && timelineMode !is Timeline.Mode.Thread) {
|
||||
event.threadInfo.threadSummary?.let { threadSummary ->
|
||||
val threadPart = stringResource(CommonStrings.common_thread)
|
||||
val numberOfReplies = threadSummary.numberOfReplies.toInt().let { replies ->
|
||||
pluralStringResource(CommonPlurals.common_replies, replies, replies)
|
||||
if (displayThreadSummaries && timelineMode !is Timeline.Mode.Thread && event.threadInfo is TimelineItemThreadInfo.ThreadRoot) {
|
||||
ThreadSummaryView(
|
||||
modifier = if (event.isMine) {
|
||||
Modifier.align(Alignment.End).padding(end = 16.dp)
|
||||
} else {
|
||||
if (timelineRoomInfo.isDm) Modifier else Modifier.padding(start = 16.dp)
|
||||
}.padding(top = 2.dp),
|
||||
threadSummary = event.threadInfo.summary,
|
||||
latestEventText = event.threadInfo.latestEventText,
|
||||
isOutgoing = event.isMine,
|
||||
onClick = {
|
||||
event.eventId?.let {
|
||||
eventSink(TimelineEvents.OpenThread(it.toThreadId(), null))
|
||||
}
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 2.dp)
|
||||
.align(if (event.isMine) Alignment.End else Alignment.Start),
|
||||
text = "$threadPart - $numberOfReplies",
|
||||
size = ButtonSize.Small,
|
||||
onClick = {
|
||||
eventSink(TimelineEvents.OpenThread(event.eventId!!.toThreadId(), null))
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Read receipts / Send state
|
||||
|
|
@ -288,6 +297,79 @@ fun TimelineItemEventRow(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThreadSummaryView(
|
||||
threadSummary: ThreadSummary,
|
||||
latestEventText: String?,
|
||||
isOutgoing: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BoxWithConstraints(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.then(if (!isOutgoing) Modifier.padding(start = 16.dp) else Modifier)
|
||||
.graphicsLayer {
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
clip = true
|
||||
}
|
||||
.background(MessageEventBubbleDefaults.backgroundBubbleColor(isOutgoing))
|
||||
.niceClickable(onClick)
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp)
|
||||
.widthIn(max = (maxWidth - 24.dp) * MessageEventBubbleDefaults.BUBBLE_WIDTH_RATIO),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = CompoundIcons.ThreadsSolid(),
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = pluralStringResource(CommonPlurals.common_replies, threadSummary.numberOfReplies.toInt(), threadSummary.numberOfReplies),
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
threadSummary.latestEvent.dataOrNull()?.let { latestEvent ->
|
||||
val avatarData = AvatarData(
|
||||
id = latestEvent.senderId.value,
|
||||
name = latestEvent.senderProfile.getDisplayName(),
|
||||
url = latestEvent.senderProfile.getAvatarUrl(),
|
||||
size = AvatarSize.TimelineThreadLatestEventSender,
|
||||
)
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
avatarType = AvatarType.User,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = latestEvent.senderProfile.getDisambiguatedDisplayName(latestEvent.senderId),
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
latestEventText?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Impact ViewConfiguration.touchSlop by [sensitivityFactor].
|
||||
* Inspired from https://issuetracker.google.com/u/1/issues/269627294.
|
||||
|
|
@ -694,7 +776,7 @@ private fun MessageEventBubbleContent(
|
|||
else -> ContentPadding.Textual
|
||||
}
|
||||
CommonLayout(
|
||||
showThreadDecoration = timelineMode !is Timeline.Mode.Thread && event.threadInfo.threadRootId != null,
|
||||
showThreadDecoration = timelineMode !is Timeline.Mode.Thread && event.threadInfo is TimelineItemThreadInfo.ThreadResponse,
|
||||
timestampPosition = timestampPosition,
|
||||
paddingBehaviour = paddingBehaviour,
|
||||
inReplyToDetails = event.inReplyTo,
|
||||
|
|
@ -746,9 +828,27 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview {
|
|||
" hopefully can be manually adjusted to test different behaviors."
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
threadInfo = EventThreadInfo(
|
||||
threadRootId = ThreadId("\$thread-root-id"),
|
||||
threadSummary = ThreadSummary(AsyncData.Uninitialized, numberOfReplies = 20L)
|
||||
threadInfo = TimelineItemThreadInfo.ThreadRoot(
|
||||
latestEventText = "This is the latest message in the thread",
|
||||
summary = ThreadSummary(AsyncData.Success(
|
||||
EmbeddedEventInfo(
|
||||
eventOrTransactionId = EventOrTransactionId.Event(EventId("\$event-id")),
|
||||
content = MessageContent(
|
||||
body = "This is the latest message in the thread",
|
||||
inReplyTo = null,
|
||||
isEdited = false,
|
||||
threadInfo = null,
|
||||
type = TextMessageType("This is the latest message in the thread", null)
|
||||
),
|
||||
senderId = UserId("@user:id"),
|
||||
senderProfile = ProfileTimelineDetails.Ready(
|
||||
displayName = "Alice",
|
||||
avatarUrl = null,
|
||||
displayNameAmbiguous = false,
|
||||
),
|
||||
timestamp = 0L,
|
||||
)
|
||||
), numberOfReplies = 20L)
|
||||
)
|
||||
),
|
||||
displayThreadSummaries = true,
|
||||
|
|
@ -756,3 +856,40 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ThreadSummaryViewPreview() {
|
||||
ElementPreview {
|
||||
val body = "This is the latest message in the thread"
|
||||
val threadSummary = ThreadSummary(
|
||||
AsyncData.Success(
|
||||
EmbeddedEventInfo(
|
||||
eventOrTransactionId = EventOrTransactionId.Event(EventId("\$event-id")),
|
||||
content = MessageContent(
|
||||
body = body,
|
||||
inReplyTo = null,
|
||||
isEdited = false,
|
||||
threadInfo = null,
|
||||
type = TextMessageType(body, null)
|
||||
),
|
||||
senderId = UserId("@user:id"),
|
||||
senderProfile = ProfileTimelineDetails.Ready(
|
||||
displayName = "Alice",
|
||||
avatarUrl = null,
|
||||
displayNameAmbiguous = true,
|
||||
),
|
||||
timestamp = 0L,
|
||||
)
|
||||
),
|
||||
numberOfReplies = 12,
|
||||
)
|
||||
|
||||
ThreadSummaryView(
|
||||
threadSummary = threadSummary,
|
||||
latestEventText = "Some event with a very long text that should get clipped",
|
||||
isOutgoing = true,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
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.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsProvider
|
||||
|
||||
|
|
@ -58,10 +58,7 @@ internal fun TimelineItemEventRowWithReplyContentToPreview(
|
|||
),
|
||||
inReplyTo = inReplyToDetails,
|
||||
displayNameAmbiguous = displayNameAmbiguous,
|
||||
threadInfo = EventThreadInfo(
|
||||
threadRootId = ThreadId("\$thread-root-id"),
|
||||
threadSummary = null,
|
||||
),
|
||||
threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = ThreadId("\$thread-root-id")),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
|
||||
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.event.CallNotifyContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
|
||||
|
|
@ -20,6 +23,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInv
|
|||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
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.api.timeline.item.event.RedactedContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
|
||||
|
|
@ -27,6 +31,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.StickerConten
|
|||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
|
||||
|
||||
@Inject
|
||||
class TimelineItemContentFactory(
|
||||
|
|
@ -40,26 +45,53 @@ class TimelineItemContentFactory(
|
|||
private val stateFactory: TimelineItemContentStateFactory,
|
||||
private val failedToParseMessageFactory: TimelineItemContentFailedToParseMessageFactory,
|
||||
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory,
|
||||
private val currentSessionIdHolder: CurrentSessionIdHolder,
|
||||
) {
|
||||
suspend fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
|
||||
return when (val itemContent = eventTimelineItem.content) {
|
||||
return create(
|
||||
itemContent = eventTimelineItem.content,
|
||||
eventId = eventTimelineItem.eventId,
|
||||
isEditable = eventTimelineItem.isEditable,
|
||||
sender = eventTimelineItem.sender,
|
||||
senderProfile = eventTimelineItem.senderProfile,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun create(
|
||||
itemContent: EventContent,
|
||||
eventId: EventId?,
|
||||
isEditable: Boolean,
|
||||
sender: UserId,
|
||||
senderProfile: ProfileTimelineDetails,
|
||||
): TimelineItemEventContent {
|
||||
val isOutgoing = currentSessionIdHolder.current == sender
|
||||
return when (itemContent) {
|
||||
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
|
||||
is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent)
|
||||
is MessageContent -> {
|
||||
val senderDisambiguatedDisplayName = eventTimelineItem.senderProfile.getDisambiguatedDisplayName(eventTimelineItem.sender)
|
||||
val senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(sender)
|
||||
messageFactory.create(
|
||||
content = itemContent,
|
||||
senderDisambiguatedDisplayName = senderDisambiguatedDisplayName,
|
||||
eventId = eventTimelineItem.eventId,
|
||||
eventId = eventId,
|
||||
)
|
||||
}
|
||||
is ProfileChangeContent -> profileChangeFactory.create(eventTimelineItem)
|
||||
is ProfileChangeContent -> {
|
||||
val senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(sender)
|
||||
profileChangeFactory.create(itemContent, isOutgoing, sender, senderDisambiguatedDisplayName)
|
||||
}
|
||||
is RedactedContent -> redactedMessageFactory.create(itemContent)
|
||||
is RoomMembershipContent -> roomMembershipFactory.create(eventTimelineItem)
|
||||
is RoomMembershipContent -> {
|
||||
val senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(sender)
|
||||
roomMembershipFactory.create(itemContent, isOutgoing, sender, senderDisambiguatedDisplayName)
|
||||
}
|
||||
is LegacyCallInviteContent -> TimelineItemLegacyCallInviteContent
|
||||
is StateContent -> stateFactory.create(eventTimelineItem)
|
||||
is StateContent -> {
|
||||
val senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(sender)
|
||||
stateFactory.create(itemContent, isOutgoing, sender, senderDisambiguatedDisplayName)
|
||||
}
|
||||
is StickerContent -> stickerFactory.create(itemContent)
|
||||
is PollContent -> pollFactory.create(eventTimelineItem, itemContent)
|
||||
is PollContent -> pollFactory.create(eventId, isEditable, isOutgoing, itemContent)
|
||||
is UnableToDecryptContent -> utdFactory.create(itemContent)
|
||||
is CallNotifyContent -> TimelineItemRtcNotificationContent()
|
||||
is UnknownContent -> TimelineItemUnknownContent
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import dev.zacsweers.metro.Inject
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
|
||||
@Inject
|
||||
|
|
@ -19,14 +19,16 @@ class TimelineItemContentPollFactory(
|
|||
private val pollContentStateFactory: PollContentStateFactory,
|
||||
) {
|
||||
suspend fun create(
|
||||
event: EventTimelineItem,
|
||||
eventId: EventId?,
|
||||
isEditable: Boolean,
|
||||
isOwn: Boolean,
|
||||
content: PollContent,
|
||||
): TimelineItemEventContent {
|
||||
val pollContentState = pollContentStateFactory.create(event, content)
|
||||
val pollContentState = pollContentStateFactory.create(eventId, isEditable, isOwn, content)
|
||||
return TimelineItemPollContent(
|
||||
isMine = pollContentState.isMine,
|
||||
isEditable = pollContentState.isPollEditable,
|
||||
eventId = event.eventId,
|
||||
eventId = eventId,
|
||||
question = pollContentState.question,
|
||||
answerItems = pollContentState.answerItems,
|
||||
pollKind = pollContentState.pollKind,
|
||||
|
|
|
|||
|
|
@ -12,14 +12,15 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
|
||||
@Inject
|
||||
class TimelineItemContentProfileChangeFactory(
|
||||
private val timelineEventFormatter: TimelineEventFormatter,
|
||||
) {
|
||||
fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
|
||||
val text = timelineEventFormatter.format(eventTimelineItem)
|
||||
fun create(content: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): TimelineItemEventContent {
|
||||
val text = timelineEventFormatter.format(content, isOutgoing, sender, senderDisambiguatedDisplayName)
|
||||
return TimelineItemProfileChangeContent(text.orEmpty().toString())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,15 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
|
||||
@Inject
|
||||
class TimelineItemContentRoomMembershipFactory(
|
||||
private val timelineEventFormatter: TimelineEventFormatter,
|
||||
) {
|
||||
fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
|
||||
val text = timelineEventFormatter.format(eventTimelineItem)
|
||||
fun create(eventContent: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): TimelineItemEventContent {
|
||||
val text = timelineEventFormatter.format(eventContent, isOutgoing, sender, senderDisambiguatedDisplayName)
|
||||
return TimelineItemRoomMembershipContent(text.orEmpty().toString())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,15 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
|
||||
@Inject
|
||||
class TimelineItemContentStateFactory(
|
||||
private val timelineEventFormatter: TimelineEventFormatter,
|
||||
) {
|
||||
fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
|
||||
val text = timelineEventFormatter.format(eventTimelineItem)
|
||||
fun create(eventContent: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): TimelineItemEventContent {
|
||||
val text = timelineEventFormatter.format(eventContent, isOutgoing, sender, senderDisambiguatedDisplayName)
|
||||
return TimelineItemStateEventContent(text.orEmpty().toString())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
|||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.utils.messagesummary.MessageSummaryFormatter
|
||||
import io.element.android.libraries.architecture.map
|
||||
import io.element.android.libraries.core.bool.orTrue
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
|
|
@ -43,6 +46,7 @@ class TimelineItemEventFactory(
|
|||
private val matrixClient: MatrixClient,
|
||||
private val dateFormatter: DateFormatter,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val summaryFormatter: MessageSummaryFormatter,
|
||||
) {
|
||||
@AssistedFactory
|
||||
interface Creator {
|
||||
|
|
@ -69,6 +73,29 @@ class TimelineItemEventFactory(
|
|||
url = senderProfile.getAvatarUrl(),
|
||||
size = AvatarSize.TimelineSender
|
||||
)
|
||||
val mappedThreadInfo = when (val threadInfo = currentTimelineItem.event.threadInfo()) {
|
||||
is EventThreadInfo.ThreadResponse -> {
|
||||
TimelineItemThreadInfo.ThreadResponse(threadInfo.threadRootId)
|
||||
}
|
||||
is EventThreadInfo.ThreadRoot -> {
|
||||
TimelineItemThreadInfo.ThreadRoot(
|
||||
summary = threadInfo.summary,
|
||||
latestEventText = threadInfo.summary.latestEvent.dataOrNull()
|
||||
?.let {
|
||||
contentFactory.create(
|
||||
itemContent = it.content,
|
||||
eventId = it.eventOrTransactionId.eventId,
|
||||
isEditable = false,
|
||||
sender = it.senderId,
|
||||
senderProfile = it.senderProfile,
|
||||
)
|
||||
}
|
||||
?.let(summaryFormatter::format)
|
||||
)
|
||||
}
|
||||
null -> null
|
||||
}
|
||||
|
||||
return TimelineItem.Event(
|
||||
id = currentTimelineItem.uniqueId,
|
||||
eventId = currentTimelineItem.eventId,
|
||||
|
|
@ -87,7 +114,7 @@ class TimelineItemEventFactory(
|
|||
readReceiptState = currentTimelineItem.computeReadReceiptState(roomMembers),
|
||||
localSendState = currentTimelineItem.event.localSendState,
|
||||
inReplyTo = currentTimelineItem.event.inReplyTo()?.map(permalinkParser = permalinkParser),
|
||||
threadInfo = currentTimelineItem.event.threadInfo() ?: EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo = mappedThreadInfo,
|
||||
origin = currentTimelineItem.event.origin,
|
||||
timelineItemDebugInfoProvider = currentTimelineItem.event.timelineItemDebugInfoProvider,
|
||||
messageShieldProvider = currentTimelineItem.event.messageShieldProvider,
|
||||
|
|
|
|||
|
|
@ -17,10 +17,11 @@ 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.SendHandle
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
|
|
@ -82,7 +83,7 @@ sealed interface TimelineItem {
|
|||
val readReceiptState: TimelineItemReadReceipts,
|
||||
val localSendState: LocalEventSendState?,
|
||||
val inReplyTo: InReplyToDetails?,
|
||||
val threadInfo: EventThreadInfo,
|
||||
val threadInfo: TimelineItemThreadInfo?,
|
||||
val origin: TimelineItemEventOrigin?,
|
||||
val timelineItemDebugInfoProvider: TimelineItemDebugInfoProvider,
|
||||
val messageShieldProvider: MessageShieldProvider,
|
||||
|
|
@ -130,3 +131,8 @@ sealed interface TimelineItem {
|
|||
val aggregatedReadReceipts: ImmutableList<ReadReceiptData>,
|
||||
) : TimelineItem
|
||||
}
|
||||
|
||||
sealed interface TimelineItemThreadInfo {
|
||||
data class ThreadRoot(val summary: ThreadSummary, val latestEventText: String?) : TimelineItemThreadInfo
|
||||
data class ThreadResponse(val threadRootId: ThreadId) : TimelineItemThreadInfo
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ package io.element.android.features.messages.impl.utils.messagesummary
|
|||
import android.content.Context
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
||||
|
|
@ -37,15 +37,15 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
class DefaultMessageSummaryFormatter(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : MessageSummaryFormatter {
|
||||
override fun format(event: TimelineItem.Event): String {
|
||||
return when (event.content) {
|
||||
is TimelineItemTextBasedContent -> event.content.plainText
|
||||
is TimelineItemProfileChangeContent -> event.content.body
|
||||
is TimelineItemStateContent -> event.content.body
|
||||
override fun format(content: TimelineItemEventContent): String {
|
||||
return when (content) {
|
||||
is TimelineItemTextBasedContent -> content.plainText
|
||||
is TimelineItemProfileChangeContent -> content.body
|
||||
is TimelineItemStateContent -> content.body
|
||||
is TimelineItemLocationContent -> context.getString(CommonStrings.common_shared_location)
|
||||
is TimelineItemEncryptedContent -> context.getString(CommonStrings.common_unable_to_decrypt)
|
||||
is TimelineItemRedactedContent -> context.getString(CommonStrings.common_message_removed)
|
||||
is TimelineItemPollContent -> event.content.question
|
||||
is TimelineItemPollContent -> content.question
|
||||
is TimelineItemVoiceContent -> context.getString(CommonStrings.common_voice_message)
|
||||
is TimelineItemUnknownContent -> context.getString(CommonStrings.common_unsupported_event)
|
||||
is TimelineItemImageContent -> context.getString(CommonStrings.common_image)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@
|
|||
package io.element.android.features.messages.impl.utils.messagesummary
|
||||
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
|
||||
interface MessageSummaryFormatter {
|
||||
fun format(event: TimelineItem.Event): String
|
||||
fun format(event: TimelineItem.Event): String {
|
||||
return format(event.content)
|
||||
}
|
||||
fun format(content: TimelineItemEventContent): String
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMess
|
|||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
|
|
@ -61,7 +62,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembersState
|
|||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
|
|
@ -1181,7 +1181,7 @@ class MessagesPresenterTest {
|
|||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(
|
||||
action = TimelineItemAction.ReplyInThread,
|
||||
event = aMessageEvent(threadInfo = EventThreadInfo(A_THREAD_ID, null))
|
||||
event = aMessageEvent(threadInfo = TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID))
|
||||
))
|
||||
awaitItem()
|
||||
openThreadLambda.assertions().isCalledOnce().with(value(A_THREAD_ID), value(null))
|
||||
|
|
@ -1204,7 +1204,7 @@ class MessagesPresenterTest {
|
|||
event = aMessageEvent(
|
||||
// The event id will be used as the thread id instead
|
||||
eventId = AN_EVENT_ID,
|
||||
threadInfo = EventThreadInfo(null, null),
|
||||
threadInfo = null,
|
||||
)
|
||||
))
|
||||
awaitItem()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUser
|
|||
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory
|
||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
|
|
@ -31,7 +32,6 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
|
|||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_CAPTION
|
||||
|
|
@ -198,7 +198,7 @@ class ActionListPresenterTest {
|
|||
val messageEvent = aMessageEvent(
|
||||
isMine = false,
|
||||
isEditable = false,
|
||||
threadInfo = EventThreadInfo(threadRootId = A_THREAD_ID, threadSummary = null),
|
||||
threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = A_THREAD_ID),
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
|
||||
)
|
||||
initialState.eventSink.invoke(
|
||||
|
|
@ -432,7 +432,7 @@ class ActionListPresenterTest {
|
|||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(
|
||||
isMine = true,
|
||||
threadInfo = EventThreadInfo(threadRootId = A_THREAD_ID, threadSummary = null),
|
||||
threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = A_THREAD_ID),
|
||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
|
||||
)
|
||||
initialState.eventSink.invoke(
|
||||
|
|
@ -1264,7 +1264,7 @@ class ActionListPresenterTest {
|
|||
content = aTimelineItemVoiceContent(
|
||||
caption = null,
|
||||
),
|
||||
threadInfo = EventThreadInfo(A_THREAD_ID, null)
|
||||
threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = A_THREAD_ID)
|
||||
)
|
||||
initialState.eventSink.invoke(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
|
|
@ -1368,7 +1368,7 @@ class ActionListPresenterTest {
|
|||
content = aTimelineItemVoiceContent(
|
||||
caption = null,
|
||||
),
|
||||
threadInfo = EventThreadInfo(A_THREAD_ID, null),
|
||||
threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = A_THREAD_ID),
|
||||
)
|
||||
|
||||
assertThat(messageEvent.isRemote).isTrue()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
|||
import io.element.android.features.messages.impl.timeline.model.ReadReceiptData
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
|
|
@ -19,7 +20,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShieldProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.SendHandleProvider
|
||||
|
|
@ -41,7 +41,7 @@ internal fun aMessageEvent(
|
|||
canBeRepliedTo: Boolean = true,
|
||||
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = A_MESSAGE, isEdited = false),
|
||||
inReplyTo: InReplyToDetails? = null,
|
||||
threadInfo: EventThreadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo: TimelineItemThreadInfo? = null,
|
||||
sendState: LocalEventSendState = LocalEventSendState.Sent(AN_EVENT_ID),
|
||||
debugInfoProvider: TimelineItemDebugInfoProvider = TimelineItemDebugInfoProvider { aTimelineItemDebugInfo() },
|
||||
messageShieldProvider: MessageShieldProvider = MessageShieldProvider { null },
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.features.messages.impl.fixtures
|
||||
|
||||
import io.element.android.features.messages.impl.messagesummary.FakeMessageSummaryFormatter
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
|
||||
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFactory
|
||||
|
|
@ -30,7 +31,9 @@ import io.element.android.features.poll.test.pollcontent.FakePollContentStateFac
|
|||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation
|
||||
|
|
@ -75,11 +78,13 @@ internal fun TestScope.aTimelineItemsFactory(
|
|||
stateFactory = TimelineItemContentStateFactory(timelineEventFormatter),
|
||||
failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
|
||||
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(),
|
||||
currentSessionIdHolder = CurrentSessionIdHolder(matrixClient),
|
||||
),
|
||||
matrixClient = matrixClient,
|
||||
dateFormatter = FakeDateFormatter(),
|
||||
permalinkParser = FakePermalinkParser(),
|
||||
config = config
|
||||
config = config,
|
||||
summaryFormatter = FakeMessageSummaryFormatter(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -95,7 +100,7 @@ internal fun TestScope.aTimelineItemsFactory(
|
|||
|
||||
internal fun aTimelineEventFormatter(): TimelineEventFormatter {
|
||||
return object : TimelineEventFormatter {
|
||||
override fun format(event: EventTimelineItem): CharSequence {
|
||||
override fun format(content: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): CharSequence? {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
package io.element.android.features.messages.impl.messagesummary
|
||||
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.utils.messagesummary.MessageSummaryFormatter
|
||||
|
||||
class FakeMessageSummaryFormatter : MessageSummaryFormatter {
|
||||
private var result = "A message"
|
||||
|
||||
override fun format(event: TimelineItem.Event): String = result
|
||||
override fun format(content: TimelineItemEventContent): String = result
|
||||
|
||||
fun givenMessageResult(value: String) {
|
||||
result = value
|
||||
|
|
|
|||
|
|
@ -750,7 +750,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
body: String = "Body",
|
||||
inReplyTo: InReplyTo? = null,
|
||||
isEdited: Boolean = false,
|
||||
threadInfo: EventThreadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo: EventThreadInfo? = null,
|
||||
type: MessageType,
|
||||
): MessageContent {
|
||||
return MessageContent(
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
|
||||
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
|
|
@ -42,7 +41,7 @@ class TimelineItemGrouperTest {
|
|||
isEditable = false,
|
||||
canBeRepliedTo = false,
|
||||
inReplyTo = null,
|
||||
threadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo = null,
|
||||
origin = null,
|
||||
timelineItemDebugInfoProvider = { aTimelineItemDebugInfo() },
|
||||
messageShieldProvider = { null },
|
||||
|
|
|
|||
|
|
@ -7,9 +7,18 @@
|
|||
|
||||
package io.element.android.features.poll.api.pollcontent
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
|
||||
interface PollContentStateFactory {
|
||||
suspend fun create(event: EventTimelineItem, content: PollContent): PollContentState
|
||||
suspend fun create(eventTimelineItem: EventTimelineItem, content: PollContent): PollContentState {
|
||||
return create(
|
||||
eventId = eventTimelineItem.eventId,
|
||||
isEditable = eventTimelineItem.isEditable,
|
||||
isOwn = eventTimelineItem.isOwn,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
suspend fun create(eventId: EventId?, isEditable: Boolean, isOwn: Boolean, content: PollContent): PollContentState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,12 @@ class PollHistoryItemsFactory(
|
|||
return when (timelineItem) {
|
||||
is MatrixTimelineItem.Event -> {
|
||||
val pollContent = timelineItem.event.content as? PollContent ?: return null
|
||||
val pollContentState = pollContentStateFactory.create(timelineItem.event, pollContent)
|
||||
val pollContentState = pollContentStateFactory.create(
|
||||
eventId = timelineItem.eventId,
|
||||
isEditable = timelineItem.event.isEditable,
|
||||
isOwn = timelineItem.event.isOwn,
|
||||
content = pollContent,
|
||||
)
|
||||
PollHistoryItem(
|
||||
formattedDate = dateFormatter.format(
|
||||
timestamp = timelineItem.event.timestamp,
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import io.element.android.features.poll.api.pollcontent.PollContentState
|
|||
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.poll.isDisclosed
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
|
|
@ -25,8 +25,10 @@ class DefaultPollContentStateFactory(
|
|||
private val matrixClient: MatrixClient,
|
||||
) : PollContentStateFactory {
|
||||
override suspend fun create(
|
||||
event: EventTimelineItem,
|
||||
content: PollContent
|
||||
eventId: EventId?,
|
||||
isEditable: Boolean,
|
||||
isOwn: Boolean,
|
||||
content: PollContent,
|
||||
): PollContentState {
|
||||
val totalVoteCount = content.votes.flatMap { it.value }.size
|
||||
val myVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys
|
||||
|
|
@ -59,13 +61,13 @@ class DefaultPollContentStateFactory(
|
|||
}
|
||||
|
||||
return PollContentState(
|
||||
eventId = event.eventId,
|
||||
eventId = eventId,
|
||||
question = content.question,
|
||||
answerItems = answerItems.toImmutableList(),
|
||||
pollKind = content.kind,
|
||||
isPollEditable = event.isEditable,
|
||||
isPollEditable = isEditable,
|
||||
isPollEnded = isPollEnded,
|
||||
isMine = event.isOwn,
|
||||
isMine = isOwn,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,20 +10,20 @@ package io.element.android.features.poll.test.pollcontent
|
|||
import io.element.android.features.poll.api.pollcontent.PollAnswerItem
|
||||
import io.element.android.features.poll.api.pollcontent.PollContentState
|
||||
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class FakePollContentStateFactory : PollContentStateFactory {
|
||||
override suspend fun create(event: EventTimelineItem, content: PollContent): PollContentState {
|
||||
override suspend fun create(eventId: EventId?, isEditable: Boolean, isOwn: Boolean, content: PollContent): PollContentState {
|
||||
return PollContentState(
|
||||
eventId = event.eventId,
|
||||
eventId = eventId,
|
||||
question = content.question,
|
||||
answerItems = emptyList<PollAnswerItem>().toImmutableList(),
|
||||
pollKind = content.kind,
|
||||
isPollEditable = event.isEditable,
|
||||
isPollEditable = isEditable,
|
||||
isPollEnded = content.endTime != null,
|
||||
isMine = event.isOwn
|
||||
isMine = isOwn,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ enum class AvatarSize(val dp: Dp) {
|
|||
TimelineRoom(32.dp),
|
||||
TimelineSender(32.dp),
|
||||
TimelineReadReceipt(16.dp),
|
||||
TimelineThreadLatestEventSender(24.dp),
|
||||
|
||||
ComposerAlert(32.dp),
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,19 @@
|
|||
|
||||
package io.element.android.libraries.eventformatter.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||
|
||||
interface TimelineEventFormatter {
|
||||
fun format(event: EventTimelineItem): CharSequence?
|
||||
fun format(event: EventTimelineItem): CharSequence? {
|
||||
return format(
|
||||
content = event.content,
|
||||
isOutgoing = event.isOwn,
|
||||
sender = event.sender,
|
||||
senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
)
|
||||
}
|
||||
fun format(content: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): CharSequence?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import io.element.android.libraries.core.meta.BuildMeta
|
|||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
||||
import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
|
||||
|
|
@ -43,12 +45,16 @@ class DefaultTimelineEventFormatter(
|
|||
override fun format(event: EventTimelineItem): CharSequence? {
|
||||
val isOutgoing = event.isOwn
|
||||
val senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender)
|
||||
return when (val content = event.content) {
|
||||
return format(event.content, isOutgoing, event.sender, senderDisambiguatedDisplayName)
|
||||
}
|
||||
|
||||
override fun format(content: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): CharSequence? {
|
||||
return when (content) {
|
||||
is RoomMembershipContent -> {
|
||||
roomMembershipContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing)
|
||||
}
|
||||
is ProfileChangeContent -> {
|
||||
profileChangeContentFormatter.format(content, event.sender, senderDisambiguatedDisplayName, isOutgoing)
|
||||
profileChangeContentFormatter.format(content, sender, senderDisambiguatedDisplayName, isOutgoing)
|
||||
}
|
||||
is StateContent -> {
|
||||
stateContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing, RenderingMode.Timeline)
|
||||
|
|
@ -66,7 +72,7 @@ class DefaultTimelineEventFormatter(
|
|||
is FailedToParseStateContent,
|
||||
is UnknownContent -> {
|
||||
if (buildMeta.isDebuggable) {
|
||||
error("You should not use this formatter for this event: $event")
|
||||
error("You should not use this formatter for this event content: $content")
|
||||
}
|
||||
sp.getString(CommonStrings.common_unsupported_event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.google.common.truth.Truth.assertWithMessage
|
|||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
|
|
@ -175,7 +174,7 @@ class DefaultBaseRoomLastMessageFormatterTest {
|
|||
) {
|
||||
val body = "Shared body"
|
||||
fun createMessageContent(type: MessageType): MessageContent {
|
||||
return MessageContent(body, null, false, EventThreadInfo(null, null), type)
|
||||
return MessageContent(body, null, false, null, type)
|
||||
}
|
||||
|
||||
val sharedContentMessagesTypes = arrayOf(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.google.common.truth.Truth.assertWithMessage
|
|||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
|
|
@ -130,7 +129,7 @@ class DefaultPinnedMessagesBannerFormatterTest {
|
|||
fun `Message contents`() {
|
||||
val body = "Shared body"
|
||||
fun createMessageContent(type: MessageType): MessageContent {
|
||||
return MessageContent(body, null, false, EventThreadInfo(null, null), type)
|
||||
return MessageContent(body, null, false, null, type)
|
||||
}
|
||||
|
||||
val sharedContentMessagesTypes = arrayOf(
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
|||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
|
||||
|
||||
data class EventThreadInfo(
|
||||
val threadRootId: ThreadId?,
|
||||
val threadSummary: ThreadSummary?,
|
||||
)
|
||||
sealed interface EventThreadInfo {
|
||||
data class ThreadRoot(val summary: ThreadSummary) : EventThreadInfo
|
||||
data class ThreadResponse(val threadRootId: ThreadId) : EventThreadInfo
|
||||
}
|
||||
|
||||
data class ThreadSummary(
|
||||
val latestEvent: AsyncData<EmbeddedEventInfo>,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ data class MessageContent(
|
|||
val body: String,
|
||||
val inReplyTo: InReplyTo?,
|
||||
val isEdited: Boolean,
|
||||
val threadInfo: EventThreadInfo,
|
||||
val threadInfo: EventThreadInfo?,
|
||||
val type: MessageType
|
||||
) : EventContent
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ private const val MSG_TYPE_GALLERY_UNSTABLE = "dm.filament.gallery"
|
|||
class EventMessageMapper {
|
||||
private val inReplyToMapper by lazy { InReplyToMapper(TimelineEventContentMapper()) }
|
||||
|
||||
fun map(message: MsgLikeKind.Message, inReplyTo: InReplyToDetails?, threadInfo: EventThreadInfo): MessageContent = message.use {
|
||||
fun map(message: MsgLikeKind.Message, inReplyTo: InReplyToDetails?, threadInfo: EventThreadInfo?): MessageContent = message.use {
|
||||
val type = it.content.msgType.use(this::mapMessageType)
|
||||
val inReplyToEvent: InReplyTo? = inReplyTo?.use(inReplyToMapper::map)
|
||||
MessageContent(
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class TimelineEventContentMapper(
|
|||
content = map(latestEvent.content),
|
||||
senderId = UserId(latestEvent.sender),
|
||||
senderProfile = latestEvent.senderProfile.map(),
|
||||
timestamp = latestEvent.timestamp.toLong()
|
||||
timestamp = latestEvent.timestamp.toLong(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -89,10 +89,12 @@ class TimelineEventContentMapper(
|
|||
numberOfReplies = numberOfReplies,
|
||||
)
|
||||
}
|
||||
val threadInfo = EventThreadInfo(
|
||||
threadRootId = it.content.threadRoot?.let(::ThreadId),
|
||||
threadSummary = threadSummary,
|
||||
)
|
||||
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 -> {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ fun aMessageContent(
|
|||
body: String = "body",
|
||||
inReplyTo: InReplyTo? = null,
|
||||
isEdited: Boolean = false,
|
||||
threadInfo: EventThreadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo: EventThreadInfo? = null,
|
||||
messageType: MessageType = TextMessageType(
|
||||
body = body,
|
||||
formatted = null
|
||||
|
|
|
|||
|
|
@ -134,10 +134,7 @@ class InReplyToDetailsOtherProvider : InReplyToDetailsProvider() {
|
|||
private fun aMessageContent(
|
||||
body: String,
|
||||
type: MessageType,
|
||||
threadInfo: EventThreadInfo = EventThreadInfo(
|
||||
threadRootId = null,
|
||||
threadSummary = null,
|
||||
),
|
||||
threadInfo: EventThreadInfo? = null,
|
||||
) = MessageContent(
|
||||
body = body,
|
||||
inReplyTo = null,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
package io.element.android.libraries.matrix.ui.messages.reply
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
|
||||
|
|
@ -70,7 +69,7 @@ class InReplyToDetailTest {
|
|||
body = "**Hello!**",
|
||||
inReplyTo = null,
|
||||
isEdited = false,
|
||||
threadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo = null,
|
||||
type = TextMessageType(
|
||||
body = "**Hello!**",
|
||||
formatted = FormattedBody(
|
||||
|
|
@ -95,7 +94,7 @@ class InReplyToDetailTest {
|
|||
body = "**Hello!**",
|
||||
inReplyTo = null,
|
||||
isEdited = false,
|
||||
threadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo = null,
|
||||
type = TextMessageType(
|
||||
body = "**Hello!**",
|
||||
formatted = null,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package io.element.android.libraries.textcomposer.model
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
|
|
@ -49,7 +50,7 @@ sealed interface MessageComposerMode {
|
|||
get() = this is Reply &&
|
||||
replyToDetails is InReplyToDetails.Ready &&
|
||||
replyToDetails.eventContent is MessageContent &&
|
||||
(replyToDetails.eventContent as MessageContent).threadInfo.threadRootId != null
|
||||
(replyToDetails.eventContent as MessageContent).threadInfo is EventThreadInfo.ThreadResponse
|
||||
}
|
||||
|
||||
fun MessageComposerMode.showCaptionCompatibilityWarning(): Boolean {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2cb3989055cf63e3ee14d8e2d8b2cdcd67b3af1addae78b38ab13ddcf93a232d
|
||||
size 9740
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8e206a2485736baa2ac8c6dae6847ef381bfa1f36a5b7bda14a7f93a55069f41
|
||||
size 9666
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ddaa247eea635d7e8c079ae128983fd25c1aeba96a48b08ae71218ef52e88d5b
|
||||
size 69475
|
||||
oid sha256:b51cd7375e0d9be97786632eb1dfc16b5023784ed0de616f029d782ca56aa07b
|
||||
size 68221
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:647365688fd0c542d4be6dae570b33fbb7bab6261dc57791db85966808d440bb
|
||||
size 67563
|
||||
oid sha256:1cb184afd2b1afff7b2f5f24f555e8278e20bbc98fe8b67a25a005771c7105b0
|
||||
size 66908
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26b9908bbd388321444037dcc1aae55037d3dabc7a9f9b14c39ba871f4f9d593
|
||||
size 16128
|
||||
oid sha256:9640e8e6d758a03f995c7fbbb6c3c123109594138a7cc53d5416ef5e3e157693
|
||||
size 18006
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a96b6d95b8941d80035b309444b9eaa038098fb16aa84dec209fc3ee215ac9e
|
||||
size 21687
|
||||
oid sha256:fbe699fb17947981c53d47a9570da71e2388ec493a3a9471aaa13a5405075069
|
||||
size 23421
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce1aba18ea8a6b45d9c40de9af961952e261c7c6200ab13cdfa67906d995208b
|
||||
size 14888
|
||||
oid sha256:8c6688901f89d3858ec67dda889fe589fb6b59202c64a4b700c2aa484e19f4cd
|
||||
size 17606
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a4d295ae71ba2709845312b84983b79348069ded46b30091b9fa769a587cbb7
|
||||
size 14337
|
||||
oid sha256:26b9908bbd388321444037dcc1aae55037d3dabc7a9f9b14c39ba871f4f9d593
|
||||
size 16128
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:da8f54591a52475c6f47fa083a1bb397e1164dd3e735562388fde4ff45644150
|
||||
size 16275
|
||||
oid sha256:1a96b6d95b8941d80035b309444b9eaa038098fb16aa84dec209fc3ee215ac9e
|
||||
size 21687
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce1aba18ea8a6b45d9c40de9af961952e261c7c6200ab13cdfa67906d995208b
|
||||
size 14888
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a4d295ae71ba2709845312b84983b79348069ded46b30091b9fa769a587cbb7
|
||||
size 14337
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:da8f54591a52475c6f47fa083a1bb397e1164dd3e735562388fde4ff45644150
|
||||
size 16275
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fd18a15bc49e87b8abc9231a13771a0fe34003b11fe1fd48df2d91f54bc40cc8
|
||||
size 15564
|
||||
oid sha256:3d5ed3a4c0340d904404c1ef68e7391e1303f865d15e88de6f82ed68e8dc4b4c
|
||||
size 19463
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:388a1ac9f1790fd305fd329b73ae621093d323db3fa4d4d32eef92f8dd5b51ec
|
||||
size 14824
|
||||
oid sha256:c44464ab02dd9f1fc0624f2110bc9938c8aa8856bdad811aafa05eff2e144e4a
|
||||
size 18884
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc90e78d2fa94425322895028f1156bfda196ee2a761231920c6ffd80f02984b
|
||||
size 17512
|
||||
oid sha256:57145b520f6702934571122f4c243f8740658847c0c60e6f901179d1d420d16a
|
||||
size 20857
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:74fda6437495995876f76dc1ff0e056de4937ef8d719b3be1ce73baccd31516e
|
||||
size 15889
|
||||
oid sha256:fd18a15bc49e87b8abc9231a13771a0fe34003b11fe1fd48df2d91f54bc40cc8
|
||||
size 15564
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:04fb2f230a09f0d3e27e151d98aa5d91e416b5ca9637b4db5d8e00118a68e7c4
|
||||
size 15166
|
||||
oid sha256:388a1ac9f1790fd305fd329b73ae621093d323db3fa4d4d32eef92f8dd5b51ec
|
||||
size 14824
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:896f698ef11d8a0577baf0a81cb54cabc307d95d05c230e5e3bdde40c3dc0900
|
||||
size 17844
|
||||
oid sha256:bc90e78d2fa94425322895028f1156bfda196ee2a761231920c6ffd80f02984b
|
||||
size 17512
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cf264b6f8af5b7432511d595ff8c663c5e2be33c9f85268627f5188e3f0f8db0
|
||||
size 18949
|
||||
oid sha256:74fda6437495995876f76dc1ff0e056de4937ef8d719b3be1ce73baccd31516e
|
||||
size 15889
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0bbe1e3e8e1ea119cc61509617d3fa8e2bd047f619c41271901c73c46be1d610
|
||||
size 18201
|
||||
oid sha256:04fb2f230a09f0d3e27e151d98aa5d91e416b5ca9637b4db5d8e00118a68e7c4
|
||||
size 15166
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7a7c183808801645e285dfba563c036c204a347de20b4b1e40fcfeab29fafb7d
|
||||
size 20876
|
||||
oid sha256:896f698ef11d8a0577baf0a81cb54cabc307d95d05c230e5e3bdde40c3dc0900
|
||||
size 17844
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a48eb99f4466e3a883c0c7441006f201e0db22fd0e920ad0a77a8512637e01bb
|
||||
size 16445
|
||||
oid sha256:cf264b6f8af5b7432511d595ff8c663c5e2be33c9f85268627f5188e3f0f8db0
|
||||
size 18949
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:895ba9a35b8854d5005fc0671a7e6cbba5e26525b4a8ed4fa0fb612432caa04e
|
||||
size 15205
|
||||
oid sha256:0bbe1e3e8e1ea119cc61509617d3fa8e2bd047f619c41271901c73c46be1d610
|
||||
size 18201
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:98baed819cd8b08085ceabf7dbccccc77b0fdf0d28a3f852879f6f8aa02ee441
|
||||
size 19848
|
||||
oid sha256:7a7c183808801645e285dfba563c036c204a347de20b4b1e40fcfeab29fafb7d
|
||||
size 20876
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c4678c15b608547c255d5eeb128c144ae7a3c5a21de2b047fc77f15364822ac2
|
||||
size 12847
|
||||
oid sha256:a48eb99f4466e3a883c0c7441006f201e0db22fd0e920ad0a77a8512637e01bb
|
||||
size 16445
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5e30395594c1a35d90fc0a04af340eb79cbe686d90856414d0024b769f86e89d
|
||||
size 12507
|
||||
oid sha256:895ba9a35b8854d5005fc0671a7e6cbba5e26525b4a8ed4fa0fb612432caa04e
|
||||
size 15205
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:60362244c7adf3644ed2ae354643e0525aad2a4454f6b13abce8fd265f4987c6
|
||||
size 13777
|
||||
oid sha256:98baed819cd8b08085ceabf7dbccccc77b0fdf0d28a3f852879f6f8aa02ee441
|
||||
size 19848
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:73de8ee7933c1a45a266761b14e09b008a62dc69b1600bbc69b8a01370fb97c1
|
||||
size 18641
|
||||
oid sha256:c4678c15b608547c255d5eeb128c144ae7a3c5a21de2b047fc77f15364822ac2
|
||||
size 12847
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b62b7dc22bfc727cfcbf7f6a19683bcf4da116c7192f9943f5addd11c39fcadb
|
||||
size 17018
|
||||
oid sha256:5e30395594c1a35d90fc0a04af340eb79cbe686d90856414d0024b769f86e89d
|
||||
size 12507
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c0855112283bf797bc846e1982c6293e321361368481a803e12658723aaf8409
|
||||
size 22988
|
||||
oid sha256:60362244c7adf3644ed2ae354643e0525aad2a4454f6b13abce8fd265f4987c6
|
||||
size 13777
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:049b3ac784a8e400bad374a960ecb77d7f4bca81079be764dc74cb161f7a1093
|
||||
size 20880
|
||||
oid sha256:73de8ee7933c1a45a266761b14e09b008a62dc69b1600bbc69b8a01370fb97c1
|
||||
size 18641
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:21d83d719bee5fd19ad090791c7848890d87e4bd9eaddb17c9da1773ec81667f
|
||||
size 19249
|
||||
oid sha256:b62b7dc22bfc727cfcbf7f6a19683bcf4da116c7192f9943f5addd11c39fcadb
|
||||
size 17018
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:edc743a5cd067eb13e464f27c2f12f95433bd020dd1b3e920614ee913e06e476
|
||||
size 25008
|
||||
oid sha256:c0855112283bf797bc846e1982c6293e321361368481a803e12658723aaf8409
|
||||
size 22988
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:45e6821d622bfef22cc41a040e12e2b49e0bb50a88b0a143bdb25e3368809412
|
||||
size 16496
|
||||
oid sha256:049b3ac784a8e400bad374a960ecb77d7f4bca81079be764dc74cb161f7a1093
|
||||
size 20880
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ddd2399d3d701fa962dc5683e054c2c4829ccd99d04498415ce88487f5e0ec8
|
||||
size 15760
|
||||
oid sha256:21d83d719bee5fd19ad090791c7848890d87e4bd9eaddb17c9da1773ec81667f
|
||||
size 19249
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4293d98ed31f87c59ff5741344c2e41f58ff8f19c9342f6626553e978dbfc674
|
||||
size 18429
|
||||
oid sha256:edc743a5cd067eb13e464f27c2f12f95433bd020dd1b3e920614ee913e06e476
|
||||
size 25008
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:37d1e302d4a610aaab7cfc6a15572b7eb92b059d9384e7bbe4daebcaf2da9049
|
||||
size 21343
|
||||
oid sha256:45e6821d622bfef22cc41a040e12e2b49e0bb50a88b0a143bdb25e3368809412
|
||||
size 16496
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:536d8d899945483352137d23356f3777db8e2225288f01e8cbcd1a770ca7f3ee
|
||||
size 20494
|
||||
oid sha256:6ddd2399d3d701fa962dc5683e054c2c4829ccd99d04498415ce88487f5e0ec8
|
||||
size 15760
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:542b080ae9374e4f6d57689b1e1b6a84a0f81f017bd276bbcab43b9659be4335
|
||||
size 23534
|
||||
oid sha256:4293d98ed31f87c59ff5741344c2e41f58ff8f19c9342f6626553e978dbfc674
|
||||
size 18429
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aeb02390258eade3ceb8c437bcf3592f3dde463d6f1b2a43ffefd61abf4184c4
|
||||
size 17236
|
||||
oid sha256:37d1e302d4a610aaab7cfc6a15572b7eb92b059d9384e7bbe4daebcaf2da9049
|
||||
size 21343
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d5c6bfe22fb71a9c6353718c2124000245182359ac1c43dfc8dd9b91415e66e6
|
||||
size 16385
|
||||
oid sha256:536d8d899945483352137d23356f3777db8e2225288f01e8cbcd1a770ca7f3ee
|
||||
size 20494
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:408f90e8a2f4dcd0716118fb522e5cd4adba0b2a429723fc2e47555b82acf005
|
||||
size 19501
|
||||
oid sha256:542b080ae9374e4f6d57689b1e1b6a84a0f81f017bd276bbcab43b9659be4335
|
||||
size 23534
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9778edbbb33ab0bbaa0072bd4659488937efe0f8b8fac0a5b64497fe6a1e28e2
|
||||
size 20838
|
||||
oid sha256:aeb02390258eade3ceb8c437bcf3592f3dde463d6f1b2a43ffefd61abf4184c4
|
||||
size 17236
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:97f43860bf9e65c7822943f8806a0642ce59957be5e35650e2b69a2c562a694c
|
||||
size 18637
|
||||
oid sha256:d5c6bfe22fb71a9c6353718c2124000245182359ac1c43dfc8dd9b91415e66e6
|
||||
size 16385
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:77e71b006b71300ccb1005ad3cac6a93ae242ff03f2936fdf9d10c0604d20faa
|
||||
size 26121
|
||||
oid sha256:408f90e8a2f4dcd0716118fb522e5cd4adba0b2a429723fc2e47555b82acf005
|
||||
size 19501
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e744d3fe3315dc6cb5beab80dc29d565500367772339071575b6505370e47207
|
||||
size 14771
|
||||
oid sha256:9778edbbb33ab0bbaa0072bd4659488937efe0f8b8fac0a5b64497fe6a1e28e2
|
||||
size 20838
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:448ea365e3e75059bc888961e3454562d35ab116c75b3b9855723b5fc1480d92
|
||||
size 14028
|
||||
oid sha256:97f43860bf9e65c7822943f8806a0642ce59957be5e35650e2b69a2c562a694c
|
||||
size 18637
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ad98105fcd6d6c02e5ae036ad49770b4aa35d94e32ab3a63f040deccc2375e6e
|
||||
size 16703
|
||||
oid sha256:77e71b006b71300ccb1005ad3cac6a93ae242ff03f2936fdf9d10c0604d20faa
|
||||
size 26121
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5b8e747d51d6a6ab1ab6ff80224d0241a16f38b39e0c156dc6eef47ea56eca63
|
||||
size 18140
|
||||
oid sha256:e744d3fe3315dc6cb5beab80dc29d565500367772339071575b6505370e47207
|
||||
size 14771
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:34f28c6dbbe094b9ba070c6f1ef4b10b2625ea094629b6305dff70bdf6367ddc
|
||||
size 16894
|
||||
oid sha256:448ea365e3e75059bc888961e3454562d35ab116c75b3b9855723b5fc1480d92
|
||||
size 14028
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ccfe44da86dce16ba4816cfe2029c05d317fe29ca3bb816278f2bd929298d855
|
||||
size 21532
|
||||
oid sha256:ad98105fcd6d6c02e5ae036ad49770b4aa35d94e32ab3a63f040deccc2375e6e
|
||||
size 16703
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5b77bec92bc7c4da3fdc72831797d685bf042131c2222351cc7c852da48cd60
|
||||
size 17662
|
||||
oid sha256:5b8e747d51d6a6ab1ab6ff80224d0241a16f38b39e0c156dc6eef47ea56eca63
|
||||
size 18140
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:99f2b0e2fda05d6fa16c0f2dcc52e92f6fa1c656c6d84cf03e8507a5c14104e8
|
||||
size 16932
|
||||
oid sha256:34f28c6dbbe094b9ba070c6f1ef4b10b2625ea094629b6305dff70bdf6367ddc
|
||||
size 16894
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e79cb41e125705af70990dec3e3fdc0a0202ec8d62f2077eb2a298243ae2e94d
|
||||
size 19624
|
||||
oid sha256:ccfe44da86dce16ba4816cfe2029c05d317fe29ca3bb816278f2bd929298d855
|
||||
size 21532
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e0faccb345f65a543c56e4db44714614915a62b6916a77a0f4adfdae5215311e
|
||||
size 15128
|
||||
oid sha256:e5b77bec92bc7c4da3fdc72831797d685bf042131c2222351cc7c852da48cd60
|
||||
size 17662
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5d626da2924325c074dcd2baf1c112430399553a9ee652cac03b0b1d3db4cff3
|
||||
size 14393
|
||||
oid sha256:99f2b0e2fda05d6fa16c0f2dcc52e92f6fa1c656c6d84cf03e8507a5c14104e8
|
||||
size 16932
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2be7c565d30e9f45a583b93ec948a7cb35a4bc872c4f479bd1efe9eda8972e7c
|
||||
size 17044
|
||||
oid sha256:e79cb41e125705af70990dec3e3fdc0a0202ec8d62f2077eb2a298243ae2e94d
|
||||
size 19624
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c28eae20f614eae53e09aa4e396792f36ee1478c8141faad788fe397502da237
|
||||
size 20671
|
||||
oid sha256:e0faccb345f65a543c56e4db44714614915a62b6916a77a0f4adfdae5215311e
|
||||
size 15128
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8ada8d054436b15e9029150eb99e78103050f8d0428a1c78ef5667bfd54d3be5
|
||||
size 19195
|
||||
oid sha256:5d626da2924325c074dcd2baf1c112430399553a9ee652cac03b0b1d3db4cff3
|
||||
size 14393
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7586db3dc8687c5c5fc73d28dd968041b192c160286f6a7c881eb7d86a2d1bd2
|
||||
size 24477
|
||||
oid sha256:2be7c565d30e9f45a583b93ec948a7cb35a4bc872c4f479bd1efe9eda8972e7c
|
||||
size 17044
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2ef75decb9f501eb6c1941e212e1b330203c16458bfc2e10bfac3d197b091a35
|
||||
size 17116
|
||||
oid sha256:c28eae20f614eae53e09aa4e396792f36ee1478c8141faad788fe397502da237
|
||||
size 20671
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue