Timeline permalink : add focused event modifier
This commit is contained in:
parent
64d02d17bb
commit
1c06db1a78
5 changed files with 104 additions and 60 deletions
|
|
@ -146,7 +146,7 @@ fun TimelineView(
|
||||||
renderReadReceipts = state.renderReadReceipts,
|
renderReadReceipts = state.renderReadReceipts,
|
||||||
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true &&
|
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true &&
|
||||||
state.timelineItems.first().identifier() == timelineItem.identifier(),
|
state.timelineItems.first().identifier() == timelineItem.identifier(),
|
||||||
highlightedItem = state.focusedEventId?.value,
|
focusedEventId = state.focusedEventId,
|
||||||
onClick = onMessageClicked,
|
onClick = onMessageClicked,
|
||||||
onLongClick = onMessageLongClicked,
|
onLongClick = onMessageLongClicked,
|
||||||
onUserDataClick = onUserDataClicked,
|
onUserDataClick = onUserDataClicked,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ fun TimelineItemGroupedEventsRow(
|
||||||
timelineRoomInfo: TimelineRoomInfo,
|
timelineRoomInfo: TimelineRoomInfo,
|
||||||
renderReadReceipts: Boolean,
|
renderReadReceipts: Boolean,
|
||||||
isLastOutgoingMessage: Boolean,
|
isLastOutgoingMessage: Boolean,
|
||||||
highlightedItem: String?,
|
focusedEventId: EventId?,
|
||||||
onClick: (TimelineItem.Event) -> Unit,
|
onClick: (TimelineItem.Event) -> Unit,
|
||||||
onLongClick: (TimelineItem.Event) -> Unit,
|
onLongClick: (TimelineItem.Event) -> Unit,
|
||||||
inReplyToClick: (EventId) -> Unit,
|
inReplyToClick: (EventId) -> Unit,
|
||||||
|
|
@ -68,7 +68,7 @@ fun TimelineItemGroupedEventsRow(
|
||||||
onExpandGroupClick = ::onExpandGroupClick,
|
onExpandGroupClick = ::onExpandGroupClick,
|
||||||
timelineItem = timelineItem,
|
timelineItem = timelineItem,
|
||||||
timelineRoomInfo = timelineRoomInfo,
|
timelineRoomInfo = timelineRoomInfo,
|
||||||
highlightedItem = highlightedItem,
|
focusedEventId = focusedEventId,
|
||||||
renderReadReceipts = renderReadReceipts,
|
renderReadReceipts = renderReadReceipts,
|
||||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|
@ -92,7 +92,7 @@ private fun TimelineItemGroupedEventsRowContent(
|
||||||
onExpandGroupClick: () -> Unit,
|
onExpandGroupClick: () -> Unit,
|
||||||
timelineItem: TimelineItem.GroupedEvents,
|
timelineItem: TimelineItem.GroupedEvents,
|
||||||
timelineRoomInfo: TimelineRoomInfo,
|
timelineRoomInfo: TimelineRoomInfo,
|
||||||
highlightedItem: String?,
|
focusedEventId: EventId?,
|
||||||
renderReadReceipts: Boolean,
|
renderReadReceipts: Boolean,
|
||||||
isLastOutgoingMessage: Boolean,
|
isLastOutgoingMessage: Boolean,
|
||||||
onClick: (TimelineItem.Event) -> Unit,
|
onClick: (TimelineItem.Event) -> Unit,
|
||||||
|
|
@ -116,7 +116,7 @@ private fun TimelineItemGroupedEventsRowContent(
|
||||||
timelineItem.events.size
|
timelineItem.events.size
|
||||||
),
|
),
|
||||||
isExpanded = isExpanded,
|
isExpanded = isExpanded,
|
||||||
isHighlighted = !isExpanded && timelineItem.events.any { it.identifier() == highlightedItem },
|
isHighlighted = !isExpanded && timelineItem.events.any { it.isEvent(focusedEventId) },
|
||||||
onClick = onExpandGroupClick,
|
onClick = onExpandGroupClick,
|
||||||
)
|
)
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
|
|
@ -127,7 +127,7 @@ private fun TimelineItemGroupedEventsRowContent(
|
||||||
timelineRoomInfo = timelineRoomInfo,
|
timelineRoomInfo = timelineRoomInfo,
|
||||||
renderReadReceipts = renderReadReceipts,
|
renderReadReceipts = renderReadReceipts,
|
||||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||||
highlightedItem = highlightedItem,
|
focusedEventId = focusedEventId,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
inReplyToClick = inReplyToClick,
|
inReplyToClick = inReplyToClick,
|
||||||
|
|
@ -165,7 +165,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi
|
||||||
onExpandGroupClick = {},
|
onExpandGroupClick = {},
|
||||||
timelineItem = aGroupedEvents(withReadReceipts = true),
|
timelineItem = aGroupedEvents(withReadReceipts = true),
|
||||||
timelineRoomInfo = aTimelineRoomInfo(),
|
timelineRoomInfo = aTimelineRoomInfo(),
|
||||||
highlightedItem = null,
|
focusedEventId = null,
|
||||||
renderReadReceipts = true,
|
renderReadReceipts = true,
|
||||||
isLastOutgoingMessage = false,
|
isLastOutgoingMessage = false,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
|
|
@ -190,7 +190,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi
|
||||||
onExpandGroupClick = {},
|
onExpandGroupClick = {},
|
||||||
timelineItem = aGroupedEvents(withReadReceipts = true),
|
timelineItem = aGroupedEvents(withReadReceipts = true),
|
||||||
timelineRoomInfo = aTimelineRoomInfo(),
|
timelineRoomInfo = aTimelineRoomInfo(),
|
||||||
highlightedItem = null,
|
focusedEventId = null,
|
||||||
renderReadReceipts = true,
|
renderReadReceipts = true,
|
||||||
isLastOutgoingMessage = false,
|
isLastOutgoingMessage = false,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,23 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
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.TimelineEvents
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
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.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
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.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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
||||||
|
|
@ -32,7 +42,7 @@ internal fun TimelineItemRow(
|
||||||
timelineRoomInfo: TimelineRoomInfo,
|
timelineRoomInfo: TimelineRoomInfo,
|
||||||
renderReadReceipts: Boolean,
|
renderReadReceipts: Boolean,
|
||||||
isLastOutgoingMessage: Boolean,
|
isLastOutgoingMessage: Boolean,
|
||||||
highlightedItem: String?,
|
focusedEventId: EventId?,
|
||||||
onUserDataClick: (UserId) -> Unit,
|
onUserDataClick: (UserId) -> Unit,
|
||||||
onLinkClicked: (String) -> Unit,
|
onLinkClicked: (String) -> Unit,
|
||||||
onClick: (TimelineItem.Event) -> Unit,
|
onClick: (TimelineItem.Event) -> Unit,
|
||||||
|
|
@ -47,71 +57,92 @@ internal fun TimelineItemRow(
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
when (timelineItem) {
|
|
||||||
is TimelineItem.Virtual -> {
|
val backgroundModifier = if (timelineItem.isEvent(focusedEventId)) {
|
||||||
TimelineItemVirtualRow(
|
Modifier.focusedEvent()
|
||||||
virtual = timelineItem,
|
} else {
|
||||||
timelineRoomInfo = timelineRoomInfo,
|
Modifier
|
||||||
eventSink = eventSink,
|
}
|
||||||
modifier = modifier,
|
Box(modifier = modifier.then(backgroundModifier)) {
|
||||||
)
|
when (timelineItem) {
|
||||||
}
|
is TimelineItem.Virtual -> {
|
||||||
is TimelineItem.Event -> {
|
TimelineItemVirtualRow(
|
||||||
if (timelineItem.content is TimelineItemStateContent || timelineItem.content is TimelineItemLegacyCallInviteContent) {
|
virtual = timelineItem,
|
||||||
TimelineItemStateEventRow(
|
timelineRoomInfo = timelineRoomInfo,
|
||||||
event = timelineItem,
|
|
||||||
renderReadReceipts = renderReadReceipts,
|
|
||||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
|
||||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
|
||||||
onClick = { onClick(timelineItem) },
|
|
||||||
onReadReceiptsClick = onReadReceiptClick,
|
|
||||||
onLongClick = { onLongClick(timelineItem) },
|
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
modifier = modifier,
|
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
TimelineItemEventRow(
|
is TimelineItem.Event -> {
|
||||||
event = timelineItem,
|
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,
|
timelineRoomInfo = timelineRoomInfo,
|
||||||
renderReadReceipts = renderReadReceipts,
|
renderReadReceipts = renderReadReceipts,
|
||||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
focusedEventId = focusedEventId,
|
||||||
onClick = { onClick(timelineItem) },
|
onClick = onClick,
|
||||||
onLongClick = { onLongClick(timelineItem) },
|
onLongClick = onLongClick,
|
||||||
|
inReplyToClick = inReplyToClick,
|
||||||
onUserDataClick = onUserDataClick,
|
onUserDataClick = onUserDataClick,
|
||||||
onLinkClicked = onLinkClicked,
|
onLinkClicked = onLinkClicked,
|
||||||
inReplyToClick = inReplyToClick,
|
onTimestampClicked = onTimestampClicked,
|
||||||
onReactionClick = onReactionClick,
|
onReactionClick = onReactionClick,
|
||||||
onReactionLongClick = onReactionLongClick,
|
onReactionLongClick = onReactionLongClick,
|
||||||
onMoreReactionsClick = onMoreReactionsClick,
|
onMoreReactionsClick = onMoreReactionsClick,
|
||||||
onReadReceiptClick = onReadReceiptClick,
|
onReadReceiptClick = onReadReceiptClick,
|
||||||
onTimestampClicked = onTimestampClicked,
|
|
||||||
onSwipeToReply = { onSwipeToReply(timelineItem) },
|
|
||||||
eventSink = eventSink,
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,14 @@ sealed interface TimelineItem {
|
||||||
is GroupedEvents -> id
|
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) {
|
fun contentType(): String = when (this) {
|
||||||
is Event -> content.type
|
is Event -> content.type
|
||||||
is Virtual -> model.type
|
is Virtual -> model.type
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,11 @@ val SemanticColors.bigIconDefaultBackgroundColor
|
||||||
val SemanticColors.bigCheckmarkBorderColor
|
val SemanticColors.bigCheckmarkBorderColor
|
||||||
get() = if (isLight) LightColorTokens.colorGray400 else DarkColorTokens.colorGray400
|
get() = if (isLight) LightColorTokens.colorGray400 else DarkColorTokens.colorGray400
|
||||||
|
|
||||||
|
@OptIn(CoreColorToken::class)
|
||||||
|
val SemanticColors.highlightedMessageBackgroundColor
|
||||||
|
get() = if (isLight) LightColorTokens.colorGreen300 else DarkColorTokens.colorGreen300
|
||||||
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColorAliasesPreview() = ElementPreview {
|
internal fun ColorAliasesPreview() = ElementPreview {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue