Rework timestamp positioning (#2075)

* Rework timestamp positioning

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-12-20 14:37:28 +01:00 committed by GitHub
parent 7d33409810
commit 9eb249c9e7
65 changed files with 169 additions and 155 deletions

View file

@ -16,26 +16,30 @@
package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.material3.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times
import io.element.android.compound.theme.ElementTheme
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.designsystem.text.toDp
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
import kotlin.math.roundToInt
import kotlin.math.ceil
// Allow to not overlap the timestamp with the text, in the message bubble.
// Compute the size of the worst case.
data class ExtraPadding(val nbChars: Int)
data class ExtraPadding(val extraWidth: Dp)
val noExtraPadding = ExtraPadding(0)
val noExtraPadding = ExtraPadding(0.dp)
/**
* See [io.element.android.features.messages.impl.timeline.components.TimelineEventTimestampView] for the related View.
@ -47,37 +51,48 @@ fun TimelineItem.Event.toExtraPadding(): ExtraPadding {
val hasMessageSendingFailed = localSendState is LocalEventSendState.SendingFailed
val isMessageEdited = (content as? TimelineItemTextBasedContent)?.isEdited.orFalse()
var strLen = 6
val textMeasurer = rememberTextMeasurer(cacheSize = 128)
val density = LocalDensity.current
var strLen = 2.dp // Extra space char
if (isMessageEdited) {
strLen += stringResource(id = CommonStrings.common_edited_suffix).length + 3
val editedText = stringResource(id = CommonStrings.common_edited_suffix)
val extraLen = remember(editedText, density) { textMeasurer.getExtraPadding(editedText, density) } + 10.dp // Text + spacing
strLen += extraLen
}
strLen += formattedTime.length
strLen += remember(formattedTime, density) { textMeasurer.getExtraPadding(formattedTime, density) }
if (hasMessageSendingFailed) {
strLen += 5
strLen += 19.dp // Image + spacing
// I do not know why, but adding extra widths avoid overlapping when the
// message is edited and in error.
if (isMessageEdited) {
// I do not know why, but adding 2 more chars avoid overlapping when the
// message is edited and in error.
strLen += 2
strLen += 2.dp
}
}
return ExtraPadding(strLen)
}
private fun TextMeasurer.getExtraPadding(text: String, density: Density): Dp {
val timestampTextStyle = ElementTheme.typography.fontBodyXsRegular
val textWidth = measure(text = text, style = timestampTextStyle).size.width
return (textWidth/density.density).dp
}
/**
* Get a string to add to the content of the message to avoid overlapping the timestamp.
* @param fontSize the font size of the message content, to be able to add more space char if the font is small.
*/
fun ExtraPadding.getStr(fontSize: TextUnit): String {
if (nbChars == 0) return ""
val timestampFontSize = ElementTheme.typography.fontBodyXsRegular.fontSize // 11.sp
val nbOfSpaces = (timestampFontSize.value / fontSize.value * nbChars).roundToInt() + 1
// A space and some unbreakable spaces
return " " + "\u00A0".repeat(nbOfSpaces)
@Composable
fun ExtraPadding.getStr(textStyle: TextStyle = LocalTextStyle.current): String {
if (extraWidth == 0.dp) return ""
val density = LocalDensity.current
val textMeasurer = rememberTextMeasurer(128)
val charWidth = remember(textStyle) { textMeasurer.measure(text = "\u00A0", style = textStyle).size.width }
val widthPx = remember(density, extraWidth) { with(density) { extraWidth.toPx() } }
// A space and some unbreakable spaces, always rounding the result to the next value if not a integer
return " " + "\u00A0".repeat(ceil(widthPx / charWidth).toInt())
}
@Composable
fun ExtraPadding.getDpSize(): Dp {
if (nbChars == 0) return 0.dp
val timestampFontSize = ElementTheme.typography.fontBodyXsRegular.fontSize // 11.sp
return nbChars * timestampFontSize.toDp() / 3
return extraWidth
}

View file

@ -33,14 +33,13 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentProvider
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.compound.theme.ElementTheme
@Composable
fun TimelineItemAudioView(
@ -76,7 +75,7 @@ fun TimelineItemAudioView(
overflow = TextOverflow.Ellipsis
)
Text(
text = content.fileExtensionAndSize + extraPadding.getStr(12.sp),
text = content.fileExtensionAndSize + extraPadding.getStr(ElementTheme.typography.fontBodySmRegular),
color = ElementTheme.materialColors.secondary,
style = ElementTheme.typography.fontBodySmRegular,
maxLines = 1,

View file

@ -32,15 +32,14 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContentProvider
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.compound.theme.ElementTheme
@Composable
fun TimelineItemFileView(
@ -77,7 +76,7 @@ fun TimelineItemFileView(
overflow = TextOverflow.Ellipsis
)
Text(
text = content.fileExtensionAndSize + extraPadding.getStr(12.sp),
text = content.fileExtensionAndSize + extraPadding.getStr(textStyle = ElementTheme.typography.fontBodySmRegular),
color = ElementTheme.materialColors.secondary,
style = ElementTheme.typography.fontBodySmRegular,
maxLines = 1,

View file

@ -27,7 +27,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.icons.CompoundDrawables
import io.element.android.libraries.designsystem.preview.ElementPreview
@ -58,7 +57,7 @@ fun TimelineItemInformativeView(
fontStyle = FontStyle.Italic,
color = MaterialTheme.colorScheme.secondary,
style = ElementTheme.typography.fontBodyMdRegular,
text = text + extraPadding.getStr(14.sp)
text = text + extraPadding.getStr(textStyle = ElementTheme.typography.fontBodyMdRegular)
)
}
}

View file

@ -25,6 +25,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.core.text.buildSpannedString
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@ -45,16 +46,16 @@ fun TimelineItemTextView(
LocalContentColor provides ElementTheme.colors.textPrimary,
LocalTextStyle provides ElementTheme.typography.fontBodyLgRegular
) {
val fontSize = LocalTextStyle.current.fontSize
val formattedBody = content.formattedBody
val body = SpannableString(formattedBody ?: content.body)
val extraPaddingText = extraPadding.getStr()
Box(modifier) {
val textWithPadding = remember(body, fontSize) {
val textWithPadding = remember(body) {
buildSpannedString {
append(body)
append(extraPadding.getStr(fontSize))
append(extraPaddingText)
}
}
EditorStyledText(
@ -73,7 +74,7 @@ internal fun TimelineItemTextViewPreview(
) = ElementPreview {
TimelineItemTextView(
content = content,
extraPadding = ExtraPadding(nbChars = 8),
extraPadding = ExtraPadding(extraWidth = 32.dp),
onLinkClicked = {},
)
}