diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index db80b92a16..358eba9c25 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -302,6 +302,7 @@ fun MessagesView( onSwipeToReply = { targetEvent -> state.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, targetEvent)) }, + onJoinCallClick = onJoinCallClick, forceJumpToBottomVisibility = forceJumpToBottomVisibility, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, knockRequestsBannerView = knockRequestsBannerView, @@ -477,6 +478,7 @@ private fun MessagesViewContent( onSendLocationClick: () -> Unit, onCreatePollClick: () -> Unit, onViewAllPinnedMessagesClick: () -> Unit, + onJoinCallClick: (isAudioCall: Boolean) -> Unit, forceJumpToBottomVisibility: Boolean, onSwipeToReply: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, @@ -534,6 +536,7 @@ private fun MessagesViewContent( onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, onReadReceiptClick = onReadReceiptClick, + onJoinCallClick = onJoinCallClick, forceJumpToBottomVisibility = forceJumpToBottomVisibility, nestedScrollConnection = scrollBehavior.nestedScrollConnection, floatingDateTopOffset = topBannersHeightDp, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index bdad9ba631..dae2ceb836 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -239,6 +239,7 @@ private fun PinnedMessagesListLoaded( onMoreReactionsClick = {}, onReadReceiptClick = {}, onSwipeToReply = {}, + onJoinCallClick = {}, eventSink = { timelineItemEvent -> when (timelineItemEvent) { is TimelineEvent.OpenThread -> state.eventSink(PinnedMessagesListEvent.OpenThread(timelineItemEvent.threadRootEventId)) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 6c0dce8cca..7db220dd01 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -130,6 +130,7 @@ fun TimelineView( onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit, + onJoinCallClick: (isAudioCall: Boolean) -> Unit, modifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), forceJumpToBottomVisibility: Boolean = false, @@ -219,6 +220,7 @@ fun TimelineView( onMoreReactionsClick = onMoreReactionsClick, onReadReceiptClick = onReadReceiptClick, onSwipeToReply = onSwipeToReply, + onJoinCallClick = onJoinCallClick, eventSink = state.eventSink, ) } @@ -599,6 +601,7 @@ internal fun TimelineViewPreview( onSwipeToReply = {}, onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, + onJoinCallClick = {}, onMoreReactionsClick = {}, onReadReceiptClick = {}, onGalleryItemClick = { _, _ -> }, @@ -649,6 +652,7 @@ private fun TimelineViewWithReadMarker( onReadReceiptClick = {}, forceJumpToBottomVisibility = true, forceJumpToReadMarkerVisibility = true, + onJoinCallClick = { }, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt index 32d4aded2b..4c80641659 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt @@ -49,6 +49,7 @@ internal fun TimelineViewMessageShieldPreview() = ElementPreview { onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, onReadReceiptClick = {}, + onJoinCallClick = {}, onGalleryItemClick = { _, _ -> }, forceJumpToBottomVisibility = true, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ActiveCallTimelineItemView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ActiveCallTimelineItemView.kt new file mode 100644 index 0000000000..9b3702c4d3 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ActiveCallTimelineItemView.kt @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.timeline.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +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.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.messages.impl.timeline.TimelineRoomInfo +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts +import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo +import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState +import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView +import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent +import io.element.android.libraries.dateformatter.api.toHumanReadableDuration +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.AvatarRow +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.onKeyboardContextMenuAction +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE +import io.element.android.libraries.designsystem.preview.USER_NAME_BOB +import io.element.android.libraries.designsystem.preview.USER_NAME_CAROL +import io.element.android.libraries.designsystem.preview.USER_NAME_CHARLIE +import io.element.android.libraries.designsystem.preview.USER_NAME_DAVID +import io.element.android.libraries.designsystem.preview.USER_NAME_JOHN_DOE +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.utils.CommonDrawables +import io.element.android.libraries.matrix.api.notification.CallIntent +import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.ui.strings.CommonPlurals +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.seconds + +@Composable +internal fun ActiveCallTimelineItemView( + timelineRoomInfo: TimelineRoomInfo, + event: TimelineItem.Event, + state: RtcNotificationState.Active, + isLastOutgoingMessage: Boolean, + onLongClick: (TimelineItem.Event) -> Unit, + onReadReceiptsClick: (TimelineItem.Event) -> Unit, + onJoinCallClick: (isAudioCall: Boolean) -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier = modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .padding(start = 16.dp, end = 16.dp, top = 16.dp) + .border(1.dp, ElementTheme.colors.borderInteractivePrimary, RoundedCornerShape(8.dp)) + .combinedClickable( + enabled = true, + onClick = {}, + onLongClick = { onLongClick(event) }, + onLongClickLabel = stringResource(CommonStrings.action_open_context_menu), + ) + .onKeyboardContextMenuAction { onLongClick(event) } + .padding(start = 12.dp, end = 12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier + .padding(top = 12.dp, bottom = 12.dp) + .size(40.dp) + .background(ElementTheme.colors.bgSubtleSecondary, RoundedCornerShape(8.dp)) + .padding(10.dp), + imageVector = if (state.callIntent == CallIntent.AUDIO) CompoundIcons.VoiceCallSolid() else CompoundIcons.VideoCallSolid(), + contentDescription = null, + tint = ElementTheme.colors.iconPrimary, + ) + + if (timelineRoomInfo.isDm) { + DirectMessageCallBody(state, Modifier.weight(1f)) + } else { + GroupCallBody( + event, + state, + Modifier + .weight(1f) + .padding(top = 12.dp, bottom = 12.dp) + ) + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(top = 12.dp, bottom = 12.dp) + ) { + if (!state.isJoined) { + Button( + text = stringResource(CommonStrings.action_join), + size = ButtonSize.Small, + onClick = { onJoinCallClick(state.callIntent == CallIntent.AUDIO) } + ) + } + if (state.callStartTsMillis != null) { + CallDurationText( + state.callStartTsMillis, + ) + } + } + } + + TimelineItemReadReceiptView( + state = ReadReceiptViewState( + sendState = event.localSendState, + isLastOutgoingMessage = isLastOutgoingMessage, + receipts = event.readReceiptState.receipts, + ), + onReadReceiptsClick = { onReadReceiptsClick(event) }, + modifier = Modifier.padding(top = 4.dp), + ) + } +} + +@Composable +private fun DirectMessageCallBody(state: RtcNotificationState.Active, modifier: Modifier = Modifier) { + Row( + verticalAlignment = Alignment.Top, + modifier = modifier + ) { + if (state.joinedMembers.size == 1) { + val caller = state.joinedMembers.first() + Avatar( + avatarData = AvatarData( + id = caller.userId.value, + name = caller.displayName, + url = caller.avatarUrl, + size = AvatarSize.ActiveCallItem, + ), + avatarType = AvatarType.User + ) + Text( + modifier = Modifier.padding(start = 6.dp), + text = stringResource(CommonStrings.common_user_started_a_call, caller.displayName ?: caller.userId), + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textPrimary, + ) + } else { + AvatarRow( + avatarDataList = state.joinedMembers + .map { user -> + AvatarData( + id = user.userId.value, + name = user.displayName, + url = user.avatarUrl, + size = AvatarSize.ActiveCallItem + ) + } + .toImmutableList(), + avatarType = AvatarType.User, + overlapRatio = 0.5f, + lastOnTop = true, + ) + Text( + modifier = Modifier.padding(start = 6.dp), + text = stringResource(CommonStrings.common_call_in_progress), + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textPrimary, + ) + } + } +} + +@Composable +private fun GroupCallBody(event: TimelineItem.Event, state: RtcNotificationState.Active, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + Text( + text = stringResource(CommonStrings.common_group_call_in_progress), + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + if (state.joinedMembers.size == 1) { + val caller = state.joinedMembers.first() + Avatar( + avatarData = AvatarData( + id = caller.userId.value, + name = caller.displayName, + url = caller.avatarUrl, + size = AvatarSize.ActiveCallItem, + ), + avatarType = AvatarType.User + ) + // Only show started a call if the last remaining user in the call + // is really the one who started the call + if (caller.userId == event.senderId) { + Text( + modifier = Modifier.padding(start = 6.dp), + text = stringResource(CommonStrings.common_user_started_a_call, caller.displayName ?: caller.userId), + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } else { + val displayMembers = state.joinedMembers.take(3) + AvatarRow( + avatarDataList = displayMembers + .map { user -> + AvatarData( + id = user.userId.value, + name = user.displayName, + url = user.avatarUrl, + size = AvatarSize.ActiveCallItem + ) + } + .toImmutableList(), + avatarType = AvatarType.User, + overlapRatio = 0.5f, + lastOnTop = true, + ) + Text( + text = pluralStringResource( + CommonPlurals.screen_timeline_active_call_joined_count, + count = state.joinedMembers.size, + state.joinedMembers.size + ), + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + } +} + +@Composable +private fun CallDurationText( + startTsMillis: Long, + modifier: Modifier = Modifier, +) { + fun formatElapsed(startTsMillis: Long): String = + (System.currentTimeMillis() - startTsMillis).coerceAtLeast(0).toHumanReadableDuration() + + val duration by produceState( + initialValue = formatElapsed(startTsMillis), + startTsMillis, + ) { + while (true) { + delay(1.seconds) + value = formatElapsed(startTsMillis) + } + } + Text( + modifier = modifier, + text = "($duration)", + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) +} + +@PreviewsDayNight +@PreviewWithLargeHeight +@Composable +internal fun ActiveCallTimelineItemViewPreview() = ElementPreview( + drawableFallbackForImages = CommonDrawables.sample_avatar +) { + val readReceiptState = mutableListOf( + aTimelineItemReadReceipts( + receipts = List(3) { aReadReceiptData(it) }, + ) + ) + Column(modifier = Modifier.padding(bottom = 16.dp)) { + listOf(CallIntent.AUDIO, CallIntent.VIDEO).forEach { callIntent -> + listOf( + RtcNotificationState.Active( + listOf(aMatrixUser(displayName = USER_NAME_ALICE, avatarUrl = "anUrl")), + isJoined = true, + callStartTsMillis = System.currentTimeMillis() - 60_000, + callIntent = callIntent + ) to true, + RtcNotificationState.Active( + listOf( + aMatrixUser(displayName = USER_NAME_ALICE, avatarUrl = "anUrl"), + aMatrixUser(displayName = USER_NAME_BOB) + ), + isJoined = true, + callStartTsMillis = System.currentTimeMillis() - 60_000, + callIntent = callIntent + ) to true, + RtcNotificationState.Active( + listOf( + aMatrixUser(displayName = USER_NAME_ALICE), + aMatrixUser(displayName = USER_NAME_BOB, avatarUrl = "anUrlB"), + aMatrixUser(displayName = USER_NAME_CAROL), + aMatrixUser(displayName = USER_NAME_DAVID), + aMatrixUser(displayName = USER_NAME_CHARLIE), + aMatrixUser(displayName = USER_NAME_JOHN_DOE, avatarUrl = "anUrlC"), + ), + isJoined = false, + callStartTsMillis = System.currentTimeMillis(), + callIntent = callIntent + ) to false, + // Group call only one member still in call, but it is not the creator + RtcNotificationState.Active( + listOf( + aMatrixUser(displayName = USER_NAME_ALICE, avatarUrl = "aliceURl"), + ), + isJoined = true, + callStartTsMillis = System.currentTimeMillis(), + callIntent = callIntent + ) to false, + // Group call only one member that is the call creator + RtcNotificationState.Active( + listOf( + aMatrixUser( + id = "@senderId:domain", + displayName = "Sender" + ), + ), + isJoined = true, + callStartTsMillis = System.currentTimeMillis(), + callIntent = callIntent + ) to false, + ).forEach { (state, isDm) -> + val content = TimelineItemRtcNotificationContent(callIntent, state) + ActiveCallTimelineItemView( + timelineRoomInfo = aTimelineRoomInfo(isDm = isDm), + event = aTimelineItemEvent( + content = content, + // Only display read receipts for the first item + readReceiptState = readReceiptState.removeFirstOrNull() ?: aTimelineItemReadReceipts(), + ), + state, + isLastOutgoingMessage = false, + onLongClick = {}, + onReadReceiptsClick = {}, + onJoinCallClick = {}, + ) + } + } + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt index a7ab44db54..6c86767dd4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt @@ -52,6 +52,7 @@ internal fun TimelineItemCallNotifyView( timelineRoomInfo: TimelineRoomInfo, event: TimelineItem.Event, content: TimelineItemRtcNotificationContent, + state: RtcNotificationState.Tombstoned, isLastOutgoingMessage: Boolean, onLongClick: (TimelineItem.Event) -> Unit, onReadReceiptsClick: (TimelineItem.Event) -> Unit, @@ -83,7 +84,7 @@ internal fun TimelineItemCallNotifyView( Text( modifier = Modifier.weight(1f), - text = stringResource(getTextRes(timelineRoomInfo, content)), + text = stringResource(getTextRes(timelineRoomInfo, state)), style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary, maxLines = 1, @@ -114,13 +115,13 @@ internal fun TimelineItemCallNotifyView( @StringRes private fun getTextRes( timelineRoomInfo: TimelineRoomInfo, - content: TimelineItemRtcNotificationContent + state: RtcNotificationState.Tombstoned ): Int = if (timelineRoomInfo.isDm) { - when (content.state) { + when (state) { is RtcNotificationState.Declined -> { - if (content.state.byMe) CommonStrings.common_call_you_declined else CommonStrings.common_call_declined + if (state.byMe) CommonStrings.common_call_you_declined else CommonStrings.common_call_declined } - RtcNotificationState.Started -> CommonStrings.common_call_started + else -> CommonStrings.common_call_started } } else { // In Rooms, do not show declined info. @@ -166,6 +167,7 @@ internal fun TimelineItemCallNotifyViewPreview() = ElementPreview { readReceiptState = readReceiptState.removeFirstOrNull() ?: aTimelineItemReadReceipts(), ), content = content, + state, isLastOutgoingMessage = false, onLongClick = {}, onReadReceiptsClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index 024169b00a..5b620a7dc3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -186,6 +186,7 @@ private fun TimelineItemGroupedEventsRowContent( onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, onReadReceiptClick = onReadReceiptClick, + onJoinCallClick = {}, onSwipeToReply = {}, eventSink = eventSink, eventContentView = eventContentView, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 20444903ad..fb9659ff70 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -31,6 +31,7 @@ import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent @@ -70,6 +71,7 @@ internal fun TimelineItemRow( onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit, + onJoinCallClick: (isAudioCall: Boolean) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, @@ -123,14 +125,27 @@ internal fun TimelineItemRow( ) } is TimelineItemRtcNotificationContent -> { - TimelineItemCallNotifyView( - timelineRoomInfo = timelineRoomInfo, - event = timelineItem, - content = timelineItem.content, - isLastOutgoingMessage = isLastOutgoingMessage, - onLongClick = onLongClick, - onReadReceiptsClick = onReadReceiptClick, - ) + when (timelineItem.content.state) { + is RtcNotificationState.Active -> ActiveCallTimelineItemView( + timelineRoomInfo = timelineRoomInfo, + event = timelineItem, + state = timelineItem.content.state, + isLastOutgoingMessage = isLastOutgoingMessage, + onLongClick = onLongClick, + onReadReceiptsClick = onReadReceiptClick, + onJoinCallClick = onJoinCallClick, + ) + is RtcNotificationState.Started, is RtcNotificationState.Declined -> + TimelineItemCallNotifyView( + timelineRoomInfo = timelineRoomInfo, + event = timelineItem, + content = timelineItem.content, + state = timelineItem.content.state, + isLastOutgoingMessage = isLastOutgoingMessage, + onLongClick = onLongClick, + onReadReceiptsClick = onReadReceiptClick, + ) + } } else -> { val a11yVoiceMessage = stringResource(CommonStrings.a11y_voice_message) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index 3b3e5118d6..3bc7f9f070 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -21,6 +21,8 @@ import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.toMatrixUser 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 @@ -39,6 +41,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.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.api.strings.StringProvider @@ -58,13 +61,14 @@ class TimelineItemContentFactory( private val dateFormatter: DateFormatter, private val stringProvider: StringProvider, ) { - suspend fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent { + suspend fun create(eventTimelineItem: EventTimelineItem, roomMembers: List): TimelineItemEventContent { return create( itemContent = eventTimelineItem.content, eventId = eventTimelineItem.eventId, isEditable = eventTimelineItem.isEditable, sender = eventTimelineItem.sender, senderProfile = eventTimelineItem.senderProfile, + roomMembers = roomMembers, ) } @@ -74,6 +78,7 @@ class TimelineItemContentFactory( isEditable: Boolean, sender: UserId, senderProfile: ProfileDetails, + roomMembers: List = emptyList(), ): TimelineItemEventContent { val isOutgoing = sessionId == sender return when (itemContent) { @@ -106,10 +111,26 @@ class TimelineItemContentFactory( is UnableToDecryptContent -> utdFactory.create(itemContent) is CallNotifyContent -> TimelineItemRtcNotificationContent( callIntent = itemContent.callIntent, - state = if (itemContent.declinedBy.isEmpty()) { - RtcNotificationState.Started - } else { - RtcNotificationState.Declined(itemContent.declinedBy.any { it == sessionId }) + state = when { + itemContent.activeMembers.isNotEmpty() -> { + RtcNotificationState.Active( + joinedMembers = itemContent.activeMembers.map { active -> + roomMembers.find { + it.userId == active + }?.toMatrixUser() ?: MatrixUser(active) + }, + isJoined = itemContent.isJoined, + callStartTsMillis = itemContent.callStartTsMillis, + callIntent = itemContent.callIntent + ) + } + else -> { + if (itemContent.declinedBy.isEmpty()) { + RtcNotificationState.Started + } else { + RtcNotificationState.Declined(itemContent.declinedBy.any { it == sessionId }) + } + } } ) is UnknownContent -> TimelineItemUnknownContent diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index a36755dc09..f29f23e903 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -108,7 +108,7 @@ class TimelineItemEventFactory( senderId = currentSender, senderProfile = senderProfile, senderAvatar = senderAvatarData, - content = contentFactory.create(currentTimelineItem.event), + content = contentFactory.create(currentTimelineItem.event, roomMembers), isMine = currentTimelineItem.event.isOwn, isEditable = currentTimelineItem.event.isEditable, canBeRepliedTo = currentTimelineItem.event.canBeRepliedTo, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt index 2359f196a9..c0db3a7547 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt @@ -10,13 +10,27 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.matrix.api.notification.CallIntent import io.element.android.libraries.matrix.api.timeline.item.event.EventType +import io.element.android.libraries.matrix.api.user.MatrixUser // State of the call, for now only isDeclined but in the future could be missed, active. sealed interface RtcNotificationState { - /** Some users have declined, byMe indicates if the current user is one of them. */ - data class Declined(val byMe: Boolean) : RtcNotificationState + /** Call is currently active. **/ + data class Active( + val joinedMembers: List, + val isJoined: Boolean, + val callStartTsMillis: Long?, + val callIntent: CallIntent, + ) : RtcNotificationState - object Started : RtcNotificationState + /** Some users have declined, byMe indicates if the current user is one of them. */ + data class Declined(val byMe: Boolean) : RtcNotificationState, Tombstoned + + object Started : RtcNotificationState, Tombstoned + + /** + * Tag for Tombstoned/Past calls. + */ + interface Tombstoned } class TimelineItemRtcNotificationContent( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt index 4ef8600043..efbbbee01e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt @@ -77,7 +77,8 @@ class DefaultMessageSummaryFormatter( context.getString(CommonStrings.common_call_declined) } } - RtcNotificationState.Started -> context.getString(CommonStrings.common_call_started) + RtcNotificationState.Started, is RtcNotificationState.Active + -> context.getString(CommonStrings.common_call_started) } } // Truncate the message to a safe length to avoid crashes in Compose diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 6348d9b115..bcddc37a74 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -218,6 +218,7 @@ private fun AndroidComposeUiTest.setTimelineView( onReadReceiptClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onGalleryItemClick: (TimelineItem.Event, Int) -> Unit = EnsureNeverCalledWithTwoParams(), forceJumpToBottomVisibility: Boolean = false, + onJoinCallClick: (isAudioCall: Boolean) -> Unit = EnsureNeverCalledWithParam(), ) { setSafeContent(clearAndroidUiDispatcher = true) { TimelineView( @@ -234,6 +235,7 @@ private fun AndroidComposeUiTest.setTimelineView( onReadReceiptClick = onReadReceiptClick, onGalleryItemClick = onGalleryItemClick, forceJumpToBottomVisibility = forceJumpToBottomVisibility, + onJoinCallClick = onJoinCallClick, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 320457cedd..eb27780073 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -79,5 +79,6 @@ enum class AvatarSize(val dp: Dp) { LeaveSpaceRoom(32.dp), SelectParentSpace(32.dp), AccountItem(32.dp), - LocationPin(32.dp) + LocationPin(32.dp), + ActiveCallItem(20.dp) } diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt index cd3a8df349..9a79f78413 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/RtcNotificationContentFormatterTest.kt @@ -43,8 +43,11 @@ class RtcNotificationContentFormatterTest : RobolectricTest() { fun `Should not display declined info in rooms`() { val result = formatter.format( CallNotifyContent( - CallIntent.VIDEO, - declinedBy = listOf(A_USER_ID_2, A_USER_ID_3) + callIntent = CallIntent.VIDEO, + declinedBy = listOf(A_USER_ID_2, A_USER_ID_3), + activeMembers = emptyList(), + isJoined = false, + callStartTsMillis = 0 ), false ) @@ -58,7 +61,10 @@ class RtcNotificationContentFormatterTest : RobolectricTest() { val result = formatter.format( CallNotifyContent( CallIntent.VIDEO, - declinedBy = listOf(fakeMatrixClient.sessionId) + declinedBy = listOf(fakeMatrixClient.sessionId), + activeMembers = listOf(A_USER_ID_2), + isJoined = false, + callStartTsMillis = 0 ), true ) @@ -72,7 +78,10 @@ class RtcNotificationContentFormatterTest : RobolectricTest() { val result = formatter.format( CallNotifyContent( CallIntent.VIDEO, - declinedBy = listOf(A_USER_ID_2) + declinedBy = listOf(A_USER_ID_2), + activeMembers = listOf(fakeMatrixClient.sessionId), + isJoined = true, + callStartTsMillis = 0 ), true ) @@ -86,7 +95,10 @@ class RtcNotificationContentFormatterTest : RobolectricTest() { val result = formatter.format( CallNotifyContent( CallIntent.AUDIO, - declinedBy = listOf() + declinedBy = listOf(), + activeMembers = emptyList(), + isJoined = false, + callStartTsMillis = 0 ), true ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index b1501601d9..c2d388ecb0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -120,7 +120,10 @@ data object LegacyCallInviteContent : EventContent data class CallNotifyContent( val callIntent: CallIntent, - val declinedBy: List + val declinedBy: List, + val activeMembers: List, + val isJoined: Boolean, + val callStartTsMillis: Long?, ) : EventContent data object UnknownContent : EventContent diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index fa671bc546..98efa505d0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -154,7 +154,10 @@ class TimelineEventContentMapper( } else { CallIntent.VIDEO }, - declinedBy = it.declinedBy.map(::UserId) + declinedBy = it.declinedBy.map(::UserId), + activeMembers = it.activeMembers.map(::UserId), + callStartTsMillis = it.callStartTsMillis?.toLong(), + isJoined = it.isJoined ) } } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt index d00f5a3eb2..4ed89e8923 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt @@ -68,7 +68,13 @@ class EventItemFactoryTest { fun `create check all null cases`() { val factory = createEventItemFactory() val contents = listOf( - CallNotifyContent(callIntent = CallIntent.VIDEO, emptyList()), + CallNotifyContent( + callIntent = CallIntent.VIDEO, + declinedBy = emptyList(), + activeMembers = emptyList(), + isJoined = false, + callStartTsMillis = null, + ), FailedToParseMessageLikeContent("", ""), FailedToParseStateContent("", "", ""), LegacyCallInviteContent, diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png index 0ccdcbb603..160d58f9ec 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0424685437a433b35b589668b409d55f26ab41bcae01af0121a54033c56a8c2 -size 646194 +oid sha256:c4e7b28b78cfbcbb6abf00317eea945859ac6465cecf81a3ad15801b0de721e4 +size 646216 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_Day_0_en.png new file mode 100644 index 0000000000..947dc86c51 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c24d5387bfc751396e258d4c53ac84547fb9f18f00d12e506fb747b1867aa4b +size 59223 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_Night_0_en.png new file mode 100644 index 0000000000..c19b613595 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da8763d8164555fb531238f70df3565831fca93208b44e737b8da13711bda1c3 +size 59450 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_en.png new file mode 100644 index 0000000000..38d7dcda9e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_ActiveCallTimelineItemView_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:757bf94f152cd24e33871165b57f3bd5c0e5e3d0666009b0be8a344c3b24599f +size 50275 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png index ab6392a424..0435f55de6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94f89a37549a3f198442db467bc7d8539f0b5649d97927bfe731f090d6dfb03d -size 379505 +oid sha256:ff839731834d7d0c94fa9a2c412014ddbaeaa053c5af1c8cbd00a3f3a583f364 +size 379511 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png index 3ac59caeea..1f0fdabe6d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a94d5198e27338675c0e15df5874ba3272ee315b8d77291dc7ccc7c754ed1f4 -size 365082 +oid sha256:b8bb5a6de6601da8d7fe59f110a054577e2e25dae4bb449f60c04e7d7a1b9551 +size 365073 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png index 29771235f0..058a3992c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60b9f2b920cd2a2a414805d1f0f77925232d9ae60bd607fa18d4395cda2dd73d -size 363291 +oid sha256:00fe1e676086766a3eac3e4457eff52c989789fc5d8d0df2624c019ed25ce9af +size 363322 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png index 94428abb56..84d9d4e5c3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36d7cf2510d546d2b330b1d1b1674ce58df0558c547e443794a1e7b2222add87 -size 347897 +oid sha256:7c609a633ab23eae5729ecb57a17a385bac65e81cbf0180c01e0847f1028039e +size 347890 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png index 22ff4965d1..bac23aab98 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:787151f8f242a8794a804bf14831f6ca680814d617e85feb3652103e8a0f20cd -size 346565 +oid sha256:e30686c7c037ef102717e0316543faf4d3ed2671c21eca96a667150daab380d6 +size 346614 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png index 67490ba700..01d4dca84b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4431e2d9f30123949ea5676ce60f642234adcf9b5a33f2acc3f80ebf6609a66 -size 369730 +oid sha256:ef2bf449671b652cb152b3baab92ab100eab77a54229ab49daeab156f2f484d0 +size 369724 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png index 1600afff71..f0dfbc3321 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:273351cc9c533b2424859a0de00a8a3786628abc3a88175705c4205dedf7e9aa -size 354737 +oid sha256:f7a6302320bd1e4258e03487539bf46b0b12781dfc8a22b3f9f267017a6146fa +size 354751 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png index 1b99468924..8aa2cca147 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a9509411777f342261cab58b92a022b3c9f8f0f7a9e4d0527120a91976575f0 -size 357733 +oid sha256:02ecb903124273e24151ea2088f6b99144f7dcadba00a582b6210dd23835b5c9 +size 357743 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png index 14a40a5676..a332744358 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a11044fe90227f1f316a1ade8587e545bb620e11cca76865fc2eb83cdb817fc5 -size 364655 +oid sha256:588967869cbd25ed8b82aff5618175660a8a9e45a5a861b1c7867cf1913f575b +size 364646 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png index 63bd9c3f6d..0041a870b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6cd94664f89bdad2a9761ea8b20acb83fed3ed7cb5be41af8fe4852643c0bfc -size 356175 +oid sha256:a2bd77c08be705c66225f16c29fd6826decd92b41922d0f8ffc0d1a63d8c59d2 +size 356193 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png index d235670def..6e83d6491d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a00bd766a00b7e7c6615fc53bf469b3ca5056a6c514c096431ae38d99d604306 -size 364845 +oid sha256:34152c742abc486a48234e22101833bc163cf11979890d06aac41e68e3c404d0 +size 364846 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png index 422c324695..77abe20982 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1df8d22766981fc76d6d767dc3f40d98cadc7c51be20417b067b91af604232b -size 367884 +oid sha256:5c3d87862c6d7fc88d0f23cd54c5b9f77028992fdd7c282a195b964dd17e85ac +size 367855 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png index 448bcec677..1d34761a9b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe0daf9161bc2da194992a2fc9505bf2009c5dbed78cdb35d2eb7ed1380a7df5 -size 353037 +oid sha256:f7d9b5fc0ff5d7828ad6f4dc0c9bd9520b4920553da97ed7b4e030ee844c0208 +size 353054 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png index 5560c21967..ceca184bd7 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af4a48ee80535f2c0a226c4ffa4344054fb63b7b0a88ea2c030a4eb8dd5e9644 -size 356078 +oid sha256:8e82011a72f36889c53ab61ed2b9947c7fb5f868c9b7010904fc10f4ca587082 +size 356082 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png index e9d85990e8..a431a9429a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66d4b6ea4a697735bbb0fac936b89cde99c5077973ba828894bf50b5dae233b3 -size 362848 +oid sha256:93c2df04d65f6078ce3f7067fbae8a7317ec42ed0fc013dee962a3953269f3b7 +size 362813 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png index aa322fc2e4..ae8495618a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fae87094ea13b14e2dc7c16ca96e4f8096ff39e962799468fb4ead4c666620f -size 354381 +oid sha256:08057fdc1e1184720b438ddd2a8753f00791baf2456cc30b45de8e6bdeb57e8e +size 354404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png index 0cfd1dc985..1a8ea3e4e9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:545c9e8933e420cc3f8fbba0fbd5162c946c65a20721faeb77c568596ca720cd -size 363045 +oid sha256:66aba7b4271a990fac85b0bbc7cbdc371fa1e38a4da5bb4e2f62a80b21cb3a8f +size 363012 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png index 7cca911c45..074820e157 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309 -size 52770 +oid sha256:7caacb336793b8ae4b83e9a10a82325e25e8b3c036f881898ed28688250eb6e0 +size 55744 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png index 616c06bf46..36d19c6b72 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98 -size 53966 +oid sha256:7dfa2767c55a81d3f121b8fa55443b7b05f9369e91639b80e0be349863799ad7 +size 55438