diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index be78037d76..48c44e38c9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -48,8 +48,11 @@ open class ActionListStateProvider : PreviewParameterProvider { ), anActionListState().copy( target = ActionListState.Target.Success( - event = aTimelineItemEvent(content = aTimelineItemImageContent()).copy( - reactionsState = reactionsState + event = aTimelineItemEvent( + content = aTimelineItemImageContent(), + displayNameAmbiguous = true, + ).copy( + reactionsState = reactionsState, ), displayEmojiReactions = true, actions = aTimelineItemActionList(), @@ -142,6 +145,7 @@ fun aTimelineItemActionList(): ImmutableList { TimelineItemAction.ViewSource, ) } + fun aTimelineItemPollActionList(): ImmutableList { return persistentListOf( TimelineItemAction.EndPoll, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index 613fb9370f..6b7b611557 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -55,6 +55,8 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction +import io.element.android.features.messages.impl.sender.SenderName +import io.element.android.features.messages.impl.sender.SenderNameMode import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent @@ -143,8 +145,8 @@ fun ActionListView( onEmojiReactionClicked = ::onEmojiReactionClicked, onCustomReactionClicked = ::onCustomReactionClicked, modifier = Modifier - .navigationBarsPadding() - .imePadding() + .navigationBarsPadding() + .imePadding() ) } } @@ -175,8 +177,8 @@ private fun SheetContent( MessageSummary( event = target.event, modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(14.dp)) HorizontalDivider() @@ -268,15 +270,11 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif icon() Spacer(modifier = Modifier.width(8.dp)) Column(modifier = Modifier.weight(1f)) { - Row { - if (event.senderDisambiguatedDisplayName != null) { - Text( - text = event.senderDisambiguatedDisplayName, - style = ElementTheme.typography.fontBodySmMedium, - color = MaterialTheme.colorScheme.primary - ) - } - } + SenderName( + senderId = event.senderId, + senderProfile = event.senderProfile, + senderNameMode = SenderNameMode.ActionList, + ) content() } Spacer(modifier = Modifier.width(16.dp)) @@ -324,13 +322,13 @@ private fun EmojiReactionsRow( contentDescription = stringResource(id = CommonStrings.a11y_react_with_other_emojis), tint = MaterialTheme.colorScheme.secondary, modifier = Modifier - .size(24.dp) - .clickable( - enabled = true, - onClick = onCustomReactionClicked, - indication = rememberRipple(bounded = false, radius = emojiRippleRadius), - interactionSource = remember { MutableInteractionSource() } - ) + .size(24.dp) + .clickable( + enabled = true, + onClick = onCustomReactionClicked, + indication = rememberRipple(bounded = false, radius = emojiRippleRadius), + interactionSource = remember { MutableInteractionSource() } + ) ) } } @@ -354,11 +352,11 @@ private fun EmojiButton( } Box( modifier = Modifier - .size(48.dp) - .background(backgroundColor, CircleShape) - .clearAndSetSemantics { - contentDescription = description - }, + .size(48.dp) + .background(backgroundColor, CircleShape) + .clearAndSetSemantics { + contentDescription = description + }, contentAlignment = Alignment.Center ) { Text( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderName.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderName.kt new file mode 100644 index 0000000000..5142c00bdf --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderName.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2024 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.sender + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +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.clipToBounds +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +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.timeline.item.event.ProfileTimelineDetails + +// https://www.figma.com/file/Ni6Ii8YKtmXCKYNE90cC67/Timeline-(new)?type=design&node-id=917-80169&mode=design&t=A0CJCBbMqR8NOwUQ-0 +@Composable +fun SenderName( + senderId: UserId, + senderProfile: ProfileTimelineDetails, + senderNameMode: SenderNameMode, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + when (senderProfile) { + is ProfileTimelineDetails.Error, + ProfileTimelineDetails.Pending, + ProfileTimelineDetails.Unavailable -> { + MainText(text = senderId.value, mode = senderNameMode) + } + is ProfileTimelineDetails.Ready -> { + val displayName = senderProfile.displayName + if (displayName.isNullOrEmpty()) { + MainText(text = senderId.value, mode = senderNameMode) + } else { + MainText(text = displayName, mode = senderNameMode) + if (senderProfile.displayNameAmbiguous) { + SecondaryText(text = senderId.value, mode = senderNameMode) + } + } + } + } + } +} + +@Composable +private fun RowScope.MainText( + text: String, + mode: SenderNameMode, +) { + val style = when (mode) { + is SenderNameMode.Timeline -> ElementTheme.typography.fontBodyMdMedium + SenderNameMode.ActionList, + SenderNameMode.Reply -> ElementTheme.typography.fontBodySmMedium + } + val modifier = when (mode) { + is SenderNameMode.Timeline -> Modifier.alignByBaseline() + SenderNameMode.ActionList, + SenderNameMode.Reply -> Modifier + } + val color = when (mode) { + is SenderNameMode.Timeline -> mode.mainColor + SenderNameMode.ActionList, + SenderNameMode.Reply -> MaterialTheme.colorScheme.primary + } + Text( + modifier = modifier.clipToBounds(), + text = text, + style = style, + color = color, + overflow = TextOverflow.Ellipsis, + ) +} + +@Composable +private fun RowScope.SecondaryText( + text: String, + mode: SenderNameMode, +) { + val style = when (mode) { + is SenderNameMode.Timeline -> ElementTheme.typography.fontBodySmRegular + SenderNameMode.ActionList, + SenderNameMode.Reply -> ElementTheme.typography.fontBodyXsRegular + } + val modifier = when (mode) { + is SenderNameMode.Timeline -> Modifier.alignByBaseline() + SenderNameMode.ActionList, + SenderNameMode.Reply -> Modifier + } + Text( + modifier = modifier.clipToBounds(), + text = text, + style = style, + color = MaterialTheme.colorScheme.secondary, + overflow = TextOverflow.Ellipsis, + ) +} + +@PreviewsDayNight +@Composable +internal fun SenderNamePreview( + @PreviewParameter(SenderNameDataProvider::class) senderNameData: SenderNameData, +) = ElementPreview { + SenderName( + senderId = senderNameData.userId, + senderProfile = senderNameData.profileTimelineDetails, + senderNameMode = senderNameData.senderNameMode, + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderNameDataProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderNameDataProvider.kt new file mode 100644 index 0000000000..6498aa0bb4 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderNameDataProvider.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 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.sender + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.timeline.components.aProfileTimelineDetailsReady +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails + +data class SenderNameData( + val userId: UserId, + val profileTimelineDetails: ProfileTimelineDetails, + val senderNameMode: SenderNameMode, +) + +open class SenderNameDataProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + SenderNameMode.Timeline(mainColor = Color.Red), + SenderNameMode.Reply, + SenderNameMode.ActionList, + ) + .flatMap { senderNameMode -> + sequenceOf( + aSenderNameData(senderNameMode = senderNameMode), + aSenderNameData(displayNameAmbiguous = true, senderNameMode = senderNameMode), + SenderNameData(UserId("@alice:${senderNameMode.javaClass.name.lowercase()}"), ProfileTimelineDetails.Unavailable, senderNameMode = senderNameMode), + ) + } +} + +fun aSenderNameData( + displayNameAmbiguous: Boolean = false, + senderNameMode: SenderNameMode, +) = SenderNameData( + userId = UserId("@alice:${senderNameMode.javaClass.name.lowercase()}"), + profileTimelineDetails = aProfileTimelineDetailsReady( + displayName = "Alice ${senderNameMode.javaClass.name}", + displayNameAmbiguous = displayNameAmbiguous, + ), + senderNameMode = senderNameMode, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderNameMode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderNameMode.kt new file mode 100644 index 0000000000..6b83480650 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/sender/SenderNameMode.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 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.sender + +import androidx.compose.ui.graphics.Color + +sealed interface SenderNameMode { + data class Timeline(val mainColor: Color) : SenderNameMode + data object Reply : SenderNameMode + data object ActionList : SenderNameMode +} 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 e9322a6087..f2d2d8624c 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,6 +16,7 @@ package io.element.android.features.messages.impl.timeline +import io.element.android.features.messages.impl.timeline.components.aProfileTimelineDetailsReady import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData import io.element.android.features.messages.impl.timeline.model.InReplyToDetails import io.element.android.features.messages.impl.timeline.model.NewEventState @@ -130,7 +131,8 @@ internal fun aTimelineItemEvent( transactionId: TransactionId? = null, isMine: Boolean = false, isEditable: Boolean = false, - senderDisambiguatedDisplayName: String = "Sender", + senderDisplayName: String = "Sender", + displayNameAmbiguous: Boolean = false, content: TimelineItemEventContent = aTimelineItemTextContent(), groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, sendState: LocalEventSendState? = null, @@ -152,7 +154,10 @@ internal fun aTimelineItemEvent( sentTime = "12:34", isMine = isMine, isEditable = isEditable, - senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, + senderProfile = aProfileTimelineDetailsReady( + displayName = senderDisplayName, + displayNameAmbiguous = displayNameAmbiguous, + ), groupPosition = groupPosition, localSendState = sendState, inReplyTo = inReplyTo, 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 d5cdd36dab..b5c60bb2af 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 @@ -69,6 +69,8 @@ import androidx.constraintlayout.compose.ConstrainScope import androidx.constraintlayout.compose.ConstraintLayout import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.messages.impl.sender.SenderName +import io.element.android.features.messages.impl.sender.SenderNameMode import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineItemEvent @@ -106,6 +108,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.testtags.TestTags @@ -292,7 +295,8 @@ private fun TimelineItemEventRowContent( val avatarStrokeSize = 3.dp if (event.showSenderInformation && !timelineRoomInfo.isDm) { MessageSenderInformation( - event.safeSenderName, + event.senderId, + event.senderProfile, event.senderAvatar, avatarStrokeSize, Modifier @@ -372,7 +376,8 @@ private fun TimelineItemEventRowContent( @Composable private fun MessageSenderInformation( - sender: String, + senderId: UserId, + senderProfile: ProfileTimelineDetails, senderAvatar: AvatarData, avatarStrokeSize: Dp, modifier: Modifier = Modifier @@ -399,13 +404,10 @@ private fun MessageSenderInformation( Row { Avatar(senderAvatar) Spacer(modifier = Modifier.width(4.dp)) - Text( - modifier = Modifier.clipToBounds(), - text = sender, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = avatarColors.foreground, - style = ElementTheme.typography.fontBodyMdMedium, + SenderName( + senderId = senderId, + senderProfile = senderProfile, + senderNameMode = SenderNameMode.Timeline(avatarColors.foreground), ) } } @@ -562,10 +564,10 @@ private fun MessageEventBubbleContent( } } val inReplyTo = @Composable { inReplyTo: InReplyToDetails -> - val senderDisambiguatedDisplayName = inReplyTo.senderProfile.getDisambiguatedDisplayName(inReplyTo.senderId) val topPadding = if (showThreadDecoration) 0.dp else 8.dp ReplyToContent( - senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, + senderId = inReplyTo.senderId, + senderProfile = inReplyTo.senderProfile, metadata = inReplyTo.metadata(), modifier = Modifier .padding(top = topPadding, start = 8.dp, end = 8.dp) @@ -610,7 +612,8 @@ private fun MessageEventBubbleContent( @Composable private fun ReplyToContent( - senderDisambiguatedDisplayName: String, + senderId: UserId, + senderProfile: ProfileTimelineDetails, metadata: InReplyToMetadata?, modifier: Modifier = Modifier, ) { @@ -634,18 +637,15 @@ private fun ReplyToContent( ) Spacer(modifier = Modifier.width(8.dp)) } - val a11InReplyToText = stringResource(CommonStrings.common_in_reply_to, senderDisambiguatedDisplayName) + val a11InReplyToText = stringResource(CommonStrings.common_in_reply_to, senderProfile.getDisambiguatedDisplayName(senderId)) Column(verticalArrangement = Arrangement.SpaceBetween) { - Text( + SenderName( + senderId = senderId, + senderProfile = senderProfile, + senderNameMode = SenderNameMode.Reply, modifier = Modifier.semantics { contentDescription = a11InReplyToText }, - text = senderDisambiguatedDisplayName, - style = ElementTheme.typography.fontBodySmMedium, - textAlign = TextAlign.Start, - color = ElementTheme.materialColors.primary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, ) ReplyToContentText(metadata) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt index 019eb183b2..2527072c24 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt @@ -28,7 +28,10 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy internal fun TimelineItemEventRowDisambiguatedPreview( @PreviewParameter(InReplyToDetailsDisambiguatedProvider::class) inReplyToDetails: InReplyToDetails, ) = ElementPreview { - TimelineItemEventRowWithReplyContentToPreview(inReplyToDetails) + TimelineItemEventRowWithReplyContentToPreview( + inReplyToDetails = inReplyToDetails, + displayNameAmbiguous = true, + ) } class InReplyToDetailsDisambiguatedProvider : InReplyToDetailsProvider() { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt index c374d15709..061ad75690 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt @@ -27,7 +27,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { ATimelineItemEventRow( event = aTimelineItemEvent( - senderDisambiguatedDisplayName = "a long sender display name to test single line and ellipsis at the end of the line", + senderDisplayName = "a long sender display name to test single line and ellipsis at the end of the line", ), ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt index b9bb881bb6..8ec1d0e554 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt @@ -43,7 +43,6 @@ internal fun TimelineItemEventRowTimestampPreview( body = str, ), reactionsState = aTimelineItemReactions(count = 0), - senderDisambiguatedDisplayName = "A sender", ), ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt index 61d16f3e35..8c59c3c07a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt @@ -59,7 +59,10 @@ internal fun TimelineItemEventRowWithReplyPreview( } @Composable -internal fun TimelineItemEventRowWithReplyContentToPreview(inReplyToDetails: InReplyToDetails) { +internal fun TimelineItemEventRowWithReplyContentToPreview( + inReplyToDetails: InReplyToDetails, + displayNameAmbiguous: Boolean = false, +) { Column { sequenceOf(false, true).forEach { ATimelineItemEventRow( @@ -70,6 +73,7 @@ internal fun TimelineItemEventRowWithReplyContentToPreview(inReplyToDetails: InR body = "A reply." ), inReplyTo = inReplyToDetails, + displayNameAmbiguous = displayNameAmbiguous, groupPosition = TimelineItemGroupPosition.First, ), ) @@ -81,6 +85,7 @@ internal fun TimelineItemEventRowWithReplyContentToPreview(inReplyToDetails: InR aspectRatio = 2.5f ), inReplyTo = inReplyToDetails, + displayNameAmbiguous = displayNameAmbiguous, isThreaded = true, groupPosition = TimelineItemGroupPosition.Last, ), @@ -169,11 +174,19 @@ open class InReplyToDetailsProvider : PreviewParameterProvider eventId = EventId("\$event"), eventContent = eventContent, senderId = UserId("@Sender:domain"), - senderProfile = ProfileTimelineDetails.Ready( - displayName = "Sender", + senderProfile = aProfileTimelineDetailsReady( displayNameAmbiguous = displayNameAmbiguous, - avatarUrl = null, ), textContent = (eventContent as? MessageContent)?.body.orEmpty(), ) } + +internal fun aProfileTimelineDetailsReady( + displayName: String? = "Sender", + displayNameAmbiguous: Boolean = false, + avatarUrl: String? = null, +) = ProfileTimelineDetails.Ready( + displayName = displayName, + displayNameAmbiguous = displayNameAmbiguous, + avatarUrl = avatarUrl, +) 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 7945a88876..50550bd84f 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 @@ -33,7 +33,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.permalink.PermalinkParser 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 io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import kotlinx.collections.immutable.toImmutableList import java.text.DateFormat @@ -55,15 +55,14 @@ class TimelineItemEventFactory @Inject constructor( val currentSender = currentTimelineItem.event.sender val groupPosition = computeGroupPosition(currentTimelineItem, timelineItems, index) - val (senderDisambiguatedDisplayName, senderAvatarUrl) = currentTimelineItem.getSenderInfo() - + val senderProfile = currentTimelineItem.event.senderProfile val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT) val sentTime = timeFormatter.format(Date(currentTimelineItem.event.timestamp)) val senderAvatarData = AvatarData( id = currentSender.value, - name = senderDisambiguatedDisplayName ?: currentSender.value, - url = senderAvatarUrl, + name = senderProfile.getDisambiguatedDisplayName(currentSender), + url = senderProfile.getAvatarUrl(), size = AvatarSize.TimelineSender ) currentTimelineItem.event @@ -72,7 +71,7 @@ class TimelineItemEventFactory @Inject constructor( eventId = currentTimelineItem.eventId, transactionId = currentTimelineItem.transactionId, senderId = currentSender, - senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, + senderProfile = senderProfile, senderAvatar = senderAvatarData, content = contentFactory.create(currentTimelineItem.event), isMine = currentTimelineItem.event.isOwn, @@ -99,26 +98,6 @@ class TimelineItemEventFactory @Inject constructor( ) } - private fun MatrixTimelineItem.Event.getSenderInfo(): Pair { - val senderDisambiguatedDisplayName: String? - val senderAvatarUrl: String? - - when (val senderProfile = event.senderProfile) { - ProfileTimelineDetails.Unavailable, - ProfileTimelineDetails.Pending, - is ProfileTimelineDetails.Error -> { - senderDisambiguatedDisplayName = null - senderAvatarUrl = null - } - is ProfileTimelineDetails.Ready -> { - senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(event.sender) - senderAvatarUrl = senderProfile.avatarUrl - } - } - - return senderDisambiguatedDisplayName to senderAvatarUrl - } - private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions { val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT) var aggregatedReactions = event.reactions.map { reaction -> 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 aa21702e35..c42869af7a 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 @@ -27,7 +27,9 @@ 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.LocalEventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin +import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import kotlinx.collections.immutable.ImmutableList @Immutable @@ -57,7 +59,7 @@ sealed interface TimelineItem { val eventId: EventId? = null, val transactionId: TransactionId? = null, val senderId: UserId, - val senderDisambiguatedDisplayName: String?, + val senderProfile: ProfileTimelineDetails, val senderAvatar: AvatarData, val content: TimelineItemEventContent, val sentTime: String = "", @@ -74,7 +76,7 @@ sealed interface TimelineItem { ) : TimelineItem { val showSenderInformation = groupPosition.isNew() && !isMine - val safeSenderName: String = senderDisambiguatedDisplayName ?: senderId.value + val safeSenderName: String = senderProfile.getDisambiguatedDisplayName(senderId) val failedToSend: Boolean = localSendState is LocalEventSendState.SendingFailed diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt index efbc2564a9..b4b6dd2a79 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt @@ -52,3 +52,10 @@ fun ProfileTimelineDetails.getDisambiguatedDisplayName(userId: UserId): String { else -> userId.value } } + +fun ProfileTimelineDetails.getAvatarUrl(): String? { + return when (this) { + is ProfileTimelineDetails.Ready -> avatarUrl + else -> null + } +}