Iterate on extra padding solution.
This commit is contained in:
committed by
Benoit Marty
parent
659d719456
commit
884aea1c80
@@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventSendStat
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlin.random.Random
|
||||
|
||||
fun aTimelineState(timelineItems: ImmutableList<TimelineItem> = persistentListOf()) = TimelineState(
|
||||
@@ -102,6 +103,7 @@ internal fun aTimelineItemEvent(
|
||||
sendState: EventSendState = EventSendState.Sent(eventId),
|
||||
inReplyTo: InReplyTo? = null,
|
||||
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
|
||||
timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(isMine = isMine),
|
||||
): TimelineItem.Event {
|
||||
return TimelineItem.Event(
|
||||
id = eventId.value,
|
||||
@@ -110,11 +112,7 @@ internal fun aTimelineItemEvent(
|
||||
senderId = UserId("@senderId:domain"),
|
||||
senderAvatar = AvatarData("@senderId:domain", "sender", size = AvatarSize.TimelineSender),
|
||||
content = content,
|
||||
reactionsState = TimelineItemReactions(
|
||||
persistentListOf(
|
||||
AggregatedReaction("👍", "1", isOnMyMessage = isMine)
|
||||
)
|
||||
),
|
||||
reactionsState = timelineItemReactions,
|
||||
sentTime = "12:34",
|
||||
isMine = isMine,
|
||||
senderDisplayName = "Sender",
|
||||
@@ -125,6 +123,19 @@ internal fun aTimelineItemEvent(
|
||||
)
|
||||
}
|
||||
|
||||
fun aTimelineItemReactions(
|
||||
count: Int = 1,
|
||||
isMine: Boolean = true,
|
||||
): TimelineItemReactions {
|
||||
return TimelineItemReactions(
|
||||
reactions = buildList {
|
||||
repeat(count) {
|
||||
add(AggregatedReaction(key = "👍", count = (it + 1).toString(), isOnMyMessage = isMine))
|
||||
}
|
||||
}.toPersistentList()
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aTimelineItemDebugInfo(
|
||||
model: String = "Rust(Model())",
|
||||
originalJson: String? = null,
|
||||
|
||||
@@ -46,13 +46,17 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.sp
|
||||
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.toExtraPadding
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
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.aTimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
@@ -73,6 +77,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageT
|
||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail
|
||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
|
||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
@Composable
|
||||
fun TimelineItemEventRow(
|
||||
@@ -228,6 +233,7 @@ private fun MessageEventBubbleContent(
|
||||
interactionSource = interactionSource,
|
||||
onClick = onMessageClick,
|
||||
onLongClick = onMessageLongClick,
|
||||
extraPadding = event.toExtraPadding(),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
@@ -437,3 +443,44 @@ private fun ContentToPreview() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowTimestampLightPreview(@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event) =
|
||||
ElementPreviewLight { ContentTimestampToPreview(event) }
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowTimestampDarkPreview(@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event) =
|
||||
ElementPreviewDark { ContentTimestampToPreview(event) }
|
||||
|
||||
@Composable
|
||||
private fun ContentTimestampToPreview(event: TimelineItem.Event) {
|
||||
Column {
|
||||
val oldContent = event.content as TimelineItemTextContent
|
||||
listOf(
|
||||
"Text",
|
||||
"Text longer but 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",
|
||||
),
|
||||
isHighlighted = false,
|
||||
onClick = {},
|
||||
onLongClick = {},
|
||||
onUserDataClick = {},
|
||||
inReplyToClick = {},
|
||||
onTimestampClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||
import io.element.android.features.messages.impl.timeline.components.event.noExtraPadding
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||
@@ -66,6 +67,7 @@ fun TimelineItemStateEventRow(
|
||||
interactionSource = interactionSource,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
extraPadding = noExtraPadding,
|
||||
modifier = Modifier.defaultTimelineContentPadding()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
package io.element.android.features.messages.impl.timeline.components.event
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||
@@ -35,6 +33,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||
fun TimelineItemEventContentView(
|
||||
content: TimelineItemEventContent,
|
||||
interactionSource: MutableInteractionSource,
|
||||
extraPadding: ExtraPadding,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
@@ -42,14 +41,17 @@ fun TimelineItemEventContentView(
|
||||
when (content) {
|
||||
is TimelineItemEncryptedContent -> TimelineItemEncryptedView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemRedactedContent -> TimelineItemRedactedView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemTextBasedContent -> TimelineItemTextView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
interactionSource = interactionSource,
|
||||
modifier = modifier,
|
||||
onTextClicked = onClick,
|
||||
@@ -57,6 +59,7 @@ fun TimelineItemEventContentView(
|
||||
)
|
||||
is TimelineItemUnknownContent -> TimelineItemUnknownView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemImageContent -> TimelineItemImageView(
|
||||
|
||||
@@ -31,12 +31,14 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun TimelineItemEncryptedView(
|
||||
content: TimelineItemEncryptedContent,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = CommonStrings.common_decryption_error),
|
||||
iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
|
||||
icon = Icons.Default.Warning,
|
||||
extraPadding = extraPadding,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -56,6 +58,7 @@ private fun ContentToPreview() {
|
||||
TimelineItemEncryptedView(
|
||||
content = TimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.Unknown
|
||||
)
|
||||
),
|
||||
extraPadding = noExtraPadding
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ fun TimelineItemInformativeView(
|
||||
text: String,
|
||||
iconDescription: String,
|
||||
icon: ImageVector,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
@@ -58,7 +59,7 @@ fun TimelineItemInformativeView(
|
||||
fontStyle = FontStyle.Italic,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
fontSize = 14.sp,
|
||||
text = text + extraPaddingTrick
|
||||
text = text + extraPadding.str
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,6 +77,7 @@ private fun ContentToPreview() {
|
||||
TimelineItemInformativeView(
|
||||
text = "Info",
|
||||
iconDescription = "",
|
||||
icon = Icons.Default.Delete
|
||||
icon = Icons.Default.Delete,
|
||||
extraPadding = noExtraPadding,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,12 +30,14 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun TimelineItemRedactedView(
|
||||
content: TimelineItemRedactedContent,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = CommonStrings.common_message_removed) + extraPaddingTrick,
|
||||
text = stringResource(id = CommonStrings.common_message_removed),
|
||||
iconDescription = stringResource(id = CommonStrings.common_message_removed),
|
||||
icon = Icons.Default.Delete,
|
||||
extraPadding = extraPadding,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -52,5 +54,8 @@ internal fun TimelineItemRedactedViewDarkPreview() =
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
TimelineItemRedactedView(TimelineItemRedactedContent)
|
||||
TimelineItemRedactedView(
|
||||
TimelineItemRedactedContent,
|
||||
extraPadding = noExtraPadding
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ import android.text.util.Linkify.PHONE_NUMBERS
|
||||
import android.text.util.Linkify.WEB_URLS
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -29,6 +32,7 @@ import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.text.util.LinkifyCompat
|
||||
import io.element.android.features.messages.impl.timeline.components.html.HtmlDocument
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||
@@ -43,12 +47,25 @@ import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
fun TimelineItemTextView(
|
||||
content: TimelineItemTextBasedContent,
|
||||
interactionSource: MutableInteractionSource,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
val htmlDocument = content.htmlDocument
|
||||
if (htmlDocument != null) {
|
||||
// For now we ignore the extra padding for html content, so add some spacing
|
||||
// below the content (as previous behavior)
|
||||
Column(modifier = modifier) {
|
||||
HtmlDocument(
|
||||
document = htmlDocument,
|
||||
modifier = Modifier,
|
||||
onTextClicked = onTextClicked,
|
||||
onTextLongClicked = onTextLongClicked,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
HtmlDocument(
|
||||
document = htmlDocument,
|
||||
modifier = modifier,
|
||||
@@ -61,7 +78,7 @@ fun TimelineItemTextView(
|
||||
val linkStyle = SpanStyle(
|
||||
color = LinkColor,
|
||||
)
|
||||
val styledText = remember(content.body) { content.body.linkify(linkStyle) + extraPaddingTrick.toAnnotatedString() }
|
||||
val styledText = remember(content.body) { content.body.linkify(linkStyle) + extraPadding.str.toAnnotatedString() }
|
||||
ClickableLinkText(
|
||||
text = styledText,
|
||||
linkAnnotationTag = "URL",
|
||||
@@ -110,6 +127,10 @@ internal fun TimelineItemTextViewDarkPreview(@PreviewParameter(TimelineItemTextB
|
||||
|
||||
@Composable
|
||||
fun ContentToPreview(content: TimelineItemTextBasedContent) {
|
||||
TimelineItemTextView(content, MutableInteractionSource())
|
||||
TimelineItemTextView(
|
||||
content = content,
|
||||
interactionSource = MutableInteractionSource(),
|
||||
extraPadding = ExtraPadding("xxxxxxx"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,14 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun TimelineItemUnknownView(
|
||||
content: TimelineItemUnknownContent,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = CommonStrings.common_unsupported_event) + extraPaddingTrick,
|
||||
text = stringResource(id = CommonStrings.common_unsupported_event),
|
||||
iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
|
||||
icon = Icons.Default.Info,
|
||||
extraPadding = extraPadding,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -52,5 +54,8 @@ internal fun TimelineItemUnknownViewDarkPreview() =
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
TimelineItemUnknownView(TimelineItemUnknownContent)
|
||||
TimelineItemUnknownView(
|
||||
content = TimelineItemUnknownContent,
|
||||
extraPadding = noExtraPadding
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,38 @@
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.components.event
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
|
||||
import io.element.android.libraries.ui.strings.R
|
||||
|
||||
// Allow to not overlap the timestamp with the text, in the message bubble.
|
||||
// Compute the size of the worst case.
|
||||
val extraPaddingTrick: String = " ".repeat(" (edited) 88:88 X ".length)
|
||||
data class ExtraPadding(val str: String)
|
||||
|
||||
val noExtraPadding = ExtraPadding("")
|
||||
|
||||
/**
|
||||
* See [io.element.android.features.messages.impl.timeline.components.TimelineEventTimestampView] for the related View.
|
||||
* And https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=1819%253A99506 for the design.
|
||||
*/
|
||||
@Composable
|
||||
fun TimelineItem.Event.toExtraPadding(): ExtraPadding {
|
||||
val formattedTime = sentTime
|
||||
val hasMessageSendingFailed = sendState is EventSendState.SendingFailed
|
||||
val isMessageEdited = (content as? TimelineItemTextBasedContent)?.isEdited.orFalse()
|
||||
|
||||
var strLen = 2
|
||||
if (isMessageEdited) {
|
||||
strLen += stringResource(id = R.string.common_edited_suffix).length + 2
|
||||
}
|
||||
strLen += formattedTime.length
|
||||
if (hasMessageSendingFailed) {
|
||||
strLen += 3
|
||||
}
|
||||
// A space and a few unbreakable spaces
|
||||
return ExtraPadding(" " + "\u00A0".repeat(strLen))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user