Merge pull request #774 from vector-im/feature/bma/swipeToReply
Swipe to reply
This commit is contained in:
commit
e50edceced
9 changed files with 202 additions and 10 deletions
|
|
@ -156,6 +156,9 @@ fun MessagesView(
|
||||||
},
|
},
|
||||||
onReactionClicked = ::onEmojiReactionClicked,
|
onReactionClicked = ::onEmojiReactionClicked,
|
||||||
onSendLocationClicked = onSendLocationClicked,
|
onSendLocationClicked = onSendLocationClicked,
|
||||||
|
onSwipeToReply = { targetEvent ->
|
||||||
|
state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
|
|
@ -241,6 +244,7 @@ fun MessagesViewContent(
|
||||||
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
||||||
onSendLocationClicked: () -> Unit,
|
onSendLocationClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -258,6 +262,7 @@ fun MessagesViewContent(
|
||||||
onUserDataClicked = onUserDataClicked,
|
onUserDataClicked = onUserDataClicked,
|
||||||
onTimestampClicked = onTimestampClicked,
|
onTimestampClicked = onTimestampClicked,
|
||||||
onReactionClicked = onReactionClicked,
|
onReactionClicked = onReactionClicked,
|
||||||
|
onSwipeToReply = onSwipeToReply,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.userHasPermissionToSendMessage) {
|
if (state.userHasPermissionToSendMessage) {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
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.room.MatrixRoom
|
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.timeline.MatrixTimeline
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||||
|
import io.element.android.libraries.matrix.ui.room.canSendEventAsState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
@ -44,7 +46,7 @@ private const val backPaginationPageSize = 50
|
||||||
|
|
||||||
class TimelinePresenter @Inject constructor(
|
class TimelinePresenter @Inject constructor(
|
||||||
private val timelineItemsFactory: TimelineItemsFactory,
|
private val timelineItemsFactory: TimelineItemsFactory,
|
||||||
room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
) : Presenter<TimelineState> {
|
) : Presenter<TimelineState> {
|
||||||
|
|
||||||
private val timeline = room.timeline
|
private val timeline = room.timeline
|
||||||
|
|
@ -62,6 +64,9 @@ class TimelinePresenter @Inject constructor(
|
||||||
val timelineItems by timelineItemsFactory.collectItemsAsState()
|
val timelineItems by timelineItemsFactory.collectItemsAsState()
|
||||||
val paginationState by timeline.paginationState.collectAsState()
|
val paginationState by timeline.paginationState.collectAsState()
|
||||||
|
|
||||||
|
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||||
|
val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
|
||||||
|
|
||||||
fun handleEvents(event: TimelineEvents) {
|
fun handleEvents(event: TimelineEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
TimelineEvents.LoadMore -> localCoroutineScope.loadMore(paginationState)
|
TimelineEvents.LoadMore -> localCoroutineScope.loadMore(paginationState)
|
||||||
|
|
@ -92,6 +97,7 @@ class TimelinePresenter @Inject constructor(
|
||||||
|
|
||||||
return TimelineState(
|
return TimelineState(
|
||||||
highlightedEventId = highlightedEventId.value,
|
highlightedEventId = highlightedEventId.value,
|
||||||
|
canReply = userHasPermissionToSendMessage,
|
||||||
paginationState = paginationState,
|
paginationState = paginationState,
|
||||||
timelineItems = timelineItems,
|
timelineItems = timelineItems,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
data class TimelineState(
|
data class TimelineState(
|
||||||
val timelineItems: ImmutableList<TimelineItem>,
|
val timelineItems: ImmutableList<TimelineItem>,
|
||||||
val highlightedEventId: EventId?,
|
val highlightedEventId: EventId?,
|
||||||
|
val canReply: Boolean,
|
||||||
val paginationState: MatrixTimeline.PaginationState,
|
val paginationState: MatrixTimeline.PaginationState,
|
||||||
val eventSink: (TimelineEvents) -> Unit
|
val eventSink: (TimelineEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ fun aTimelineState(timelineItems: ImmutableList<TimelineItem> = persistentListOf
|
||||||
timelineItems = timelineItems,
|
timelineItems = timelineItems,
|
||||||
paginationState = MatrixTimeline.PaginationState(isBackPaginating = false, canBackPaginate = true),
|
paginationState = MatrixTimeline.PaginationState(isBackPaginating = false, canBackPaginate = true),
|
||||||
highlightedEventId = null,
|
highlightedEventId = null,
|
||||||
|
canReply = true,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ fun TimelineView(
|
||||||
onMessageClicked: (TimelineItem.Event) -> Unit,
|
onMessageClicked: (TimelineItem.Event) -> Unit,
|
||||||
onMessageLongClicked: (TimelineItem.Event) -> Unit,
|
onMessageLongClicked: (TimelineItem.Event) -> Unit,
|
||||||
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
||||||
|
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
||||||
onReactionClicked: (emoji: String, TimelineItem.Event) -> Unit,
|
onReactionClicked: (emoji: String, TimelineItem.Event) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -120,12 +121,14 @@ fun TimelineView(
|
||||||
TimelineItemRow(
|
TimelineItemRow(
|
||||||
timelineItem = timelineItem,
|
timelineItem = timelineItem,
|
||||||
highlightedItem = state.highlightedEventId?.value,
|
highlightedItem = state.highlightedEventId?.value,
|
||||||
|
canReply = state.canReply,
|
||||||
onClick = onMessageClicked,
|
onClick = onMessageClicked,
|
||||||
onLongClick = onMessageLongClicked,
|
onLongClick = onMessageLongClicked,
|
||||||
onUserDataClick = onUserDataClicked,
|
onUserDataClick = onUserDataClicked,
|
||||||
inReplyToClick = ::inReplyToClicked,
|
inReplyToClick = ::inReplyToClicked,
|
||||||
onReactionClick = onReactionClicked,
|
onReactionClick = onReactionClicked,
|
||||||
onTimestampClicked = onTimestampClicked,
|
onTimestampClicked = onTimestampClicked,
|
||||||
|
onSwipeToReply = onSwipeToReply,
|
||||||
)
|
)
|
||||||
if (index == state.timelineItems.lastIndex) {
|
if (index == state.timelineItems.lastIndex) {
|
||||||
onReachedLoadMore()
|
onReachedLoadMore()
|
||||||
|
|
@ -145,12 +148,14 @@ fun TimelineView(
|
||||||
fun TimelineItemRow(
|
fun TimelineItemRow(
|
||||||
timelineItem: TimelineItem,
|
timelineItem: TimelineItem,
|
||||||
highlightedItem: String?,
|
highlightedItem: String?,
|
||||||
|
canReply: Boolean,
|
||||||
onUserDataClick: (UserId) -> Unit,
|
onUserDataClick: (UserId) -> Unit,
|
||||||
onClick: (TimelineItem.Event) -> Unit,
|
onClick: (TimelineItem.Event) -> Unit,
|
||||||
onLongClick: (TimelineItem.Event) -> Unit,
|
onLongClick: (TimelineItem.Event) -> Unit,
|
||||||
inReplyToClick: (EventId) -> Unit,
|
inReplyToClick: (EventId) -> Unit,
|
||||||
onReactionClick: (key: String, TimelineItem.Event) -> Unit,
|
onReactionClick: (key: String, TimelineItem.Event) -> Unit,
|
||||||
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
||||||
|
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
when (timelineItem) {
|
when (timelineItem) {
|
||||||
|
|
@ -173,12 +178,14 @@ fun TimelineItemRow(
|
||||||
TimelineItemEventRow(
|
TimelineItemEventRow(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
isHighlighted = highlightedItem == timelineItem.identifier(),
|
||||||
|
canReply = canReply,
|
||||||
onClick = { onClick(timelineItem) },
|
onClick = { onClick(timelineItem) },
|
||||||
onLongClick = { onLongClick(timelineItem) },
|
onLongClick = { onLongClick(timelineItem) },
|
||||||
onUserDataClick = onUserDataClick,
|
onUserDataClick = onUserDataClick,
|
||||||
inReplyToClick = inReplyToClick,
|
inReplyToClick = inReplyToClick,
|
||||||
onReactionClick = onReactionClick,
|
onReactionClick = onReactionClick,
|
||||||
onTimestampClicked = onTimestampClicked,
|
onTimestampClicked = onTimestampClicked,
|
||||||
|
onSwipeToReply = { onSwipeToReply(timelineItem) },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -207,12 +214,14 @@ fun TimelineItemRow(
|
||||||
TimelineItemRow(
|
TimelineItemRow(
|
||||||
timelineItem = subGroupEvent,
|
timelineItem = subGroupEvent,
|
||||||
highlightedItem = highlightedItem,
|
highlightedItem = highlightedItem,
|
||||||
|
canReply = false,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
inReplyToClick = inReplyToClick,
|
inReplyToClick = inReplyToClick,
|
||||||
onUserDataClick = onUserDataClick,
|
onUserDataClick = onUserDataClick,
|
||||||
onTimestampClicked = onTimestampClicked,
|
onTimestampClicked = onTimestampClicked,
|
||||||
onReactionClick = onReactionClick,
|
onReactionClick = onReactionClick,
|
||||||
|
onSwipeToReply = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -314,5 +323,6 @@ private fun ContentToPreview(content: TimelineItemEventContent) {
|
||||||
onUserDataClicked = {},
|
onUserDataClicked = {},
|
||||||
onMessageLongClicked = {},
|
onMessageLongClicked = {},
|
||||||
onReactionClicked = { _, _ -> },
|
onReactionClicked = { _, _ -> },
|
||||||
|
onSwipeToReply = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.libraries.designsystem.VectorIcons
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A swipe indicator that appears when swiping to reply to a message.
|
||||||
|
*
|
||||||
|
* @param swipeProgress the progress of the swipe, between 0 and X. When swipeProgress >= 1 the swipe will be detected.
|
||||||
|
* @param modifier the modifier to apply to this Composable root.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun RowScope.ReplySwipeIndicator(
|
||||||
|
swipeProgress: () -> Float,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.graphicsLayer {
|
||||||
|
translationX = 36.dp.toPx() * swipeProgress().coerceAtMost(1f)
|
||||||
|
alpha = swipeProgress()
|
||||||
|
},
|
||||||
|
contentDescription = null,
|
||||||
|
resourceId = VectorIcons.Reply,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun ReplySwipeIndicatorLightPreview() =
|
||||||
|
ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun ReplySwipeIndicatorDarkPreview() =
|
||||||
|
ElementPreviewDark { ContentToPreview() }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview() {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
for (i in 0..8) {
|
||||||
|
Row { ReplySwipeIndicator(swipeProgress = { i / 8f }) }
|
||||||
|
}
|
||||||
|
Row { ReplySwipeIndicator(swipeProgress = { 1.5f }) }
|
||||||
|
Row { ReplySwipeIndicator(swipeProgress = { 2f }) }
|
||||||
|
Row { ReplySwipeIndicator(swipeProgress = { 3f }) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components
|
package io.element.android.features.messages.impl.timeline.components
|
||||||
|
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
|
|
@ -33,7 +35,13 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.DismissDirection
|
||||||
|
import androidx.compose.material3.DismissState
|
||||||
|
import androidx.compose.material3.DismissValue
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SwipeToDismiss
|
||||||
|
import androidx.compose.material3.rememberDismissState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -86,12 +94,14 @@ import org.jsoup.Jsoup
|
||||||
fun TimelineItemEventRow(
|
fun TimelineItemEventRow(
|
||||||
event: TimelineItem.Event,
|
event: TimelineItem.Event,
|
||||||
isHighlighted: Boolean,
|
isHighlighted: Boolean,
|
||||||
|
canReply: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onUserDataClick: (UserId) -> Unit,
|
onUserDataClick: (UserId) -> Unit,
|
||||||
inReplyToClick: (EventId) -> Unit,
|
inReplyToClick: (EventId) -> Unit,
|
||||||
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
||||||
onReactionClick: (emoji: String, eventId: TimelineItem.Event) -> Unit,
|
onReactionClick: (emoji: String, eventId: TimelineItem.Event) -> Unit,
|
||||||
|
onSwipeToReply: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
|
@ -108,6 +118,70 @@ fun TimelineItemEventRow(
|
||||||
inReplyToClick(inReplyToEventId)
|
inReplyToClick(inReplyToEventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canReply) {
|
||||||
|
val dismissState = rememberDismissState(
|
||||||
|
confirmValueChange = {
|
||||||
|
if (it == DismissValue.DismissedToEnd) {
|
||||||
|
onSwipeToReply()
|
||||||
|
}
|
||||||
|
// Do not dismiss the message, return false!
|
||||||
|
false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
SwipeToDismiss(
|
||||||
|
state = dismissState,
|
||||||
|
background = {
|
||||||
|
ReplySwipeIndicator({ dismissState.toSwipeProgress() })
|
||||||
|
},
|
||||||
|
directions = setOf(DismissDirection.StartToEnd),
|
||||||
|
dismissContent = {
|
||||||
|
TimelineItemEventRowContent(
|
||||||
|
event = event,
|
||||||
|
isHighlighted = isHighlighted,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onTimestampClicked = onTimestampClicked,
|
||||||
|
inReplyToClicked = ::inReplyToClicked,
|
||||||
|
onUserDataClicked = ::onUserDataClicked,
|
||||||
|
onReactionClicked = ::onReactionClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TimelineItemEventRowContent(
|
||||||
|
event = event,
|
||||||
|
isHighlighted = isHighlighted,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onTimestampClicked = onTimestampClicked,
|
||||||
|
inReplyToClicked = ::inReplyToClicked,
|
||||||
|
onUserDataClicked = ::onUserDataClicked,
|
||||||
|
onReactionClicked = ::onReactionClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// This is assuming that we are in a ColumnScope, but this is OK, for both Preview and real usage.
|
||||||
|
if (event.groupPosition.isNew()) {
|
||||||
|
Spacer(modifier = modifier.height(16.dp))
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = modifier.height(2.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TimelineItemEventRowContent(
|
||||||
|
event: TimelineItem.Event,
|
||||||
|
isHighlighted: Boolean,
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
onTimestampClicked: (TimelineItem.Event) -> Unit,
|
||||||
|
inReplyToClicked: () -> Unit,
|
||||||
|
onUserDataClicked: () -> Unit,
|
||||||
|
onReactionClicked: (emoji: String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
// To avoid using negative offset, we display in this Box a column with:
|
// To avoid using negative offset, we display in this Box a column with:
|
||||||
// - Spacer to give room to the Sender information if they must be displayed;
|
// - Spacer to give room to the Sender information if they must be displayed;
|
||||||
// - The message bubble;
|
// - The message bubble;
|
||||||
|
|
@ -140,7 +214,7 @@ fun TimelineItemEventRow(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onMessageClick = onClick,
|
onMessageClick = onClick,
|
||||||
onMessageLongClick = onLongClick,
|
onMessageLongClick = onLongClick,
|
||||||
inReplyToClick = ::inReplyToClicked,
|
inReplyToClick = inReplyToClicked,
|
||||||
onTimestampClicked = {
|
onTimestampClicked = {
|
||||||
onTimestampClicked(event)
|
onTimestampClicked(event)
|
||||||
}
|
}
|
||||||
|
|
@ -158,25 +232,27 @@ fun TimelineItemEventRow(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
.clickable(onClick = ::onUserDataClicked)
|
.clickable(onClick = onUserDataClicked)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Align to the bottom of the box
|
// Align to the bottom of the box
|
||||||
if (event.reactionsState.reactions.isNotEmpty()) {
|
if (event.reactionsState.reactions.isNotEmpty()) {
|
||||||
TimelineItemReactionsView(
|
TimelineItemReactionsView(
|
||||||
reactionsState = event.reactionsState,
|
reactionsState = event.reactionsState,
|
||||||
onReactionClicked = ::onReactionClicked,
|
onReactionClicked = onReactionClicked,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(if (event.isMine) Alignment.BottomEnd else Alignment.BottomStart)
|
.align(if (event.isMine) Alignment.BottomEnd else Alignment.BottomStart)
|
||||||
.padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp)
|
.padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This is assuming that we are in a ColumnScope, but this is OK, for both Preview and real usage.
|
}
|
||||||
if (event.groupPosition.isNew()) {
|
|
||||||
Spacer(modifier = modifier.height(16.dp))
|
private fun DismissState.toSwipeProgress(): Float {
|
||||||
} else {
|
return when (targetValue) {
|
||||||
Spacer(modifier = modifier.height(2.dp))
|
DismissValue.Default -> 0f
|
||||||
|
DismissValue.DismissedToEnd -> progress * 3
|
||||||
|
DismissValue.DismissedToStart -> progress * 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -452,12 +528,14 @@ private fun ContentToPreview() {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
isHighlighted = false,
|
isHighlighted = false,
|
||||||
|
canReply = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {},
|
onLongClick = {},
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
inReplyToClick = {},
|
inReplyToClick = {},
|
||||||
onReactionClick = { _, _ -> },
|
onReactionClick = { _, _ -> },
|
||||||
onTimestampClicked = {},
|
onTimestampClicked = {},
|
||||||
|
onSwipeToReply = {},
|
||||||
)
|
)
|
||||||
TimelineItemEventRow(
|
TimelineItemEventRow(
|
||||||
event = aTimelineItemEvent(
|
event = aTimelineItemEvent(
|
||||||
|
|
@ -467,12 +545,14 @@ private fun ContentToPreview() {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
isHighlighted = false,
|
isHighlighted = false,
|
||||||
|
canReply = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {},
|
onLongClick = {},
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
inReplyToClick = {},
|
inReplyToClick = {},
|
||||||
onReactionClick = { _, _ -> },
|
onReactionClick = { _, _ -> },
|
||||||
onTimestampClicked = {},
|
onTimestampClicked = {},
|
||||||
|
onSwipeToReply = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -492,7 +572,7 @@ internal fun TimelineItemEventRowWithReplyDarkPreview() =
|
||||||
private fun ContentToPreviewWithReply() {
|
private fun ContentToPreviewWithReply() {
|
||||||
Column {
|
Column {
|
||||||
sequenceOf(false, true).forEach {
|
sequenceOf(false, true).forEach {
|
||||||
val replyContent = if(it) {
|
val replyContent = if (it) {
|
||||||
// Short
|
// Short
|
||||||
"Message which are being replied."
|
"Message which are being replied."
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -509,12 +589,14 @@ private fun ContentToPreviewWithReply() {
|
||||||
inReplyTo = aInReplyToReady(replyContent)
|
inReplyTo = aInReplyToReady(replyContent)
|
||||||
),
|
),
|
||||||
isHighlighted = false,
|
isHighlighted = false,
|
||||||
|
canReply = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {},
|
onLongClick = {},
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
inReplyToClick = {},
|
inReplyToClick = {},
|
||||||
onReactionClick = { _, _ -> },
|
onReactionClick = { _, _ -> },
|
||||||
onTimestampClicked = {},
|
onTimestampClicked = {},
|
||||||
|
onSwipeToReply = {},
|
||||||
)
|
)
|
||||||
TimelineItemEventRow(
|
TimelineItemEventRow(
|
||||||
event = aTimelineItemEvent(
|
event = aTimelineItemEvent(
|
||||||
|
|
@ -525,12 +607,14 @@ private fun ContentToPreviewWithReply() {
|
||||||
inReplyTo = aInReplyToReady(replyContent)
|
inReplyTo = aInReplyToReady(replyContent)
|
||||||
),
|
),
|
||||||
isHighlighted = false,
|
isHighlighted = false,
|
||||||
|
canReply = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {},
|
onLongClick = {},
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
inReplyToClick = {},
|
inReplyToClick = {},
|
||||||
onReactionClick = { _, _ -> },
|
onReactionClick = { _, _ -> },
|
||||||
onTimestampClicked = {},
|
onTimestampClicked = {},
|
||||||
|
onSwipeToReply = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -578,12 +662,14 @@ private fun ContentTimestampToPreview(event: TimelineItem.Event) {
|
||||||
senderDisplayName = if (useDocument) "Document case" else "Text case",
|
senderDisplayName = if (useDocument) "Document case" else "Text case",
|
||||||
),
|
),
|
||||||
isHighlighted = false,
|
isHighlighted = false,
|
||||||
|
canReply = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {},
|
onLongClick = {},
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
inReplyToClick = {},
|
inReplyToClick = {},
|
||||||
onReactionClick = { _, _ -> },
|
onReactionClick = { _, _ -> },
|
||||||
onTimestampClicked = {},
|
onTimestampClicked = {},
|
||||||
|
onSwipeToReply = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6418097195ef3c6a166d216913469aa3adf30a4fb42fbda21bc27e321d431410
|
||||||
|
size 9792
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7f64649306919ac8dfeb6a7729f73a0617e8fc809c2d62f3aeabac6566bced1e
|
||||||
|
size 9151
|
||||||
Loading…
Add table
Add a link
Reference in a new issue