diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt new file mode 100644 index 0000000000..625282f1e8 --- /dev/null +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appconfig + +object TimelineConfig { + const val maxReadReceiptToDisplay = 3 +} diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 809cc9441e..6faf07f916 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) api(projects.features.messages.api) + implementation(projects.appconfig) implementation(projects.features.call) implementation(projects.features.location.api) implementation(projects.features.poll.api) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index a9a9e7fa50..2bb6e85df4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -42,6 +42,7 @@ import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent @@ -97,6 +98,7 @@ class MessagesPresenter @AssistedInject constructor( private val customReactionPresenter: CustomReactionPresenter, private val reactionSummaryPresenter: ReactionSummaryPresenter, private val retrySendMenuPresenter: RetrySendMenuPresenter, + private val readReceiptBottomSheetPresenter: ReadReceiptBottomSheetPresenter, private val networkMonitor: NetworkMonitor, private val snackbarDispatcher: SnackbarDispatcher, private val messageSummaryFormatter: MessageSummaryFormatter, @@ -124,6 +126,7 @@ class MessagesPresenter @AssistedInject constructor( val customReactionState = customReactionPresenter.present() val reactionSummaryState = reactionSummaryPresenter.present() val retryState = retrySendMenuPresenter.present() + val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) @@ -201,6 +204,7 @@ class MessagesPresenter @AssistedInject constructor( customReactionState = customReactionState, reactionSummaryState = reactionSummaryState, retrySendMenuState = retryState, + readReceiptBottomSheetState = readReceiptBottomSheetState, hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, snackbarMessage = snackbarMessage, showReinvitePrompt = showReinvitePrompt, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 5bce4f19a1..325c695988 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState 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.components.retrysendmenu.RetrySendMenuState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.libraries.architecture.Async @@ -43,6 +44,7 @@ data class MessagesState( val customReactionState: CustomReactionState, val reactionSummaryState: ReactionSummaryState, val retrySendMenuState: RetrySendMenuState, + val readReceiptBottomSheetState: ReadReceiptBottomSheetState, val hasNetworkConnection: Boolean, val snackbarMessage: SnackbarMessage?, val inviteProgress: Async, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 38d6289a88..720c385f89 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState 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.components.retrysendmenu.RetrySendMenuState import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState @@ -96,6 +97,10 @@ fun aMessagesState() = MessagesState( selectedEvent = null, eventSink = {}, ), + readReceiptBottomSheetState = ReadReceiptBottomSheetState( + selectedEvent = null, + eventSink = {}, + ), actionListState = anActionListState(), customReactionState = CustomReactionState( target = CustomReactionState.Target.None, 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 c9063f2a79..d07afd43b5 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 @@ -70,6 +70,8 @@ import io.element.android.features.messages.impl.timeline.components.customreact import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheet +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuEvents import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMessageMenu import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -212,6 +214,9 @@ fun MessagesView( onReactionClicked = ::onEmojiReactionClicked, onReactionLongClicked = ::onEmojiReactionLongClicked, onMoreReactionsClicked = ::onMoreReactionsClicked, + onReadReceiptClick = { event -> + state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(event)) + }, onSendLocationClicked = onSendLocationClicked, onCreatePollClicked = onCreatePollClicked, onSwipeToReply = { targetEvent -> @@ -246,13 +251,12 @@ fun MessagesView( ) ReactionSummaryView(state = state.reactionSummaryState) - RetrySendMessageMenu( - state = state.retrySendMenuState - ) - - ReinviteDialog( - state = state + RetrySendMessageMenu(state = state.retrySendMenuState) + ReadReceiptBottomSheet( + state = state.readReceiptBottomSheetState, + onUserDataClicked = onUserDataClicked, ) + ReinviteDialog(state = state) // Since the textfield is now based on an Android view, this is no longer done automatically. // We need to hide the keyboard automatically when navigating out of this screen. @@ -310,6 +314,7 @@ private fun MessagesViewContent( onReactionClicked: (key: String, TimelineItem.Event) -> Unit, onReactionLongClicked: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClicked: (TimelineItem.Event) -> Unit, + onReadReceiptClick: (TimelineItem.Event) -> Unit, onMessageLongClicked: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSendLocationClicked: () -> Unit, @@ -381,6 +386,7 @@ private fun MessagesViewContent( onReactionClicked = onReactionClicked, onReactionLongClicked = onReactionLongClicked, onMoreReactionsClicked = onMoreReactionsClicked, + onReadReceiptClick = onReadReceiptClick, onSwipeToReply = onSwipeToReply, ) }, @@ -406,7 +412,8 @@ private fun MessagesViewComposerBottomSheetContents( if (state.userHasPermissionToSendMessage) { Column(modifier = modifier.fillMaxWidth()) { MentionSuggestionsPickerView( - modifier = Modifier.heightIn(max = 230.dp) + modifier = Modifier + .heightIn(max = 230.dp) // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions .nestedScroll(object : NestedScrollConnection { override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index c9dcbbc8ab..0bad8f8830 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -35,11 +35,14 @@ import io.element.android.features.messages.impl.timeline.session.SessionState import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus @@ -64,6 +67,7 @@ class TimelinePresenter @Inject constructor( private val analyticsService: AnalyticsService, private val verificationService: SessionVerificationService, private val encryptionService: EncryptionService, + private val featureFlagService: FeatureFlagService, private val redactedVoiceMessageManager: RedactedVoiceMessageManager, ) : Presenter { @@ -99,6 +103,9 @@ class TimelinePresenter @Inject constructor( } } + val readReceiptsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.ReadReceipts).collectAsState(initial = false) + val membersState by room.membersStateFlow.collectAsState() + fun handleEvents(event: TimelineEvents) { when (event) { TimelineEvents.LoadMore -> localScope.paginateBackwards() @@ -138,7 +145,17 @@ class TimelinePresenter @Inject constructor( LaunchedEffect(Unit) { timeline .timelineItems - .onEach(timelineItemsFactory::replaceWith) + .onEach { + timelineItemsFactory.replaceWith( + timelineItems = it, + roomMembers = if (readReceiptsEnabled) { + membersState.roomMembers().orEmpty() + } else { + // Give an empty list to not affect performance + emptyList() + } + ) + } .onEach { timelineItems -> if (timelineItems.isEmpty()) { paginateBackwards() @@ -153,6 +170,7 @@ class TimelinePresenter @Inject constructor( userHasPermissionToSendMessage = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, + showReadReceipts = readReceiptsEnabled, hasNewItems = hasNewItems.value, sessionState = sessionState, eventSink = ::handleEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index 173e33b9c9..62836db130 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -26,6 +26,7 @@ import kotlinx.collections.immutable.ImmutableList @Immutable data class TimelineState( val timelineItems: ImmutableList, + val showReadReceipts: Boolean, val highlightedEventId: EventId?, val userHasPermissionToSendMessage: Boolean, val paginationState: MatrixTimeline.PaginationState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index affb77e88a..3defab97f5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -16,9 +16,11 @@ 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 +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts 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 @@ -43,6 +45,7 @@ import kotlin.random.Random fun aTimelineState(timelineItems: ImmutableList = persistentListOf()) = TimelineState( timelineItems = timelineItems, + showReadReceipts = false, paginationState = MatrixTimeline.PaginationState( isBackPaginating = false, hasMoreToLoadBackwards = true, @@ -118,11 +121,12 @@ internal fun aTimelineItemEvent( senderDisplayName: String = "Sender", content: TimelineItemEventContent = aTimelineItemTextContent(), groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, - sendState: LocalEventSendState = LocalEventSendState.Sent(eventId), + sendState: LocalEventSendState? = null, inReplyTo: InReplyTo? = null, isThreaded: Boolean = false, debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(), timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(), + readReceiptState: TimelineItemReadReceipts = aTimelineItemReadReceipts(), ): TimelineItem.Event { return TimelineItem.Event( id = UUID.randomUUID().toString(), @@ -132,6 +136,7 @@ internal fun aTimelineItemEvent( senderAvatar = AvatarData("@senderId:domain", "sender", size = AvatarSize.TimelineSender), content = content, reactionsState = timelineItemReactions, + readReceiptState = readReceiptState, sentTime = "12:34", isMine = isMine, senderDisplayName = senderDisplayName, @@ -173,6 +178,12 @@ internal fun aTimelineItemDebugInfo( model, originalJson, latestEditedJson ) +internal fun aTimelineItemReadReceipts(): TimelineItemReadReceipts { + return TimelineItemReadReceipts( + receipts = emptyList().toImmutableList(), + ) +} + fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents { val event = aTimelineItemEvent( isMine = true, 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 5b6b8c6d1d..1d831e5e21 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 @@ -92,6 +92,7 @@ fun TimelineView( onReactionClicked: (emoji: String, TimelineItem.Event) -> Unit, onReactionLongClicked: (emoji: String, TimelineItem.Event) -> Unit, onMoreReactionsClicked: (TimelineItem.Event) -> Unit, + onReadReceiptClick: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, ) { fun onReachedLoadMore() { @@ -126,6 +127,9 @@ fun TimelineView( ) { timelineItem -> TimelineItemRow( timelineItem = timelineItem, + showReadReceipts = state.showReadReceipts, + isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true + && state.timelineItems.first().identifier() == timelineItem.identifier(), highlightedItem = state.highlightedEventId?.value, userHasPermissionToSendMessage = state.userHasPermissionToSendMessage, onClick = onMessageClicked, @@ -135,6 +139,7 @@ fun TimelineView( onReactionClick = onReactionClicked, onReactionLongClick = onReactionLongClicked, onMoreReactionsClick = onMoreReactionsClicked, + onReadReceiptClick = onReadReceiptClick, onTimestampClicked = onTimestampClicked, sessionState = state.sessionState, eventSink = state.eventSink, @@ -169,6 +174,8 @@ fun TimelineView( @Composable private fun TimelineItemRow( timelineItem: TimelineItem, + showReadReceipts: Boolean, + isLastOutgoingMessage: Boolean, highlightedItem: String?, userHasPermissionToSendMessage: Boolean, sessionState: SessionState, @@ -179,6 +186,7 @@ private fun TimelineItemRow( onReactionClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit, + onReadReceiptClick: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, eventSink: (TimelineEvents) -> Unit, @@ -205,6 +213,8 @@ private fun TimelineItemRow( } else { TimelineItemEventRow( event = timelineItem, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = highlightedItem == timelineItem.identifier(), canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(), onClick = { onClick(timelineItem) }, @@ -214,6 +224,7 @@ private fun TimelineItemRow( onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, + onReadReceiptClick = onReadReceiptClick, onTimestampClicked = onTimestampClicked, onSwipeToReply = { onSwipeToReply(timelineItem) }, eventSink = eventSink, @@ -244,6 +255,8 @@ private fun TimelineItemRow( timelineItem.events.forEach { subGroupEvent -> TimelineItemRow( timelineItem = subGroupEvent, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, sessionState = sessionState, userHasPermissionToSendMessage = false, @@ -255,6 +268,7 @@ private fun TimelineItemRow( onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, + onReadReceiptClick = onReadReceiptClick, eventSink = eventSink, onSwipeToReply = {}, ) @@ -362,6 +376,7 @@ internal fun TimelineViewPreview( onReactionLongClicked = { _, _ -> }, onMoreReactionsClicked = {}, onSwipeToReply = {}, + onReadReceiptClick = {}, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index d4fccc296b..5f7fff64ae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -66,6 +66,8 @@ 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.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.toExtraPadding +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.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState @@ -114,6 +116,8 @@ import kotlin.math.roundToInt @Composable fun TimelineItemEventRow( event: TimelineItem.Event, + showReadReceipts: Boolean, + isLastOutgoingMessage: Boolean, isHighlighted: Boolean, canReply: Boolean, onClick: () -> Unit, @@ -124,6 +128,7 @@ fun TimelineItemEventRow( onReactionClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onReactionLongClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, + onReadReceiptClick: (event: TimelineItem.Event) -> Unit, onSwipeToReply: () -> Unit, eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier @@ -173,6 +178,8 @@ fun TimelineItemEventRow( state = state.draggableState, ), event = event, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, interactionSource = interactionSource, onClick = onClick, @@ -183,6 +190,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onReadReceiptsClicked = { onReadReceiptClick(event) }, eventSink = eventSink, ) } @@ -190,6 +198,8 @@ fun TimelineItemEventRow( } else { TimelineItemEventRowContent( event = event, + showReadReceipts = showReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, interactionSource = interactionSource, onClick = onClick, @@ -200,6 +210,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onReadReceiptsClicked = { onReadReceiptClick(event) }, eventSink = eventSink, ) } @@ -232,6 +243,8 @@ private fun SwipeSensitivity( @Composable private fun TimelineItemEventRowContent( event: TimelineItem.Event, + showReadReceipts: Boolean, + isLastOutgoingMessage: Boolean, isHighlighted: Boolean, interactionSource: MutableInteractionSource, onClick: () -> Unit, @@ -240,6 +253,7 @@ private fun TimelineItemEventRowContent( inReplyToClicked: () -> Unit, onUserDataClicked: () -> Unit, onReactionClicked: (emoji: String) -> Unit, + onReadReceiptsClicked: () -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, eventSink: (TimelineEvents) -> Unit, @@ -256,7 +270,12 @@ private fun TimelineItemEventRowContent( .wrapContentHeight() .fillMaxWidth(), ) { - val (sender, message, reactions) = createRefs() + val ( + sender, + message, + reactions, + readReceipts, + ) = createRefs() // Sender val avatarStrokeSize = 3.dp @@ -322,6 +341,25 @@ private fun TimelineItemEventRowContent( .padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp) ) } + + // Read receipts / Send state + TimelineItemReadReceiptView( + state = ReadReceiptViewState( + sendState = event.localSendState, + isLastOutgoingMessage = isLastOutgoingMessage, + receipts = event.readReceiptState.receipts, + ), + showReadReceipts = showReadReceipts, + 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) + } + } + ) } } @@ -658,6 +696,8 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { ), groupPosition = TimelineItemGroupPosition.First, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -667,6 +707,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -679,6 +720,8 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { ), groupPosition = TimelineItemGroupPosition.Last, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -688,6 +731,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -718,6 +762,8 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { inReplyTo = aInReplyToReady(replyContent), groupPosition = TimelineItemGroupPosition.First, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -727,6 +773,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -741,6 +788,8 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { isThreaded = true, groupPosition = TimelineItemGroupPosition.Last, ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -750,6 +799,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -792,6 +842,8 @@ internal fun TimelineItemEventRowTimestampPreview( reactionsState = aTimelineItemReactions(count = 0), senderDisplayName = if (useDocument) "Document case" else "Text case", ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -801,6 +853,7 @@ internal fun TimelineItemEventRowTimestampPreview( onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, eventSink = {}, @@ -824,6 +877,8 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { ), timelineItemReactions = aTimelineItemReactions(count = 20), ), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -833,6 +888,7 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, eventSink = {}, @@ -849,6 +905,8 @@ 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, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -858,6 +916,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, eventSink = {}, @@ -870,6 +929,8 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { TimelineItemEventRow( event = aTimelineItemEvent(content = aTimelineItemPollContent()), + showReadReceipts = false, + isLastOutgoingMessage = false, isHighlighted = false, canReply = true, onClick = {}, @@ -879,6 +940,7 @@ internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, onMoreReactionsClick = {}, + onReadReceiptClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, eventSink = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt index c5e94b0742..764b4cdea4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt @@ -69,7 +69,7 @@ private fun TimelineItemReactionsView( onToggleExpandClick: () -> Unit, modifier: Modifier = Modifier ) { - // In LTR languages we want an incoming message's reactions to be LRT and outgoing to be RTL. + // In LTR languages we want an incoming message's reactions to be LTR and outgoing to be RTL. // For RTL languages it should be the opposite. val currentLayout = LocalLayoutDirection.current val reactionsLayoutDirection = if (!isOutgoing) currentLayout diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt new file mode 100644 index 0000000000..a15ecc781b --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt + +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import kotlinx.collections.immutable.ImmutableList + +data class ReadReceiptViewState( + val sendState: LocalEventSendState?, + val isLastOutgoingMessage: Boolean, + val receipts: ImmutableList, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt new file mode 100644 index 0000000000..e0f72b010d --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData +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.anAvatarData +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import kotlinx.collections.immutable.toImmutableList + +class ReadReceiptViewStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aReadReceiptViewState(), + aReadReceiptViewState(sendState = LocalEventSendState.NotSentYet), + aReadReceiptViewState(sendState = LocalEventSendState.Sent(EventId("\$eventId"))), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(1) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(2) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(3) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(4) { add(aReadReceiptData(it)) } }, + ), + aReadReceiptViewState( + sendState = LocalEventSendState.Sent(EventId("\$eventId")), + receipts = mutableListOf().apply { repeat(5) { add(aReadReceiptData(it)) } }, + ), + ) +} + +private fun aReadReceiptViewState( + sendState: LocalEventSendState? = null, + isLastOutgoingMessage: Boolean = true, + receipts: List = emptyList(), +) = ReadReceiptViewState( + sendState = sendState, + isLastOutgoingMessage = isLastOutgoingMessage, + receipts = receipts.toImmutableList(), +) + +private fun aReadReceiptData( + index: Int, + avatarData: AvatarData = anAvatarData( + id = "$index", + size = AvatarSize.TimelineReadReceipt + ), + formattedDate: String = "12:34", +) = ReadReceiptData( + avatarData = avatarData, + formattedDate = formattedDate, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt new file mode 100644 index 0000000000..8725b365d0 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.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.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import io.element.android.appconfig.TimelineConfig +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.getBestName +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.CommonDrawables +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonPlurals +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun TimelineItemReadReceiptView( + state: ReadReceiptViewState, + showReadReceipts: Boolean, + onReadReceiptsClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + if (state.receipts.isNotEmpty()) { + if (showReadReceipts) { + ReadReceiptsRow(modifier = modifier) { + ReadReceiptsAvatars( + receipts = state.receipts, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .clickable { onReadReceiptsClicked() } + .padding(2.dp) + ) + } + } + } else when (state.sendState) { + LocalEventSendState.NotSentYet -> { + ReadReceiptsRow(modifier) { + Icon( + modifier = Modifier.padding(2.dp), + resourceId = CommonDrawables.ic_sending, + contentDescription = null, + tint = ElementTheme.colors.iconSecondary + ) + } + } + LocalEventSendState.Canceled -> Unit + is LocalEventSendState.SendingFailed -> { + // Error? The timestamp is already displayed in red + } + null, + is LocalEventSendState.Sent -> { + if (state.isLastOutgoingMessage) { + ReadReceiptsRow(modifier = modifier) { + Icon( + modifier = Modifier.padding(2.dp), + resourceId = CommonDrawables.ic_sent, + contentDescription = null, + tint = ElementTheme.colors.iconSecondary + ) + } + } + } + } +} + +@Composable +private fun ReadReceiptsRow( + modifier: Modifier = Modifier, + content: @Composable () -> Unit = {}, +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(AvatarSize.TimelineReadReceipt.dp + 8.dp) + .padding(horizontal = 18.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + ) { + content() + } + } +} + +@Composable +private fun ReadReceiptsAvatars( + receipts: ImmutableList, + modifier: Modifier = Modifier +) { + val avatarSize = AvatarSize.TimelineReadReceipt.dp + val avatarStrokeSize = 1.dp + val avatarStrokeColor = MaterialTheme.colorScheme.background + val receiptDescription = computeReceiptDescription(receipts) + Row( + modifier = modifier + .clearAndSetSemantics { + stateDescription = receiptDescription + }, + horizontalArrangement = Arrangement.spacedBy(4.dp - avatarStrokeSize), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + contentAlignment = Alignment.CenterEnd, + ) { + receipts + .take(TimelineConfig.maxReadReceiptToDisplay) + .reversed() + .forEachIndexed { index, readReceiptData -> + Box( + modifier = Modifier + .padding(end = (12.dp + avatarStrokeSize * 2) * index) + .size(size = avatarSize + avatarStrokeSize * 2) + .clip(CircleShape) + .background(avatarStrokeColor) + .zIndex(index.toFloat()), + contentAlignment = Alignment.Center, + ) { + Avatar( + avatarData = readReceiptData.avatarData, + ) + } + } + } + if (receipts.size > TimelineConfig.maxReadReceiptToDisplay) { + Text( + text = "+" + (receipts.size - TimelineConfig.maxReadReceiptToDisplay), + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } +} + +@Composable +private fun computeReceiptDescription(receipts: ImmutableList): String { + return when (receipts.size) { + 0 -> "" // Cannot happen + 1 -> stringResource( + id = CommonStrings.a11y_read_receipts_single, + receipts[0].avatarData.getBestName() + ) + 2 -> stringResource( + id = CommonStrings.a11y_read_receipts_multiple, + receipts[0].avatarData.getBestName(), + receipts[1].avatarData.getBestName(), + ) + else -> pluralStringResource( + id = CommonPlurals.a11y_read_receipts_multiple_with_others, + count = receipts.size - 1, + receipts[0].avatarData.getBestName(), + receipts.size - 1 + ) + } +} + +@PreviewsDayNight +@Composable +internal fun TimelineItemReactionsViewPreview( + @PreviewParameter(ReadReceiptViewStateProvider::class) state: ReadReceiptViewState, +) = ElementPreview { + TimelineItemReadReceiptView( + state = state, + showReadReceipts = true, + onReadReceiptsClicked = {}, + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt new file mode 100644 index 0000000000..391ec3de7f --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +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.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.components.MatrixUserRow +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun ReadReceiptBottomSheet( + state: ReadReceiptBottomSheetState, + onUserDataClicked: (UserId) -> Unit, + modifier: Modifier = Modifier, +) { + val isVisible = state.selectedEvent != null + + val sheetState = rememberModalBottomSheetState() + val coroutineScope = rememberCoroutineScope() + if (isVisible) { + ModalBottomSheet( + modifier = modifier, +// modifier = modifier.navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044 +// .imePadding() + sheetState = sheetState, + onDismissRequest = { + coroutineScope.launch { + sheetState.hide() + state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + } + } + ) { + ReadReceiptBottomSheetContent( + state = state, + onUserDataClicked = { + coroutineScope.launch { + sheetState.hide() + state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + onUserDataClicked.invoke(it) + } + }, + ) + // FIXME remove after https://issuetracker.google.com/issues/275849044 + Spacer(modifier = Modifier.height(32.dp)) + } + } +} + +@Composable +private fun ColumnScope.ReadReceiptBottomSheetContent( + state: ReadReceiptBottomSheetState, + onUserDataClicked: (UserId) -> Unit, +) { + ListItem( + headlineContent = { + Text(text = stringResource(id = CommonStrings.common_seen_by)) + } + ) + val receipts = state.selectedEvent?.readReceiptState?.receipts.orEmpty() + receipts.forEach { + val userId = UserId(it.avatarData.id) + MatrixUserRow( + modifier = Modifier.clickable { onUserDataClicked(userId) }, + matrixUser = MatrixUser( + userId = userId, + displayName = it.avatarData.name, + avatarUrl = it.avatarData.url, + ), + avatarSize = AvatarSize.ReadReceiptList, + trailingContent = { + Text( + text = it.formattedDate, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + ) + } +} + +@PreviewsDayNight +@Composable +internal fun ReadReceiptBottomSheetPreview(@PreviewParameter(ReadReceiptBottomSheetStateProvider::class) state: ReadReceiptBottomSheetState) = ElementPreview { + // TODO restore RetrySendMessageMenuBottomSheet once the issue with bottom sheet not being previewable is fixed + Column { + ReadReceiptBottomSheetContent( + state = state, + onUserDataClicked = {}, + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt new file mode 100644 index 0000000000..213a43277d --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import io.element.android.features.messages.impl.timeline.model.TimelineItem + +sealed interface ReadReceiptBottomSheetEvents { + data class EventSelected(val event: TimelineItem.Event) : ReadReceiptBottomSheetEvents + data object Dismiss : ReadReceiptBottomSheetEvents +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt new file mode 100644 index 0000000000..a4a55cbc9e --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class ReadReceiptBottomSheetPresenter @Inject constructor( +) : Presenter { + + @Composable + override fun present(): ReadReceiptBottomSheetState { + var selectedEvent: TimelineItem.Event? by remember { mutableStateOf(null) } + + fun handleEvent(event: ReadReceiptBottomSheetEvents) { + @Suppress("LiftReturnOrAssignment") + when (event) { + is ReadReceiptBottomSheetEvents.EventSelected -> { + selectedEvent = event.event + } + ReadReceiptBottomSheetEvents.Dismiss -> { + selectedEvent = null + } + } + } + + return ReadReceiptBottomSheetState( + selectedEvent = selectedEvent, + eventSink = { handleEvent(it) }, + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt new file mode 100644 index 0000000000..34db5488fa --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.runtime.Immutable +import io.element.android.features.messages.impl.timeline.model.TimelineItem + +@Immutable +data class ReadReceiptBottomSheetState( + val selectedEvent: TimelineItem.Event?, + val eventSink: (ReadReceiptBottomSheetEvents) -> Unit, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt new file mode 100644 index 0000000000..3a3bf1dbc4 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStateProvider +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import kotlinx.collections.immutable.toImmutableList + +class ReadReceiptBottomSheetStateProvider : PreviewParameterProvider { + // Reuse the provider ReadReceiptViewStateProvider + private val readReceiptViewStateProvider = ReadReceiptViewStateProvider() + override val values: Sequence = readReceiptViewStateProvider.values + .filter { it.sendState is LocalEventSendState.Sent } + .map { readReceiptViewState -> + ReadReceiptBottomSheetState( + selectedEvent = aTimelineItemEvent( + readReceiptState = TimelineItemReadReceipts( + receipts = readReceiptViewState.receipts.map { readReceiptData -> + readReceiptData + .copy(avatarData = readReceiptData.avatarData.copy(id = "@${readReceiptData.avatarData.id}:localhost")) + }.toImmutableList() + ) + ), + eventSink = {}, + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt index 8c894bc99a..ffc1a1b3f1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.androidutils.diff.DiffCacheUpdater import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -66,19 +67,23 @@ class TimelineItemsFactory @Inject constructor( suspend fun replaceWith( timelineItems: List, + roomMembers: List, ) = withContext(dispatchers.computation) { lock.withLock { diffCacheUpdater.updateWith(timelineItems) - buildAndEmitTimelineItemStates(timelineItems) + buildAndEmitTimelineItemStates(timelineItems, roomMembers) } } - private suspend fun buildAndEmitTimelineItemStates(timelineItems: List) { + private suspend fun buildAndEmitTimelineItemStates( + timelineItems: List, + roomMembers: List, + ) { val newTimelineItemStates = ArrayList() for (index in diffCache.indices().reversed()) { val cacheItem = diffCache.get(index) if (cacheItem == null) { - buildAndCacheItem(timelineItems, index)?.also { timelineItemState -> + buildAndCacheItem(timelineItems, index, roomMembers)?.also { timelineItemState -> newTimelineItemStates.add(timelineItemState) } } else { @@ -91,11 +96,12 @@ class TimelineItemsFactory @Inject constructor( private suspend fun buildAndCacheItem( timelineItems: List, - index: Int + index: Int, + roomMembers: List, ): TimelineItem? { val timelineItemState = when (val currentTimelineItem = timelineItems[index]) { - is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems) + is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems, roomMembers) is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem) MatrixTimelineItem.Other -> null } 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 14f8429c85..4c9217a884 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 @@ -19,13 +19,17 @@ package io.element.android.features.messages.impl.timeline.factories.event import io.element.android.features.messages.impl.timeline.groups.canBeDisplayedInBubbleBlock import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.AggregatedReactionSender +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 +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts import io.element.android.libraries.core.bool.orTrue +import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import kotlinx.collections.immutable.toImmutableList @@ -36,12 +40,14 @@ import javax.inject.Inject class TimelineItemEventFactory @Inject constructor( private val contentFactory: TimelineItemContentFactory, private val matrixClient: MatrixClient, + private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, ) { suspend fun create( currentTimelineItem: MatrixTimelineItem.Event, index: Int, timelineItems: List, + roomMembers: List, ): TimelineItem.Event { val currentSender = currentTimelineItem.event.sender val groupPosition = @@ -84,6 +90,7 @@ class TimelineItemEventFactory @Inject constructor( sentTime = sentTime, groupPosition = groupPosition, reactionsState = currentTimelineItem.computeReactionsState(), + readReceiptState = currentTimelineItem.computeReadReceiptState(roomMembers), localSendState = currentTimelineItem.event.localSendState, inReplyTo = currentTimelineItem.event.inReplyTo(), isThreaded = currentTimelineItem.event.isThreaded(), @@ -102,7 +109,7 @@ class TimelineItemEventFactory @Inject constructor( key = reaction.key, currentUserId = matrixClient.sessionId, senders = reaction.senders - .sortedByDescending{ it.timestamp } + .sortedByDescending { it.timestamp } .map { val date = Date(it.timestamp) AggregatedReactionSender( @@ -124,6 +131,27 @@ class TimelineItemEventFactory @Inject constructor( return TimelineItemReactions(aggregatedReactions.toImmutableList()) } + private fun MatrixTimelineItem.Event.computeReadReceiptState( + roomMembers: List, + ): TimelineItemReadReceipts { + return TimelineItemReadReceipts( + receipts = event.receipts + .map { receipt -> + val roomMember = roomMembers.find { it.userId == receipt.userId } + ReadReceiptData( + avatarData = AvatarData( + id = receipt.userId.value, + name = roomMember?.displayName, + url = roomMember?.avatarUrl, + size = AvatarSize.TimelineReadReceipt, + ), + formattedDate = lastMessageTimestampFormatter.format(receipt.timestamp) + ) + } + .toImmutableList() + ) + } + private fun computeGroupPosition( currentTimelineItem: MatrixTimelineItem.Event, timelineItems: List, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index bd3090e390..5ceaba5550 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -65,6 +65,7 @@ sealed interface TimelineItem { val isMine: Boolean = false, val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, val reactionsState: TimelineItemReactions, + val readReceiptState: TimelineItemReadReceipts, val localSendState: LocalEventSendState?, val inReplyTo: InReplyTo?, val isThreaded: Boolean, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt new file mode 100644 index 0000000000..dc5ae8289c --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.model + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import kotlinx.collections.immutable.ImmutableList + +data class TimelineItemReadReceipts( + val receipts: ImmutableList, +) + +data class ReadReceiptData( + val avatarData: AvatarData, + val formattedDate: String, +) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 88468e6857..7fdb2885c4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent @@ -654,10 +655,12 @@ class MessagesPresenterTest { analyticsService = analyticsService, encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), + featureFlagService = FakeFeatureFlagService(), redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), ) val preferencesStore = InMemoryPreferencesStore(isRichTextEditorEnabled = true) val actionListPresenter = ActionListPresenter(preferencesStore = preferencesStore) + val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter() val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom) val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom) @@ -670,6 +673,7 @@ class MessagesPresenterTest { customReactionPresenter = customReactionPresenter, reactionSummaryPresenter = reactionSummaryPresenter, retrySendMenuPresenter = retrySendMenuPresenter, + readReceiptBottomSheetPresenter = readReceiptBottomSheetPresenter, networkMonitor = FakeNetworkMonitor(), snackbarDispatcher = SnackbarDispatcher(), messageSummaryFormatter = FakeMessageSummaryFormatter(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt index 6d944075d1..68c80d6b80 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt @@ -17,7 +17,9 @@ package io.element.android.features.messages.fixtures 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.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -31,6 +33,7 @@ import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.room.aTimelineItemDebugInfo +import kotlinx.collections.immutable.toImmutableList internal fun aMessageEvent( eventId: EventId? = AN_EVENT_ID, @@ -50,6 +53,7 @@ internal fun aMessageEvent( sentTime = "", isMine = isMine, reactionsState = aTimelineItemReactions(count = 0), + readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), localSendState = sendState, inReplyTo = inReplyTo, debugInfo = debugInfo, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt index 18fa0e390e..972f105a4a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.timeline.groups.TimelineItemGro import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractorWithoutValidation import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter +import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter import io.element.android.libraries.eventformatter.api.TimelineEventFormatter import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem @@ -65,6 +66,7 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(), ), matrixClient = matrixClient, + lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(), ), virtualItemFactory = TimelineItemVirtualFactory( daySeparatorFactory = TimelineItemDaySeparatorFactory( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index fc60fbf759..15ec930d2e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -29,6 +29,7 @@ import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.session.SessionState +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.features.messages.voicemessages.timeline.FakeRedactedVoiceMessageManager import io.element.android.features.messages.voicemessages.timeline.aRedactedMatrixTimeline @@ -347,6 +348,7 @@ class TimelinePresenterTest { analyticsService = FakeAnalyticsService(), encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), + featureFlagService = FakeFeatureFlagService(), redactedVoiceMessageManager = redactedVoiceMessageManager, ) } @@ -363,6 +365,7 @@ class TimelinePresenterTest { analyticsService = analyticsService, encryptionService = FakeEncryptionService(), verificationService = FakeSessionVerificationService(), + featureFlagService = FakeFeatureFlagService(), redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt new file mode 100644 index 0000000000..248fcad399 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTests.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.timeline.components.receipt.bottomsheet + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter +import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class ReadReceiptBottomSheetPresenterTests { + + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - handle event selected`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val selectedEvent = aTimelineItemEvent() + initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) + assertThat(awaitItem().selectedEvent).isSameInstanceAs(selectedEvent) + } + } + + @Test + fun `present - handle dismiss`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val selectedEvent = aTimelineItemEvent() + initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) + skipItems(1) + initialState.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + assertThat(awaitItem().selectedEvent).isNull() + } + } + + private fun createPresenter() = ReadReceiptBottomSheetPresenter() +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt index 4c43a8552f..e8a8eb30cf 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt @@ -21,7 +21,9 @@ import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper import io.element.android.features.messages.impl.timeline.groups.computeGroupIdWith +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.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel import io.element.android.libraries.designsystem.components.avatar.anAvatarData @@ -42,6 +44,7 @@ class TimelineItemGrouperTest { senderDisplayName = "", content = TimelineItemStateEventContent(body = "a state event"), reactionsState = aTimelineItemReactions(count = 0), + readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), localSendState = LocalEventSendState.Sent(AN_EVENT_ID), inReplyTo = null, isThreaded = false, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt index b8be4d4d50..ccf3d4aa92 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -89,6 +89,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf( isRemote = false, localSendState = null, reactions = listOf(), + receipts = listOf(), sender = A_USER_ID, senderProfile = ProfileTimelineDetails.Unavailable, timestamp = 9442, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt index 9ba984f565..7bcb8bf1dd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt @@ -58,3 +58,7 @@ data class AvatarData( .uppercase() } } + +fun AvatarData.getBestName(): String { + return name?.takeIf { it.isNotEmpty() } ?: id +} 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 b2004ed204..0b4264e8e6 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 @@ -37,6 +37,9 @@ enum class AvatarSize(val dp: Dp) { TimelineRoom(32.dp), TimelineSender(32.dp), + TimelineReadReceipt(16.dp), + + ReadReceiptList(32.dp), MessageActionSender(32.dp), diff --git a/libraries/designsystem/src/main/res/drawable/ic_sending.xml b/libraries/designsystem/src/main/res/drawable/ic_sending.xml new file mode 100644 index 0000000000..92a8312f70 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_sending.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_sent.xml b/libraries/designsystem/src/main/res/drawable/ic_sent.xml new file mode 100644 index 0000000000..9a3ea31479 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_sent.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 0c08d25385..9bb6d2f862 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -66,5 +66,11 @@ enum class FeatureFlags( title = "Chat backup", description = "Allow access to backup and restore chat history settings", defaultValue = false, - ) + ), + ReadReceipts( + key = "feature.readreceipts", + title = "Show read receipts", + description = null, + defaultValue = false, + ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 89553cef3e..a1a2c3665c 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -41,6 +41,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.PinUnlock -> true FeatureFlags.Mentions -> false FeatureFlags.SecureStorage -> false + FeatureFlags.ReadReceipts -> false } } else { false diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 49108f8d54..9f38ce9441 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -30,6 +30,7 @@ data class EventTimelineItem( val isRemote: Boolean, val localSendState: LocalEventSendState?, val reactions: List, + val receipts: List, val sender: UserId, val senderProfile: ProfileTimelineDetails, val timestamp: Long, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt new file mode 100644 index 0000000000..f638d71c89 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.timeline.item.event + +import io.element.android.libraries.matrix.api.core.UserId + +data class Receipt( + val userId: UserId, + val timestamp: Long, +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 21e7d51638..d761e91d6c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -20,18 +20,20 @@ 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.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender +import io.element.android.libraries.matrix.api.timeline.item.event.Receipt +import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import org.matrix.rustcomponents.sdk.Reaction import org.matrix.rustcomponents.sdk.EventItemOrigin as RustEventItemOrigin import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails +import org.matrix.rustcomponents.sdk.Receipt as RustReceipt class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) { @@ -45,6 +47,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap isRemote = it.isRemote(), localSendState = it.localSendState()?.map(), reactions = it.reactions().map(), + receipts = it.readReceipts().map(), sender = UserId(it.sender()), senderProfile = it.senderProfile().map(), timestamp = it.timestamp().toLong(), @@ -92,6 +95,15 @@ private fun List?.map(): List { } ?: emptyList() } +private fun Map.map(): List { + return map { + Receipt( + userId = UserId(it.key), + timestamp = it.value.timestamp?.toLong() ?: 0 + ) + }.sortedByDescending { it.timestamp } +} + private fun RustEventTimelineItemDebugInfo.map(): TimelineItemDebugInfo { return TimelineItemDebugInfo( model = model, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index ae473d8da8..d12f168789 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -37,6 +37,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageType 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.Receipt import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -107,6 +108,7 @@ fun anEventTimelineItem( isRemote: Boolean = false, localSendState: LocalEventSendState? = null, reactions: List = emptyList(), + receipts: List = emptyList(), sender: UserId = A_USER_ID, senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), timestamp: Long = 0L, @@ -121,6 +123,7 @@ fun anEventTimelineItem( isRemote = isRemote, localSendState = localSendState, reactions = reactions, + receipts = receipts, sender = sender, senderProfile = senderProfile, timestamp = timestamp, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt index 1380d15e8b..947afd8bbd 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt @@ -31,11 +31,13 @@ fun MatrixUserRow( matrixUser: MatrixUser, modifier: Modifier = Modifier, avatarSize: AvatarSize = AvatarSize.UserListItem, + trailingContent: @Composable (() -> Unit)? = null, ) = UserRow( avatarData = matrixUser.getAvatarData(avatarSize), name = matrixUser.getBestName(), subtext = if (matrixUser.displayName.isNullOrEmpty()) null else matrixUser.userId.value, modifier = modifier, + trailingContent, ) @PreviewsDayNight diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt index 5e59ce7aef..7aa11ba193 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt @@ -38,6 +38,7 @@ internal fun UserRow( name: String, subtext: String?, modifier: Modifier = Modifier, + trailingContent: @Composable (() -> Unit)? = null, ) { Row( modifier = modifier @@ -49,7 +50,8 @@ internal fun UserRow( Avatar(avatarData) Column( modifier = Modifier - .padding(start = 12.dp), + .padding(start = 12.dp) + .weight(1f), ) { // Name Text( @@ -70,5 +72,6 @@ internal fun UserRow( ) } } + trailingContent?.invoke() } } diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b829f5c0e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2d69362d225e97cf9ca07e071de06c89d8ca4694214dfebde90b771a2c11f54 +size 6707 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..aeaf2f7dd7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5af53d9bbf0fb985efeb48b3e8f1c808e10fff496d6aafc068e7e50cfafcd798 +size 12367 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6ddbf182b0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9779c9c980c32b3e8ede34e5f9e7beeca6e0000fc77e1fc1cf0a8c0eb1da0be7 +size 17760 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d8023b8b76 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbcfa1b91e13109969c31b6e2d09c5099a1c27ed15912401db7ac946323a1027 +size 22729 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2ac6c8fa54 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97fd46b3b79c90a4104c2fd4e9f5667abf67d998d9f7136b9c18da920af4cb99 +size 27836 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8881b0cfed --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Day-48_48_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e767451a900ff3629047e9931784c1b5edd3a52e44dd4d172107c71fe92671d3 +size 33561 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..366399824d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63620fd5bfea92c632a0a4b1d88eabd51a973e7e4281af2b662b8955cc209e0a +size 6549 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..65bb8111f9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a8a1900a56b87bb96135cbbfe92e504b486775b5b0ba02bb6626a351081ef37 +size 12272 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..13a2cf3633 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63e229114afc8317c1690f817e4ba4a275c581c9f107c75e88c99728c6698219 +size 17755 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cd31a0814a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:175c2bed417fdfd3c80b8ce3079675decc3a27cd8df6d06c00db0015d193cb4e +size 22808 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b7eec1e1a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb15b22c4fc99d3f7c655e85d1e2c1b9d0ae4478908a4f40234a8a4651df7d49 +size 28080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7372f4b2a7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_null_ReadReceiptBottomSheet-Night-48_49_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21078a2cfa738bc3a2a7f36c52ea7c6ff9c83a9885f6059fe472121f08a08c05 +size 33681 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..601cda52a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec996e41aaecde031e8b4568f9dbe08b359b4390589b7e25c8da084a0fa9fc93 +size 5502 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e13c280efc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:379c809e565791ae199367bf9e7fe5d2e7767bbd5f33f51309d0caacc976dd5f +size 5356 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..601cda52a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec996e41aaecde031e8b4568f9dbe08b359b4390589b7e25c8da084a0fa9fc93 +size 5502 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..54d5ed96d5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61d4e36b624371171b6ee1909318dcc2ba96c55b42a92fb880b25abbbff48645 +size 5080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c9e7c2f15d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:821606acb7dcd9bdcad17bff0c33268f2ab22569e846e610c4401d852e17be2e +size 5628 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..173509112b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3552965164107b4a6881a5834bef98dfba2494c7c2186f6a6b8315a64c0c0d6b +size 6233 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..523cbf9120 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fedc10863be4b1777614ecf777ee24b43a4d9fb48059833cd08a122e6cec221 +size 6410 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2de7d24afd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Day-47_47_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b980fa478c1d508284323e094281db096a7b6360339493c59b0c1fd69c2288c3 +size 6609 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c7bd8a176b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3663e96a724a0a75e693c4c516e6adedc4c20967604c6931142972fcee8fe488 +size 5475 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..86623d35e8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fec37a2d969d74fba0c26a0ff179f204c52c8e3d0c4a2b2d189ba071e4428887 +size 5339 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c7bd8a176b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3663e96a724a0a75e693c4c516e6adedc4c20967604c6931142972fcee8fe488 +size 5475 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ad0545303d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d33ed61dd503fa4b3956e27cc7a06090c6eb589cf5dbdfd0dd781dfd335b774d +size 5213 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7bb404a775 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d29f6726900317e6869ae00759b9a861a1036a1a8280621bc1ed47c37752a18 +size 5901 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7c235a780c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e69990e066a3eb36e66820e2414397fdb415151ba76d9c062588e58e01e6e97 +size 6425 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..234380bde4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3cfd9aaf78a8e66993d269e3d5b49966eb067f2df1dc2582ba066d5e6a086b0 +size 6608 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8d0f250d51 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.receipt_TimelineItemReactionsView_null_TimelineItemReactionsView-Night-47_48_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91961cd20e51f2065a48720fe3a5067d3f640a7e87d5f369eb45842ba1f256f7 +size 6811 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-47_47_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-47_47_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-47_47_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-47_47_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-49_49_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-47_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-47_48_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-47_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-47_48_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-49_50_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-48_48_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-48_48_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-48_48_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-48_48_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-48_48_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-48_48_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-50_50_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-48_49_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-48_49_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-48_49_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-48_49_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-48_49_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-48_49_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-50_51_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-49_49_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-49_49_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-49_49_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-49_49_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Day-51_51_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-49_50_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-49_50_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-49_50_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-49_50_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_null_TimelineItemDaySeparatorView-Night-51_52_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Day-50_50_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Day-52_52_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Day-50_50_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Day-52_52_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Night-50_51_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Night-52_53_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Night-50_51_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_null_TimelineItemReadMarkerView-Night-52_53_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Day-51_51_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Day-53_53_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Day-51_51_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Day-53_53_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Night-51_52_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Night-53_54_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Night-51_52_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_null_TimelineItemRoomBeginningView-Night-53_54_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-52_52_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-54_54_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-52_52_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Day-54_54_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-52_53_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-54_55_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-52_53_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_null_TimelineLoadingMoreIndicator-Night-54_55_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-53_53_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-55_55_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-53_53_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-55_55_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-53_54_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-55_56_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-53_54_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-55_56_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png index df93febb6f..608ac4b27d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_33,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:748a2562d8c3e9b5e4def3b8ea3c782868e56f449de1b1ddb4bc36cd84945318 -size 20642 +oid sha256:a5953d21cc18cb10614361f7343fed9375ca851677b4a380e97e86851a082c3b +size 17118 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png index f97e401957..70d8326e1c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_34,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a6ce0c3b6fe3ac503ee7019dee234a9f96552e1106fe6858030a7c70c506d4f -size 19869 +oid sha256:d8134edc79b20771f89e8b15eed3b3660e46ab6f3805130252f32d2fb6b26cb6 +size 16745 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png index 4e2e281f59..7814f8545b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_35,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5af89d5988d168ee2cbd30e3ba6447f5cd1399d35a2ca04c7348e74cb623f37 -size 22501 +oid sha256:9e6022f750c6e26d822d4ffd1172cd8c34f7afe0188cce2da9f72d3dd8e4f45a +size 18030 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png index d21aa10955..174d9c208d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_36,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c49437c9f943cf044ec64a0ecab2b70d1fa811a34aff4baa8ed0ed846a4f5c5 -size 18595 +oid sha256:d5901d9bf468a0ca5fc7c3d6d308898b802b57967afe4d61c814f86e2194ee16 +size 17829 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png index 726d7087b0..a0c98f1f74 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_37,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:063cdf0bbdfc846d3040af143f79a0b6eb325beab62cba5657236d028db6bc26 -size 17267 +oid sha256:6aa7aa8f18b1a7d3a68c65e5275a69d49138e51046d913686f111f809cb2235f +size 17054 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png index 1f2604180e..085f22c2d0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_38,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8118e782f4ec7b67b6aa148f1bb31c9d30f03f72a7abfc73dc8d5094a5a7545c -size 21791 +oid sha256:e0dd5b1c404dd33a70083e7d775bf4c89e8eb1823e72711e8aa6c4947fb49b6e +size 19756 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png index 38c479cfdc..df93febb6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_39,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:032ed67865d3ad800371e193ea922589f27cf03697e3c91c8d698957befa8d42 -size 14590 +oid sha256:748a2562d8c3e9b5e4def3b8ea3c782868e56f449de1b1ddb4bc36cd84945318 +size 20642 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png index 8152088666..f97e401957 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_40,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54b00e4382ea77eb90e6de983ec1b363b504456cebd1754946759fd2ee1e9b85 -size 14232 +oid sha256:9a6ce0c3b6fe3ac503ee7019dee234a9f96552e1106fe6858030a7c70c506d4f +size 19869 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png index d8b2b5928a..4e2e281f59 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_41,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b9cdb7894f9fe410183d73a2febebe576de6967ad3f2183cfda504f081c567b -size 15475 +oid sha256:b5af89d5988d168ee2cbd30e3ba6447f5cd1399d35a2ca04c7348e74cb623f37 +size 22501 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png index cb0e6b6fc9..d21aa10955 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_42,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eac20dcc3e285cde5f3d2515d65416162362de196940b3c208886934592070e4 -size 21346 +oid sha256:0c49437c9f943cf044ec64a0ecab2b70d1fa811a34aff4baa8ed0ed846a4f5c5 +size 18595 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png index f939fb7b9e..726d7087b0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_43,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96d3a1b71b8372ca80b667ae57bf9f87b5e8167684ea62c8f71d90994113929f -size 19530 +oid sha256:063cdf0bbdfc846d3040af143f79a0b6eb325beab62cba5657236d028db6bc26 +size 17267 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png index f64b9f6a2d..1f2604180e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_44,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f3a728b5791495710209e5b308dda4b38defded1a44d00bbd69fb1c45877218 -size 25254 +oid sha256:8118e782f4ec7b67b6aa148f1bb31c9d30f03f72a7abfc73dc8d5094a5a7545c +size 21791 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png index d69da80047..38c479cfdc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_45,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:820b23e9b9175b48461a4e7e359c2440b8c45cb598f512ea360519b0815621fc -size 18259 +oid sha256:032ed67865d3ad800371e193ea922589f27cf03697e3c91c8d698957befa8d42 +size 14590 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png index 4cf9f8f839..8152088666 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_46,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ce4abbf622711cf6a106cb6c74ed729c6e609d329579b7b00ba8c1581b4b953 -size 17463 +oid sha256:54b00e4382ea77eb90e6de983ec1b363b504456cebd1754946759fd2ee1e9b85 +size 14232 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png index a475b2e7d1..d8b2b5928a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_47,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0cb196c59e3bbd845f0e110150ac358ca78c9c08c212e184423ead5d841f67c -size 20219 +oid sha256:8b9cdb7894f9fe410183d73a2febebe576de6967ad3f2183cfda504f081c567b +size 15475 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png index a3d0076294..cb0e6b6fc9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2619a9a70eca12d5931ebc21adce48a1aed8383f35908bb09546f44b40f04543 -size 23094 +oid sha256:eac20dcc3e285cde5f3d2515d65416162362de196940b3c208886934592070e4 +size 21346 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png index 81b9668b0f..f939fb7b9e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d7d71d58b250bdec2d9b6e1ed46c5e3ffd98ffeefcaf4267b9979970a89750b -size 22226 +oid sha256:96d3a1b71b8372ca80b667ae57bf9f87b5e8167684ea62c8f71d90994113929f +size 19530 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png index 5cb8c0a0bf..f64b9f6a2d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7bdea3caef3f1be9fac1fbb5511fe2d76b7985576c038f8c1d920615c3d49cd -size 25005 +oid sha256:1f3a728b5791495710209e5b308dda4b38defded1a44d00bbd69fb1c45877218 +size 25254 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_51,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_51,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d69da80047 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_51,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:820b23e9b9175b48461a4e7e359c2440b8c45cb598f512ea360519b0815621fc +size 18259 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_52,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_52,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4cf9f8f839 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_52,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ce4abbf622711cf6a106cb6c74ed729c6e609d329579b7b00ba8c1581b4b953 +size 17463 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_53,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_53,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a475b2e7d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_53,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0cb196c59e3bbd845f0e110150ac358ca78c9c08c212e184423ead5d841f67c +size 20219 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_54,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_54,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a3d0076294 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_54,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2619a9a70eca12d5931ebc21adce48a1aed8383f35908bb09546f44b40f04543 +size 23094 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_55,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_55,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..81b9668b0f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_55,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d7d71d58b250bdec2d9b6e1ed46c5e3ffd98ffeefcaf4267b9979970a89750b +size 22226 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_56,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_56,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5cb8c0a0bf --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_56,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7bdea3caef3f1be9fac1fbb5511fe2d76b7985576c038f8c1d920615c3d49cd +size 25005 diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index ca8370ae23..69608a0a1a 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -31,6 +31,9 @@ style: active: false UnusedPrivateMember: active: true + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 5 UnusedParameter: active: true UnnecessaryInnerClass: