Merge pull request #1866 from vector-im/feature/bma/morePreview
More preview on TimelineItemEventRow
This commit is contained in:
commit
a6e7714812
256 changed files with 589 additions and 256 deletions
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime.Composable
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
|
||||||
|
// For previews
|
||||||
|
@Composable
|
||||||
|
internal fun ATimelineItemEventRow(
|
||||||
|
event: TimelineItem.Event,
|
||||||
|
showReadReceipts: Boolean = false,
|
||||||
|
isLastOutgoingMessage: Boolean = false,
|
||||||
|
isHighlighted: Boolean = false,
|
||||||
|
) = TimelineItemEventRow(
|
||||||
|
event = event,
|
||||||
|
showReadReceipts = showReadReceipts,
|
||||||
|
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||||
|
isHighlighted = isHighlighted,
|
||||||
|
canReply = true,
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {},
|
||||||
|
onUserDataClick = {},
|
||||||
|
inReplyToClick = {},
|
||||||
|
onReactionClick = { _, _ -> },
|
||||||
|
onReactionLongClick = { _, _ -> },
|
||||||
|
onMoreReactionsClick = {},
|
||||||
|
onReadReceiptClick = {},
|
||||||
|
onSwipeToReply = {},
|
||||||
|
onTimestampClicked = {},
|
||||||
|
eventSink = {},
|
||||||
|
)
|
||||||
|
|
@ -53,8 +53,6 @@ import androidx.compose.ui.platform.ViewConfiguration
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -63,7 +61,6 @@ import androidx.constraintlayout.compose.ConstrainScope
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
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.aTimelineItemEvent
|
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.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.toExtraPadding
|
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.ReadReceiptViewState
|
||||||
|
|
@ -75,17 +72,14 @@ import io.element.android.features.messages.impl.timeline.model.bubble.BubbleSta
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
||||||
import io.element.android.libraries.designsystem.components.EqualWidthColumn
|
import io.element.android.libraries.designsystem.components.EqualWidthColumn
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.swipe.SwipeableActionsState
|
import io.element.android.libraries.designsystem.swipe.SwipeableActionsState
|
||||||
import io.element.android.libraries.designsystem.swipe.rememberSwipeableActionsState
|
import io.element.android.libraries.designsystem.swipe.rememberSwipeableActionsState
|
||||||
|
|
@ -101,7 +95,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageT
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
|
||||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail
|
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail
|
||||||
|
|
@ -110,7 +103,6 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
@ -696,7 +688,7 @@ private fun textForInReplyTo(inReplyTo: InReplyToDetails): String {
|
||||||
internal fun TimelineItemEventRowPreview() = ElementPreview {
|
internal fun TimelineItemEventRowPreview() = ElementPreview {
|
||||||
Column {
|
Column {
|
||||||
sequenceOf(false, true).forEach {
|
sequenceOf(false, true).forEach {
|
||||||
TimelineItemEventRow(
|
ATimelineItemEventRow(
|
||||||
event = aTimelineItemEvent(
|
event = aTimelineItemEvent(
|
||||||
isMine = it,
|
isMine = it,
|
||||||
content = aTimelineItemTextContent().copy(
|
content = aTimelineItemTextContent().copy(
|
||||||
|
|
@ -705,23 +697,8 @@ internal fun TimelineItemEventRowPreview() = ElementPreview {
|
||||||
),
|
),
|
||||||
groupPosition = TimelineItemGroupPosition.First,
|
groupPosition = TimelineItemGroupPosition.First,
|
||||||
),
|
),
|
||||||
showReadReceipts = false,
|
|
||||||
isLastOutgoingMessage = false,
|
|
||||||
isHighlighted = false,
|
|
||||||
canReply = true,
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
)
|
||||||
TimelineItemEventRow(
|
ATimelineItemEventRow(
|
||||||
event = aTimelineItemEvent(
|
event = aTimelineItemEvent(
|
||||||
isMine = it,
|
isMine = it,
|
||||||
content = aTimelineItemImageContent().copy(
|
content = aTimelineItemImageContent().copy(
|
||||||
|
|
@ -729,230 +706,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview {
|
||||||
),
|
),
|
||||||
groupPosition = TimelineItemGroupPosition.Last,
|
groupPosition = TimelineItemGroupPosition.Last,
|
||||||
),
|
),
|
||||||
showReadReceipts = false,
|
|
||||||
isLastOutgoingMessage = false,
|
|
||||||
isHighlighted = false,
|
|
||||||
canReply = true,
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview {
|
|
||||||
Column {
|
|
||||||
sequenceOf(false, true).forEach {
|
|
||||||
val replyContent = if (it) {
|
|
||||||
// Short
|
|
||||||
"Message which are being replied."
|
|
||||||
} else {
|
|
||||||
// Long, to test 2 lines and ellipsis)
|
|
||||||
"Message which are being replied, and which was long enough to be displayed on two lines (only!)."
|
|
||||||
}
|
|
||||||
TimelineItemEventRow(
|
|
||||||
event = aTimelineItemEvent(
|
|
||||||
isMine = it,
|
|
||||||
content = aTimelineItemTextContent().copy(
|
|
||||||
body = "A long text which will be displayed on several lines and" +
|
|
||||||
" hopefully can be manually adjusted to test different behaviors."
|
|
||||||
),
|
|
||||||
inReplyTo = aInReplyToDetails(replyContent),
|
|
||||||
groupPosition = TimelineItemGroupPosition.First,
|
|
||||||
),
|
|
||||||
showReadReceipts = false,
|
|
||||||
isLastOutgoingMessage = false,
|
|
||||||
isHighlighted = false,
|
|
||||||
canReply = true,
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
|
||||||
TimelineItemEventRow(
|
|
||||||
event = aTimelineItemEvent(
|
|
||||||
isMine = it,
|
|
||||||
content = aTimelineItemImageContent().copy(
|
|
||||||
aspectRatio = 5f
|
|
||||||
),
|
|
||||||
inReplyTo = aInReplyToDetails(replyContent),
|
|
||||||
isThreaded = true,
|
|
||||||
groupPosition = TimelineItemGroupPosition.Last,
|
|
||||||
),
|
|
||||||
showReadReceipts = false,
|
|
||||||
isLastOutgoingMessage = false,
|
|
||||||
isHighlighted = false,
|
|
||||||
canReply = true,
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun aInReplyToDetails(
|
|
||||||
replyContent: String,
|
|
||||||
): InReplyToDetails {
|
|
||||||
return InReplyToDetails(
|
|
||||||
eventId = EventId("\$event"),
|
|
||||||
eventContent = MessageContent(replyContent, null, false, false, TextMessageType(replyContent, null)),
|
|
||||||
senderId = UserId("@Sender:domain"),
|
|
||||||
senderDisplayName = "Sender",
|
|
||||||
senderAvatarUrl = null,
|
|
||||||
textContent = replyContent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun TimelineItemEventRowTimestampPreview(
|
|
||||||
@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event
|
|
||||||
) = ElementPreview {
|
|
||||||
Column {
|
|
||||||
val oldContent = event.content as TimelineItemTextContent
|
|
||||||
listOf(
|
|
||||||
"Text",
|
|
||||||
"Text longer, displayed on 1 line",
|
|
||||||
"Text which should be rendered on several lines",
|
|
||||||
).forEach { str ->
|
|
||||||
listOf(false, true).forEach { useDocument ->
|
|
||||||
TimelineItemEventRow(
|
|
||||||
event = event.copy(
|
|
||||||
content = oldContent.copy(
|
|
||||||
body = str,
|
|
||||||
htmlDocument = if (useDocument) Jsoup.parse(str) else null,
|
|
||||||
),
|
|
||||||
reactionsState = aTimelineItemReactions(count = 0),
|
|
||||||
senderDisplayName = if (useDocument) "Document case" else "Text case",
|
|
||||||
),
|
|
||||||
showReadReceipts = false,
|
|
||||||
isLastOutgoingMessage = false,
|
|
||||||
isHighlighted = false,
|
|
||||||
canReply = true,
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview {
|
|
||||||
Column {
|
|
||||||
listOf(false, true).forEach { isMine ->
|
|
||||||
TimelineItemEventRow(
|
|
||||||
event = aTimelineItemEvent(
|
|
||||||
isMine = isMine,
|
|
||||||
content = aTimelineItemTextContent().copy(
|
|
||||||
body = "A couple of multi-line messages with many reactions attached." +
|
|
||||||
" One sent by me and another from someone else."
|
|
||||||
),
|
|
||||||
timelineItemReactions = aTimelineItemReactions(count = 20),
|
|
||||||
),
|
|
||||||
showReadReceipts = false,
|
|
||||||
isLastOutgoingMessage = false,
|
|
||||||
isHighlighted = false,
|
|
||||||
canReply = true,
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: no need for light/dark variant for this preview
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight {
|
|
||||||
TimelineItemEventRow(
|
|
||||||
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 = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: no need for light/dark variant for this preview, we only look at the timestamp position
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight {
|
|
||||||
TimelineItemEventRow(
|
|
||||||
event = aTimelineItemEvent(content = aTimelineItemPollContent()),
|
|
||||||
showReadReceipts = false,
|
|
||||||
isLastOutgoingMessage = false,
|
|
||||||
isHighlighted = false,
|
|
||||||
canReply = true,
|
|
||||||
onClick = {},
|
|
||||||
onLongClick = {},
|
|
||||||
onUserDataClick = {},
|
|
||||||
inReplyToClick = {},
|
|
||||||
onReactionClick = { _, _ -> },
|
|
||||||
onReactionLongClick = { _, _ -> },
|
|
||||||
onMoreReactionsClick = {},
|
|
||||||
onReadReceiptClick = {},
|
|
||||||
onSwipeToReply = {},
|
|
||||||
onTimestampClicked = {},
|
|
||||||
eventSink = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
|
||||||
|
// Note: no need for light/dark variant for this preview
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight {
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(
|
||||||
|
senderDisplayName = "a long sender display name to test single line and ellipsis at the end of the line",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun TimelineItemEventRowTimestampPreview(
|
||||||
|
@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event
|
||||||
|
) = ElementPreview {
|
||||||
|
Column {
|
||||||
|
val oldContent = event.content as TimelineItemTextContent
|
||||||
|
listOf(
|
||||||
|
"Text",
|
||||||
|
"Text longer, displayed on 1 line",
|
||||||
|
"Text which should be rendered on several lines",
|
||||||
|
).forEach { str ->
|
||||||
|
listOf(false, true).forEach { useDocument ->
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = event.copy(
|
||||||
|
content = oldContent.copy(
|
||||||
|
body = str,
|
||||||
|
htmlDocument = if (useDocument) Jsoup.parse(str) else null,
|
||||||
|
),
|
||||||
|
reactionsState = aTimelineItemReactions(count = 0),
|
||||||
|
senderDisplayName = if (useDocument) "Document case" else "Text case",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime.Composable
|
||||||
|
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.model.event.aTimelineItemTextContent
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview {
|
||||||
|
Column {
|
||||||
|
listOf(false, true).forEach { isMine ->
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(
|
||||||
|
isMine = isMine,
|
||||||
|
content = aTimelineItemTextContent().copy(
|
||||||
|
body = "A couple of multi-line messages with many reactions attached." +
|
||||||
|
" One sent by me and another from someone else."
|
||||||
|
),
|
||||||
|
timelineItemReactions = aTimelineItemReactions(count = 20),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
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.receipt.ReadReceiptViewState
|
||||||
|
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStateForTimelineItemEventRowProvider
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
||||||
|
// Note: I add to reduce the size of the fun name, or it does not compile.
|
||||||
|
// Previous name: TimelineItemEventRowWithSendingStateAndReadReceiptPreview
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun TimelineItemEventRowWithRRPreview(
|
||||||
|
@PreviewParameter(ReadReceiptViewStateForTimelineItemEventRowProvider::class) state: ReadReceiptViewState,
|
||||||
|
) = ElementPreview {
|
||||||
|
Column {
|
||||||
|
// A message from someone else
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(
|
||||||
|
isMine = false,
|
||||||
|
sendState = null,
|
||||||
|
content = aTimelineItemTextContent().copy(
|
||||||
|
body = "A message from someone else"
|
||||||
|
),
|
||||||
|
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||||
|
readReceiptState = TimelineItemReadReceipts(state.receipts),
|
||||||
|
),
|
||||||
|
showReadReceipts = true,
|
||||||
|
isLastOutgoingMessage = false,
|
||||||
|
)
|
||||||
|
// A message from current user
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(
|
||||||
|
isMine = true,
|
||||||
|
sendState = state.sendState,
|
||||||
|
content = aTimelineItemTextContent().copy(
|
||||||
|
body = "A message from me"
|
||||||
|
),
|
||||||
|
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||||
|
readReceiptState = TimelineItemReadReceipts(state.receipts),
|
||||||
|
),
|
||||||
|
showReadReceipts = true,
|
||||||
|
isLastOutgoingMessage = false,
|
||||||
|
)
|
||||||
|
// Another message from current user
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(
|
||||||
|
isMine = true,
|
||||||
|
sendState = state.sendState,
|
||||||
|
content = aTimelineItemTextContent().copy(
|
||||||
|
body = "A last message from me"
|
||||||
|
),
|
||||||
|
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||||
|
readReceiptState = TimelineItemReadReceipts(state.receipts),
|
||||||
|
),
|
||||||
|
showReadReceipts = true,
|
||||||
|
isLastOutgoingMessage = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
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.model.InReplyToDetails
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
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.media.MediaSource
|
||||||
|
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun TimelineItemEventRowWithReplyPreview(
|
||||||
|
@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails,
|
||||||
|
) = ElementPreview {
|
||||||
|
Column {
|
||||||
|
sequenceOf(false, true).forEach {
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(
|
||||||
|
isMine = it,
|
||||||
|
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||||
|
content = aTimelineItemTextContent().copy(
|
||||||
|
body = "A reply."
|
||||||
|
),
|
||||||
|
inReplyTo = inReplyToDetails,
|
||||||
|
groupPosition = TimelineItemGroupPosition.First,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(
|
||||||
|
isMine = it,
|
||||||
|
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||||
|
content = aTimelineItemImageContent().copy(
|
||||||
|
aspectRatio = 5f
|
||||||
|
),
|
||||||
|
inReplyTo = inReplyToDetails,
|
||||||
|
isThreaded = true,
|
||||||
|
groupPosition = TimelineItemGroupPosition.Last,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InReplyToDetailsProvider : PreviewParameterProvider<InReplyToDetails> {
|
||||||
|
override val values: Sequence<InReplyToDetails>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aMessageContent(
|
||||||
|
body = "Message which are being replied.",
|
||||||
|
type = TextMessageType("Message which are being replied.", null)
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Message which are being replied, and which was long enough to be displayed on two lines (only!).",
|
||||||
|
type = TextMessageType("Message which are being replied, and which was long enough to be displayed on two lines (only!).", null)
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Video",
|
||||||
|
type = VideoMessageType("Video", MediaSource("url"), null),
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Audio",
|
||||||
|
type = AudioMessageType("Audio", MediaSource("url"), null),
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Voice",
|
||||||
|
type = VoiceMessageType("Voice", MediaSource("url"), null, null),
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Image",
|
||||||
|
type = ImageMessageType("Image", MediaSource("url"), null),
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "File",
|
||||||
|
type = FileMessageType("File", MediaSource("url"), null),
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Location",
|
||||||
|
type = LocationMessageType("Location", "geo:1,2", null),
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Notice",
|
||||||
|
type = NoticeMessageType("Notice", null),
|
||||||
|
),
|
||||||
|
aMessageContent(
|
||||||
|
body = "Emote",
|
||||||
|
type = EmoteMessageType("Emote", null),
|
||||||
|
),
|
||||||
|
PollContent(
|
||||||
|
question = "Poll which are being replied.",
|
||||||
|
kind = PollKind.Disclosed,
|
||||||
|
maxSelections = 1u,
|
||||||
|
answers = emptyList(),
|
||||||
|
votes = emptyMap(),
|
||||||
|
endTime = null
|
||||||
|
),
|
||||||
|
).map {
|
||||||
|
aInReplyToDetails(
|
||||||
|
eventContent = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aMessageContent(
|
||||||
|
body: String,
|
||||||
|
type: MessageType,
|
||||||
|
) = MessageContent(
|
||||||
|
body = body,
|
||||||
|
inReplyTo = null,
|
||||||
|
isEdited = false,
|
||||||
|
isThreaded = false,
|
||||||
|
type = type,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun aInReplyToDetails(
|
||||||
|
eventContent: EventContent,
|
||||||
|
) = InReplyToDetails(
|
||||||
|
eventId = EventId("\$event"),
|
||||||
|
eventContent = eventContent,
|
||||||
|
senderId = UserId("@Sender:domain"),
|
||||||
|
senderDisplayName = "Sender",
|
||||||
|
senderAvatarUrl = null,
|
||||||
|
textContent = (eventContent as? MessageContent)?.body.orEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
|
||||||
|
// Note: no need for light/dark variant for this preview, we only look at the timestamp position
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight {
|
||||||
|
ATimelineItemEventRow(
|
||||||
|
event = aTimelineItemEvent(content = aTimelineItemPollContent()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.api.core.EventId
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||||
|
|
||||||
|
class ReadReceiptViewStateForTimelineItemEventRowProvider :
|
||||||
|
PreviewParameterProvider<ReadReceiptViewState> {
|
||||||
|
override val values: Sequence<ReadReceiptViewState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aReadReceiptViewState(
|
||||||
|
sendState = LocalEventSendState.NotSentYet
|
||||||
|
),
|
||||||
|
aReadReceiptViewState(
|
||||||
|
sendState = LocalEventSendState.Sent(EventId("\$eventId")),
|
||||||
|
),
|
||||||
|
aReadReceiptViewState(
|
||||||
|
sendState = LocalEventSendState.Sent(EventId("\$eventId")),
|
||||||
|
receipts = mutableListOf<ReadReceiptData>().apply {
|
||||||
|
repeat(5) {
|
||||||
|
add(
|
||||||
|
aReadReceiptData(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ class ReadReceiptViewStateProvider : PreviewParameterProvider<ReadReceiptViewSta
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aReadReceiptViewState(
|
internal fun aReadReceiptViewState(
|
||||||
sendState: LocalEventSendState? = null,
|
sendState: LocalEventSendState? = null,
|
||||||
isLastOutgoingMessage: Boolean = true,
|
isLastOutgoingMessage: Boolean = true,
|
||||||
receipts: List<ReadReceiptData> = emptyList(),
|
receipts: List<ReadReceiptData> = emptyList(),
|
||||||
|
|
@ -64,7 +64,7 @@ private fun aReadReceiptViewState(
|
||||||
receipts = receipts.toImmutableList(),
|
receipts = receipts.toImmutableList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun aReadReceiptData(
|
internal fun aReadReceiptData(
|
||||||
index: Int,
|
index: Int,
|
||||||
avatarData: AvatarData = anAvatarData(
|
avatarData: AvatarData = anAvatarData(
|
||||||
id = "$index",
|
id = "$index",
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue