Read receipt: Rework how the feature flag is used.
tom
This commit is contained in:
committed by
Benoit Marty
parent
75eb2bbd25
commit
e15610441b
@@ -147,9 +147,10 @@ class TimelinePresenter @Inject constructor(
|
||||
timelineItemsFactory.replaceWith(
|
||||
timelineItems = it,
|
||||
roomMembers = if (readReceiptsEnabled) {
|
||||
membersState.roomMembers()
|
||||
membersState.roomMembers().orEmpty()
|
||||
} else {
|
||||
null
|
||||
// Give an empty list to not affect performance
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -166,6 +167,7 @@ class TimelinePresenter @Inject constructor(
|
||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
||||
paginationState = paginationState,
|
||||
timelineItems = timelineItems,
|
||||
showReadReceipts = readReceiptsEnabled,
|
||||
hasNewItems = hasNewItems.value,
|
||||
sessionState = sessionState,
|
||||
eventSink = ::handleEvents
|
||||
|
||||
@@ -26,6 +26,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
@Immutable
|
||||
data class TimelineState(
|
||||
val timelineItems: ImmutableList<TimelineItem>,
|
||||
val showReadReceipts: Boolean,
|
||||
val highlightedEventId: EventId?,
|
||||
val userHasPermissionToSendMessage: Boolean,
|
||||
val paginationState: MatrixTimeline.PaginationState,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package io.element.android.features.messages.impl.timeline
|
||||
|
||||
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.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||
@@ -44,6 +45,7 @@ import kotlin.random.Random
|
||||
|
||||
fun aTimelineState(timelineItems: ImmutableList<TimelineItem> = persistentListOf()) = TimelineState(
|
||||
timelineItems = timelineItems,
|
||||
showReadReceipts = false,
|
||||
paginationState = MatrixTimeline.PaginationState(
|
||||
isBackPaginating = false,
|
||||
hasMoreToLoadBackwards = true,
|
||||
@@ -124,7 +126,7 @@ internal fun aTimelineItemEvent(
|
||||
isThreaded: Boolean = false,
|
||||
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
|
||||
timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(),
|
||||
readReceiptState: TimelineItemReadReceipts = TimelineItemReadReceipts.Hidden,
|
||||
readReceiptState: TimelineItemReadReceipts = aTimelineItemReadReceipts(),
|
||||
): TimelineItem.Event {
|
||||
return TimelineItem.Event(
|
||||
id = UUID.randomUUID().toString(),
|
||||
@@ -176,6 +178,12 @@ internal fun aTimelineItemDebugInfo(
|
||||
model, originalJson, latestEditedJson
|
||||
)
|
||||
|
||||
internal fun aTimelineItemReadReceipts(): TimelineItemReadReceipts {
|
||||
return TimelineItemReadReceipts(
|
||||
receipts = emptyList<ReadReceiptData>().toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents {
|
||||
val event = aTimelineItemEvent(
|
||||
isMine = true,
|
||||
|
||||
@@ -127,6 +127,7 @@ fun TimelineView(
|
||||
) { timelineItem ->
|
||||
TimelineItemRow(
|
||||
timelineItem = timelineItem,
|
||||
showReadReceipts = state.showReadReceipts,
|
||||
highlightedItem = state.highlightedEventId?.value,
|
||||
userHasPermissionToSendMessage = state.userHasPermissionToSendMessage,
|
||||
onClick = onMessageClicked,
|
||||
@@ -171,6 +172,7 @@ fun TimelineView(
|
||||
@Composable
|
||||
private fun TimelineItemRow(
|
||||
timelineItem: TimelineItem,
|
||||
showReadReceipts: Boolean,
|
||||
highlightedItem: String?,
|
||||
userHasPermissionToSendMessage: Boolean,
|
||||
sessionState: SessionState,
|
||||
@@ -208,6 +210,7 @@ private fun TimelineItemRow(
|
||||
} else {
|
||||
TimelineItemEventRow(
|
||||
event = timelineItem,
|
||||
showReadReceipts = showReadReceipts,
|
||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
||||
canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(),
|
||||
onClick = { onClick(timelineItem) },
|
||||
@@ -248,6 +251,7 @@ private fun TimelineItemRow(
|
||||
timelineItem.events.forEach { subGroupEvent ->
|
||||
TimelineItemRow(
|
||||
timelineItem = subGroupEvent,
|
||||
showReadReceipts = showReadReceipts,
|
||||
highlightedItem = highlightedItem,
|
||||
sessionState = sessionState,
|
||||
userHasPermissionToSendMessage = false,
|
||||
|
||||
@@ -79,7 +79,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.model.receipts
|
||||
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
|
||||
@@ -117,6 +116,7 @@ import kotlin.math.roundToInt
|
||||
@Composable
|
||||
fun TimelineItemEventRow(
|
||||
event: TimelineItem.Event,
|
||||
showReadReceipts: Boolean,
|
||||
isHighlighted: Boolean,
|
||||
canReply: Boolean,
|
||||
onClick: () -> Unit,
|
||||
@@ -177,6 +177,7 @@ fun TimelineItemEventRow(
|
||||
state = state.draggableState,
|
||||
),
|
||||
event = event,
|
||||
showReadReceipts = showReadReceipts,
|
||||
isHighlighted = isHighlighted,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onClick,
|
||||
@@ -195,6 +196,7 @@ fun TimelineItemEventRow(
|
||||
} else {
|
||||
TimelineItemEventRowContent(
|
||||
event = event,
|
||||
showReadReceipts = showReadReceipts,
|
||||
isHighlighted = isHighlighted,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onClick,
|
||||
@@ -238,6 +240,7 @@ private fun SwipeSensitivity(
|
||||
@Composable
|
||||
private fun TimelineItemEventRowContent(
|
||||
event: TimelineItem.Event,
|
||||
showReadReceipts: Boolean,
|
||||
isHighlighted: Boolean,
|
||||
interactionSource: MutableInteractionSource,
|
||||
onClick: () -> Unit,
|
||||
@@ -336,21 +339,23 @@ private fun TimelineItemEventRowContent(
|
||||
}
|
||||
|
||||
// Read receipts / Send state
|
||||
TimelineItemReadReceiptView(
|
||||
state = ReadReceiptViewState(
|
||||
sendState = event.localSendState,
|
||||
receipts = event.readReceiptState.receipts(),
|
||||
),
|
||||
onReadReceiptsClicked = onReadReceiptsClicked,
|
||||
modifier = Modifier
|
||||
.constrainAs(readReceipts) {
|
||||
if (event.reactionsState.reactions.isNotEmpty()) {
|
||||
top.linkTo(reactions.bottom, margin = 4.dp)
|
||||
} else {
|
||||
top.linkTo(message.bottom, margin = 4.dp)
|
||||
if (showReadReceipts) {
|
||||
TimelineItemReadReceiptView(
|
||||
state = ReadReceiptViewState(
|
||||
sendState = event.localSendState,
|
||||
receipts = event.readReceiptState.receipts,
|
||||
),
|
||||
onReadReceiptsClicked = onReadReceiptsClicked,
|
||||
modifier = Modifier
|
||||
.constrainAs(readReceipts) {
|
||||
if (event.reactionsState.reactions.isNotEmpty()) {
|
||||
top.linkTo(reactions.bottom, margin = 4.dp)
|
||||
} else {
|
||||
top.linkTo(message.bottom, margin = 4.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,6 +684,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview {
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
@@ -701,6 +707,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview {
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
@@ -741,6 +748,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview {
|
||||
inReplyTo = aInReplyToReady(replyContent),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
@@ -765,6 +773,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview {
|
||||
isThreaded = true,
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
@@ -817,6 +826,7 @@ internal fun TimelineItemEventRowTimestampPreview(
|
||||
reactionsState = aTimelineItemReactions(count = 0),
|
||||
senderDisplayName = if (useDocument) "Document case" else "Text case",
|
||||
),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
@@ -850,6 +860,7 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview {
|
||||
),
|
||||
timelineItemReactions = aTimelineItemReactions(count = 20),
|
||||
),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
@@ -876,6 +887,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight {
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "a long sender display name to test single line and ellipsis at the end of the line",
|
||||
),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
@@ -898,6 +910,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight {
|
||||
internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight {
|
||||
TimelineItemEventRow(
|
||||
event = aTimelineItemEvent(content = aTimelineItemPollContent()),
|
||||
showReadReceipts = false,
|
||||
isHighlighted = false,
|
||||
canReply = true,
|
||||
onClick = {},
|
||||
|
||||
@@ -29,7 +29,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.messages.impl.timeline.model.receipts
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@@ -93,7 +92,7 @@ private fun ColumnScope.ReadReceiptBottomSheetContent(
|
||||
Text(text = stringResource(id = CommonStrings.common_seen_by))
|
||||
}
|
||||
)
|
||||
val receipts = state.selectedEvent?.readReceiptState?.receipts().orEmpty()
|
||||
val receipts = state.selectedEvent?.readReceiptState?.receipts.orEmpty()
|
||||
receipts.forEach {
|
||||
val userId = UserId(it.avatarData.id)
|
||||
MatrixUserRow(
|
||||
|
||||
@@ -31,7 +31,7 @@ class ReadReceiptBottomSheetStateProvider : PreviewParameterProvider<ReadReceipt
|
||||
.map { readReceiptViewState ->
|
||||
ReadReceiptBottomSheetState(
|
||||
selectedEvent = aTimelineItemEvent(
|
||||
readReceiptState = TimelineItemReadReceipts.ReadReceipts(
|
||||
readReceiptState = TimelineItemReadReceipts(
|
||||
receipts = readReceiptViewState.receipts.map { readReceiptData ->
|
||||
readReceiptData
|
||||
.copy(avatarData = readReceiptData.avatarData.copy(id = "@${readReceiptData.avatarData.id}:localhost"))
|
||||
|
||||
@@ -67,7 +67,7 @@ class TimelineItemsFactory @Inject constructor(
|
||||
|
||||
suspend fun replaceWith(
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
roomMembers: List<RoomMember>?,
|
||||
roomMembers: List<RoomMember>,
|
||||
) = withContext(dispatchers.computation) {
|
||||
lock.withLock {
|
||||
diffCacheUpdater.updateWith(timelineItems)
|
||||
@@ -77,7 +77,7 @@ class TimelineItemsFactory @Inject constructor(
|
||||
|
||||
private suspend fun buildAndEmitTimelineItemStates(
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
roomMembers: List<RoomMember>?,
|
||||
roomMembers: List<RoomMember>,
|
||||
) {
|
||||
val newTimelineItemStates = ArrayList<TimelineItem>()
|
||||
for (index in diffCache.indices().reversed()) {
|
||||
@@ -97,7 +97,7 @@ class TimelineItemsFactory @Inject constructor(
|
||||
private suspend fun buildAndCacheItem(
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
index: Int,
|
||||
roomMembers: List<RoomMember>?,
|
||||
roomMembers: List<RoomMember>,
|
||||
): TimelineItem? {
|
||||
val timelineItemState =
|
||||
when (val currentTimelineItem = timelineItems[index]) {
|
||||
|
||||
@@ -47,7 +47,7 @@ class TimelineItemEventFactory @Inject constructor(
|
||||
currentTimelineItem: MatrixTimelineItem.Event,
|
||||
index: Int,
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
roomMembers: List<RoomMember>?,
|
||||
roomMembers: List<RoomMember>,
|
||||
): TimelineItem.Event {
|
||||
val currentSender = currentTimelineItem.event.sender
|
||||
val groupPosition =
|
||||
@@ -132,10 +132,9 @@ class TimelineItemEventFactory @Inject constructor(
|
||||
}
|
||||
|
||||
private fun MatrixTimelineItem.Event.computeReadReceiptState(
|
||||
roomMembers: List<RoomMember>?,
|
||||
roomMembers: List<RoomMember>,
|
||||
): TimelineItemReadReceipts {
|
||||
if (roomMembers == null) return TimelineItemReadReceipts.Hidden
|
||||
return TimelineItemReadReceipts.ReadReceipts(
|
||||
return TimelineItemReadReceipts(
|
||||
receipts = event.receipts
|
||||
.map { receipt ->
|
||||
val roomMember = roomMembers.find { it.userId == receipt.userId }
|
||||
|
||||
@@ -18,25 +18,12 @@ package io.element.android.features.messages.impl.timeline.model
|
||||
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
sealed interface TimelineItemReadReceipts {
|
||||
/**
|
||||
* Value when the feature is disabled.
|
||||
*/
|
||||
data object Hidden : TimelineItemReadReceipts
|
||||
|
||||
data class ReadReceipts(
|
||||
val receipts: ImmutableList<ReadReceiptData>,
|
||||
) : TimelineItemReadReceipts
|
||||
}
|
||||
data class TimelineItemReadReceipts(
|
||||
val receipts: ImmutableList<ReadReceiptData>,
|
||||
)
|
||||
|
||||
data class ReadReceiptData(
|
||||
val avatarData: AvatarData,
|
||||
val formattedDate: String,
|
||||
)
|
||||
|
||||
fun TimelineItemReadReceipts.receipts(): ImmutableList<ReadReceiptData> = when (this) {
|
||||
TimelineItemReadReceipts.Hidden -> persistentListOf()
|
||||
is TimelineItemReadReceipts.ReadReceipts -> receipts
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ internal fun aMessageEvent(
|
||||
sentTime = "",
|
||||
isMine = isMine,
|
||||
reactionsState = aTimelineItemReactions(count = 0),
|
||||
readReceiptState = TimelineItemReadReceipts.ReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||
localSendState = sendState,
|
||||
inReplyTo = inReplyTo,
|
||||
debugInfo = debugInfo,
|
||||
|
||||
@@ -44,7 +44,7 @@ class TimelineItemGrouperTest {
|
||||
senderDisplayName = "",
|
||||
content = TimelineItemStateEventContent(body = "a state event"),
|
||||
reactionsState = aTimelineItemReactions(count = 0),
|
||||
readReceiptState = TimelineItemReadReceipts.ReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
|
||||
inReplyTo = null,
|
||||
isThreaded = false,
|
||||
|
||||
Reference in New Issue
Block a user