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 63a1cb1505..477314e20e 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 @@ -146,7 +146,7 @@ fun TimelineView( renderReadReceipts = state.renderReadReceipts, isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true && state.timelineItems.first().identifier() == timelineItem.identifier(), - highlightedItem = state.focusedEventId?.value, + focusedEventId = state.focusedEventId, onClick = onMessageClicked, onLongClick = onMessageLongClicked, onUserDataClick = onUserDataClicked, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index a5d42d5dbf..35571278bb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -43,7 +43,7 @@ fun TimelineItemGroupedEventsRow( timelineRoomInfo: TimelineRoomInfo, renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, - highlightedItem: String?, + focusedEventId: EventId?, onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, @@ -68,7 +68,7 @@ fun TimelineItemGroupedEventsRow( onExpandGroupClick = ::onExpandGroupClick, timelineItem = timelineItem, timelineRoomInfo = timelineRoomInfo, - highlightedItem = highlightedItem, + focusedEventId = focusedEventId, renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, onClick = onClick, @@ -92,7 +92,7 @@ private fun TimelineItemGroupedEventsRowContent( onExpandGroupClick: () -> Unit, timelineItem: TimelineItem.GroupedEvents, timelineRoomInfo: TimelineRoomInfo, - highlightedItem: String?, + focusedEventId: EventId?, renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, onClick: (TimelineItem.Event) -> Unit, @@ -116,7 +116,7 @@ private fun TimelineItemGroupedEventsRowContent( timelineItem.events.size ), isExpanded = isExpanded, - isHighlighted = !isExpanded && timelineItem.events.any { it.identifier() == highlightedItem }, + isHighlighted = !isExpanded && timelineItem.events.any { it.isEvent(focusedEventId) }, onClick = onExpandGroupClick, ) if (isExpanded) { @@ -127,7 +127,7 @@ private fun TimelineItemGroupedEventsRowContent( timelineRoomInfo = timelineRoomInfo, renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, - highlightedItem = highlightedItem, + focusedEventId = focusedEventId, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, @@ -165,7 +165,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi onExpandGroupClick = {}, timelineItem = aGroupedEvents(withReadReceipts = true), timelineRoomInfo = aTimelineRoomInfo(), - highlightedItem = null, + focusedEventId = null, renderReadReceipts = true, isLastOutgoingMessage = false, onClick = {}, @@ -190,7 +190,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi onExpandGroupClick = {}, timelineItem = aGroupedEvents(withReadReceipts = true), timelineRoomInfo = aTimelineRoomInfo(), - highlightedItem = null, + focusedEventId = null, renderReadReceipts = true, isLastOutgoingMessage = false, onClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 7c3557b774..4782ff21f1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -16,13 +16,23 @@ package io.element.android.features.messages.impl.timeline.components +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme 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.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent +import io.element.android.libraries.designsystem.text.toPx +import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId @@ -32,7 +42,7 @@ internal fun TimelineItemRow( timelineRoomInfo: TimelineRoomInfo, renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, - highlightedItem: String?, + focusedEventId: EventId?, onUserDataClick: (UserId) -> Unit, onLinkClicked: (String) -> Unit, onClick: (TimelineItem.Event) -> Unit, @@ -47,71 +57,92 @@ internal fun TimelineItemRow( eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, modifier: Modifier = Modifier ) { - when (timelineItem) { - is TimelineItem.Virtual -> { - TimelineItemVirtualRow( - virtual = timelineItem, - timelineRoomInfo = timelineRoomInfo, - eventSink = eventSink, - modifier = modifier, - ) - } - is TimelineItem.Event -> { - if (timelineItem.content is TimelineItemStateContent || timelineItem.content is TimelineItemLegacyCallInviteContent) { - TimelineItemStateEventRow( - event = timelineItem, - renderReadReceipts = renderReadReceipts, - isLastOutgoingMessage = isLastOutgoingMessage, - isHighlighted = highlightedItem == timelineItem.identifier(), - onClick = { onClick(timelineItem) }, - onReadReceiptsClick = onReadReceiptClick, - onLongClick = { onLongClick(timelineItem) }, + + val backgroundModifier = if (timelineItem.isEvent(focusedEventId)) { + Modifier.focusedEvent() + } else { + Modifier + } + Box(modifier = modifier.then(backgroundModifier)) { + when (timelineItem) { + is TimelineItem.Virtual -> { + TimelineItemVirtualRow( + virtual = timelineItem, + timelineRoomInfo = timelineRoomInfo, eventSink = eventSink, - modifier = modifier, ) - } else { - TimelineItemEventRow( - event = timelineItem, + } + is TimelineItem.Event -> { + if (timelineItem.content is TimelineItemStateContent || timelineItem.content is TimelineItemLegacyCallInviteContent) { + TimelineItemStateEventRow( + event = timelineItem, + renderReadReceipts = renderReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, + isHighlighted = timelineItem.isEvent(focusedEventId), + onClick = { onClick(timelineItem) }, + onReadReceiptsClick = onReadReceiptClick, + onLongClick = { onLongClick(timelineItem) }, + eventSink = eventSink, + ) + } else { + TimelineItemEventRow( + event = timelineItem, + timelineRoomInfo = timelineRoomInfo, + renderReadReceipts = renderReadReceipts, + isLastOutgoingMessage = isLastOutgoingMessage, + isHighlighted = timelineItem.isEvent(focusedEventId), + onClick = { onClick(timelineItem) }, + onLongClick = { onLongClick(timelineItem) }, + onUserDataClick = onUserDataClick, + onLinkClicked = onLinkClicked, + inReplyToClick = inReplyToClick, + onReactionClick = onReactionClick, + onReactionLongClick = onReactionLongClick, + onMoreReactionsClick = onMoreReactionsClick, + onReadReceiptClick = onReadReceiptClick, + onTimestampClicked = onTimestampClicked, + onSwipeToReply = { onSwipeToReply(timelineItem) }, + eventSink = eventSink, + ) + } + } + is TimelineItem.GroupedEvents -> { + TimelineItemGroupedEventsRow( + timelineItem = timelineItem, timelineRoomInfo = timelineRoomInfo, renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, - isHighlighted = highlightedItem == timelineItem.identifier(), - onClick = { onClick(timelineItem) }, - onLongClick = { onLongClick(timelineItem) }, + focusedEventId = focusedEventId, + onClick = onClick, + onLongClick = onLongClick, + inReplyToClick = inReplyToClick, onUserDataClick = onUserDataClick, onLinkClicked = onLinkClicked, - inReplyToClick = inReplyToClick, + onTimestampClicked = onTimestampClicked, onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, onReadReceiptClick = onReadReceiptClick, - onTimestampClicked = onTimestampClicked, - onSwipeToReply = { onSwipeToReply(timelineItem) }, eventSink = eventSink, - modifier = modifier, ) } } - is TimelineItem.GroupedEvents -> { - TimelineItemGroupedEventsRow( - timelineItem = timelineItem, - timelineRoomInfo = timelineRoomInfo, - renderReadReceipts = renderReadReceipts, - isLastOutgoingMessage = isLastOutgoingMessage, - highlightedItem = highlightedItem, - onClick = onClick, - onLongClick = onLongClick, - inReplyToClick = inReplyToClick, - onUserDataClick = onUserDataClick, - onLinkClicked = onLinkClicked, - onTimestampClicked = onTimestampClicked, - onReactionClick = onReactionClick, - onReactionLongClick = onReactionLongClick, - onMoreReactionsClick = onMoreReactionsClick, - onReadReceiptClick = onReadReceiptClick, - eventSink = eventSink, - modifier = modifier, - ) - } } } + +@Composable +private fun Modifier.focusedEvent(): Modifier { + val highlightedLineColor = ElementTheme.colors.textActionAccent + val gradientColors = listOf( + ElementTheme.colors.highlightedMessageBackgroundColor, + ElementTheme.materialColors.background + ) + val verticalOffset = 2.dp.toPx() + return drawWithCache { + val brush = Brush.verticalGradient(gradientColors) + onDrawBehind { + drawRect(brush, topLeft = Offset(0f, verticalOffset), size = Size(size.width, size.height * 0.7f)) + drawLine(highlightedLineColor, start = Offset(0f, verticalOffset), end = Offset(size.width, verticalOffset)) + } + }.padding(top = 4.dp) +} 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 fa00c26760..3f46639c6a 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 @@ -38,6 +38,14 @@ sealed interface TimelineItem { is GroupedEvents -> id } + fun isEvent(eventId: EventId?): Boolean { + if (eventId == null) return false + return when (this) { + is Event -> this.eventId == eventId + else -> false + } + } + fun contentType(): String = when (this) { is Event -> content.type is Virtual -> model.type diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt index 5039d10467..1fedcceedd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt @@ -149,6 +149,11 @@ val SemanticColors.bigIconDefaultBackgroundColor val SemanticColors.bigCheckmarkBorderColor get() = if (isLight) LightColorTokens.colorGray400 else DarkColorTokens.colorGray400 +@OptIn(CoreColorToken::class) +val SemanticColors.highlightedMessageBackgroundColor + get() = if (isLight) LightColorTokens.colorGreen300 else DarkColorTokens.colorGreen300 + + @PreviewsDayNight @Composable internal fun ColorAliasesPreview() = ElementPreview {