Implement ContentAvoidingLayout for timeline items (#2113)
* Implement `ContentAvoidingLayout` for timeline items * Truncate long mention pills --------- Co-authored-by: Benoit Marty <benoit@matrix.org> Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
0381027dae
commit
4f6c7421bd
110 changed files with 573 additions and 299 deletions
|
|
@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -69,7 +70,8 @@ fun TimelineEventTimestampView(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.then(clickModifier)
|
||||
.padding(start = 16.dp) // Add extra padding for touch target size
|
||||
// Add extra padding for touch target size
|
||||
.padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing))
|
||||
.then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
@ -107,3 +109,7 @@ internal fun TimelineEventTimestampViewPreview(@PreviewParameter(TimelineItemEve
|
|||
onLongClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
object TimelineEventTimestampViewDefaults {
|
||||
val spacing = 16.dp
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
|
|
@ -67,7 +68,8 @@ import io.element.android.features.messages.impl.timeline.TimelineEvents
|
|||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
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.layout.ContentAvoidingLayout
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
||||
import io.element.android.features.messages.impl.timeline.model.InReplyToDetails
|
||||
|
|
@ -80,6 +82,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||
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.features.messages.impl.timeline.model.event.canBeRepliedTo
|
||||
|
|
@ -448,12 +451,13 @@ private fun MessageEventBubbleContent(
|
|||
fun WithTimestampLayout(
|
||||
timestampPosition: TimestampPosition,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
canShrinkContent: Boolean = false,
|
||||
content: @Composable (onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit) -> Unit,
|
||||
) {
|
||||
when (timestampPosition) {
|
||||
TimestampPosition.Overlay ->
|
||||
Box(modifier, contentAlignment = Alignment.Center) {
|
||||
content()
|
||||
content {}
|
||||
TimelineEventTimestampView(
|
||||
event = event,
|
||||
onClick = onTimestampClicked,
|
||||
|
|
@ -466,20 +470,26 @@ private fun MessageEventBubbleContent(
|
|||
)
|
||||
}
|
||||
TimestampPosition.Aligned ->
|
||||
Box(modifier) {
|
||||
content()
|
||||
TimelineEventTimestampView(
|
||||
event = event,
|
||||
onClick = onTimestampClicked,
|
||||
onLongClick = ::onTimestampLongClick,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
ContentAvoidingLayout(
|
||||
modifier = modifier,
|
||||
// The spacing is negative to make the content overlap the empty space at the start of the timestamp
|
||||
spacing = (-4).dp,
|
||||
overlayOffset = DpOffset(0.dp, -1.dp),
|
||||
shrinkContent = canShrinkContent,
|
||||
content = { content(this::onContentLayoutChanged) },
|
||||
overlay = {
|
||||
TimelineEventTimestampView(
|
||||
event = event,
|
||||
onClick = onTimestampClicked,
|
||||
onLongClick = ::onTimestampLongClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
TimestampPosition.Below ->
|
||||
Column(modifier) {
|
||||
content()
|
||||
content {}
|
||||
TimelineEventTimestampView(
|
||||
event = event,
|
||||
onClick = onTimestampClicked,
|
||||
|
|
@ -498,7 +508,8 @@ private fun MessageEventBubbleContent(
|
|||
timestampPosition: TimestampPosition,
|
||||
showThreadDecoration: Boolean,
|
||||
inReplyToDetails: InReplyToDetails?,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
canShrinkContent: Boolean = false,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val timestampLayoutModifier: Modifier
|
||||
|
|
@ -515,7 +526,8 @@ private fun MessageEventBubbleContent(
|
|||
}
|
||||
timestampPosition != TimestampPosition.Overlay -> {
|
||||
timestampLayoutModifier = Modifier
|
||||
contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp)
|
||||
contentModifier = Modifier
|
||||
.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp)
|
||||
}
|
||||
else -> {
|
||||
timestampLayoutModifier = Modifier
|
||||
|
|
@ -530,8 +542,9 @@ private fun MessageEventBubbleContent(
|
|||
val contentWithTimestamp = @Composable {
|
||||
WithTimestampLayout(
|
||||
timestampPosition = timestampPosition,
|
||||
canShrinkContent = canShrinkContent,
|
||||
modifier = timestampLayoutModifier,
|
||||
) {
|
||||
) { onContentLayoutChanged ->
|
||||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
onLinkClicked = { url ->
|
||||
|
|
@ -548,9 +561,9 @@ private fun MessageEventBubbleContent(
|
|||
}
|
||||
}
|
||||
},
|
||||
extraPadding = event.toExtraPadding(),
|
||||
eventSink = eventSink,
|
||||
modifier = contentModifier,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = contentModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -594,6 +607,7 @@ private fun MessageEventBubbleContent(
|
|||
showThreadDecoration = event.isThreaded,
|
||||
timestampPosition = timestampPosition,
|
||||
inReplyToDetails = event.inReplyTo,
|
||||
canShrinkContent = event.content is TimelineItemVoiceContent,
|
||||
modifier = bubbleModifier
|
||||
)
|
||||
}
|
||||
|
|
@ -655,7 +669,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview {
|
|||
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."
|
||||
" hopefully can be manually adjusted to test different behaviors."
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import androidx.compose.ui.zIndex
|
|||
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.components.event.TimelineItemEventContentView
|
||||
import io.element.android.features.messages.impl.timeline.components.event.noExtraPadding
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
|
||||
|
|
@ -81,7 +80,6 @@ fun TimelineItemStateEventRow(
|
|||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
onLinkClicked = {},
|
||||
extraPadding = noExtraPadding,
|
||||
eventSink = eventSink,
|
||||
modifier = Modifier.defaultTimelineContentPadding()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
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.ElementPreview
|
||||
|
|
@ -44,15 +46,17 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
@Composable
|
||||
fun TimelineItemAudioView(
|
||||
content: TimelineItemAudioContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val iconSize = 32.dp
|
||||
val spacing = 8.dp
|
||||
Row(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.size(iconSize)
|
||||
.clip(CircleShape)
|
||||
.background(ElementTheme.materialColors.background),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
@ -65,7 +69,7 @@ fun TimelineItemAudioView(
|
|||
.size(16.dp),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Spacer(Modifier.width(spacing))
|
||||
Column {
|
||||
Text(
|
||||
text = content.body,
|
||||
|
|
@ -75,11 +79,15 @@ fun TimelineItemAudioView(
|
|||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = content.fileExtensionAndSize + extraPadding.getStr(ElementTheme.typography.fontBodySmRegular),
|
||||
text = content.fileExtensionAndSize,
|
||||
color = ElementTheme.materialColors.secondary,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
onTextLayout = ContentAvoidingLayout.measureLastTextLine(
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
extraWidth = iconSize + spacing
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +99,6 @@ internal fun TimelineItemAudioViewPreview(@PreviewParameter(TimelineItemAudioCon
|
|||
ElementPreview {
|
||||
TimelineItemAudioView(
|
||||
content,
|
||||
extraPadding = noExtraPadding,
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -29,14 +30,14 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun TimelineItemEncryptedView(
|
||||
@Suppress("UNUSED_PARAMETER") content: TimelineItemEncryptedContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = CommonStrings.common_waiting_for_decryption_key),
|
||||
iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
|
||||
iconResourceId = CommonDrawables.ic_waiting_to_decrypt,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -48,6 +49,6 @@ internal fun TimelineItemEncryptedViewPreview() = ElementPreview {
|
|||
content = TimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.Unknown
|
||||
),
|
||||
extraPadding = noExtraPadding
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||
import io.element.android.features.messages.impl.timeline.di.rememberPresenter
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
|
||||
|
|
@ -41,32 +42,32 @@ import io.element.android.libraries.architecture.Presenter
|
|||
@Composable
|
||||
fun TimelineItemEventContentView(
|
||||
content: TimelineItemEventContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onLinkClicked: (url: String) -> Unit,
|
||||
eventSink: (TimelineEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit = {},
|
||||
) {
|
||||
val presenterFactories = LocalTimelineItemPresenterFactories.current
|
||||
when (content) {
|
||||
is TimelineItemEncryptedContent -> TimelineItemEncryptedView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemRedactedContent -> TimelineItemRedactedView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemTextBasedContent -> TimelineItemTextView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
modifier = modifier,
|
||||
onLinkClicked = onLinkClicked,
|
||||
onContentLayoutChanged = onContentLayoutChanged
|
||||
)
|
||||
is TimelineItemUnknownContent -> TimelineItemUnknownView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemLocationContent -> TimelineItemLocationView(
|
||||
|
|
@ -87,12 +88,12 @@ fun TimelineItemEventContentView(
|
|||
)
|
||||
is TimelineItemFileContent -> TimelineItemFileView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemAudioContent -> TimelineItemAudioView(
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
is TimelineItemStateContent -> TimelineItemStateView(
|
||||
|
|
@ -109,7 +110,7 @@ fun TimelineItemEventContentView(
|
|||
TimelineItemVoiceView(
|
||||
state = presenter.present(),
|
||||
content = content,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
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.ElementPreview
|
||||
|
|
@ -44,15 +46,17 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables
|
|||
@Composable
|
||||
fun TimelineItemFileView(
|
||||
content: TimelineItemFileContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val iconSize = 32.dp
|
||||
val spacing = 8.dp
|
||||
Row(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.size(iconSize)
|
||||
.clip(CircleShape)
|
||||
.background(ElementTheme.materialColors.background),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
@ -66,7 +70,7 @@ fun TimelineItemFileView(
|
|||
.rotate(-45f),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Spacer(Modifier.width(spacing))
|
||||
Column {
|
||||
Text(
|
||||
text = content.body,
|
||||
|
|
@ -76,11 +80,15 @@ fun TimelineItemFileView(
|
|||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = content.fileExtensionAndSize + extraPadding.getStr(textStyle = ElementTheme.typography.fontBodySmRegular),
|
||||
text = content.fileExtensionAndSize,
|
||||
color = ElementTheme.materialColors.secondary,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
onTextLayout = ContentAvoidingLayout.measureLastTextLine(
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
extraWidth = iconSize + spacing
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +99,6 @@ fun TimelineItemFileView(
|
|||
internal fun TimelineItemFileViewPreview(@PreviewParameter(TimelineItemFileContentProvider::class) content: TimelineItemFileContent) = ElementPreview {
|
||||
TimelineItemFileView(
|
||||
content,
|
||||
extraPadding = noExtraPadding,
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -39,12 +41,19 @@ fun TimelineItemInformativeView(
|
|||
text: String,
|
||||
iconDescription: String,
|
||||
@DrawableRes iconResourceId: Int,
|
||||
extraPadding: ExtraPadding,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
modifier = modifier.onSizeChanged { size ->
|
||||
onContentLayoutChanged(
|
||||
ContentAvoidingLayoutData(
|
||||
contentWidth = size.width,
|
||||
contentHeight = size.height,
|
||||
)
|
||||
)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
resourceId = iconResourceId,
|
||||
|
|
@ -57,7 +66,7 @@ fun TimelineItemInformativeView(
|
|||
fontStyle = FontStyle.Italic,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = text + extraPadding.getStr(textStyle = ElementTheme.typography.fontBodyMdRegular)
|
||||
text = text
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +78,6 @@ internal fun TimelineItemInformativeViewPreview() = ElementPreview {
|
|||
text = "Info",
|
||||
iconDescription = "",
|
||||
iconResourceId = CompoundDrawables.ic_delete,
|
||||
extraPadding = noExtraPadding,
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -28,14 +29,14 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun TimelineItemRedactedView(
|
||||
@Suppress("UNUSED_PARAMETER") content: TimelineItemRedactedContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = CommonStrings.common_message_removed),
|
||||
iconDescription = stringResource(id = CommonStrings.common_message_removed),
|
||||
iconResourceId = CompoundDrawables.ic_delete,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -45,6 +46,6 @@ fun TimelineItemRedactedView(
|
|||
internal fun TimelineItemRedactedViewPreview() = ElementPreview {
|
||||
TimelineItemRedactedView(
|
||||
TimelineItemRedactedContent,
|
||||
extraPadding = noExtraPadding
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,11 @@ import androidx.compose.material3.LocalContentColor
|
|||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.runtime.Composable
|
||||
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.components.layout.ContentAvoidingLayout
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentProvider
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -38,9 +37,9 @@ import io.element.android.wysiwyg.compose.EditorStyledText
|
|||
@Composable
|
||||
fun TimelineItemTextView(
|
||||
content: TimelineItemTextBasedContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onLinkClicked: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit = {},
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides ElementTheme.colors.textPrimary,
|
||||
|
|
@ -49,19 +48,14 @@ fun TimelineItemTextView(
|
|||
|
||||
val formattedBody = content.formattedBody
|
||||
val body = SpannableString(formattedBody ?: content.body)
|
||||
val extraPaddingText = extraPadding.getStr()
|
||||
|
||||
Box(modifier) {
|
||||
val textWithPadding = remember(body) {
|
||||
buildSpannedString {
|
||||
append(body)
|
||||
append(extraPaddingText)
|
||||
}
|
||||
}
|
||||
EditorStyledText(
|
||||
text = textWithPadding,
|
||||
text = body,
|
||||
onLinkClickedListener = onLinkClicked,
|
||||
style = ElementRichTextEditorStyle.textStyle(),
|
||||
onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChanged = onContentLayoutChanged),
|
||||
releaseOnDetach = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +68,6 @@ internal fun TimelineItemTextViewPreview(
|
|||
) = ElementPreview {
|
||||
TimelineItemTextView(
|
||||
content = content,
|
||||
extraPadding = ExtraPadding(extraWidth = 32.dp),
|
||||
onLinkClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -28,14 +29,14 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun TimelineItemUnknownView(
|
||||
@Suppress("UNUSED_PARAMETER") content: TimelineItemUnknownContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = CommonStrings.common_unsupported_event),
|
||||
iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
|
||||
iconResourceId = CompoundDrawables.ic_info_solid,
|
||||
extraPadding = extraPadding,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -45,6 +46,6 @@ fun TimelineItemUnknownView(
|
|||
internal fun TimelineItemUnknownViewPreview() = ElementPreview {
|
||||
TimelineItemUnknownView(
|
||||
content = TimelineItemUnknownContent,
|
||||
extraPadding = noExtraPadding
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
|
|
@ -43,6 +44,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentProvider
|
||||
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageEvents
|
||||
|
|
@ -64,7 +66,7 @@ import kotlinx.coroutines.delay
|
|||
fun TimelineItemVoiceView(
|
||||
state: VoiceMessageState,
|
||||
content: TimelineItemVoiceContent,
|
||||
extraPadding: ExtraPadding,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun playPause() {
|
||||
|
|
@ -73,9 +75,18 @@ fun TimelineItemVoiceView(
|
|||
|
||||
val a11y = stringResource(CommonStrings.common_voice_message)
|
||||
Row(
|
||||
modifier = modifier.semantics {
|
||||
contentDescription = a11y
|
||||
},
|
||||
modifier = modifier
|
||||
.semantics {
|
||||
contentDescription = a11y
|
||||
}
|
||||
.onSizeChanged {
|
||||
onContentLayoutChanged(
|
||||
ContentAvoidingLayoutData(
|
||||
contentWidth = it.width,
|
||||
contentHeight = it.height,
|
||||
)
|
||||
)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
when (state.button) {
|
||||
|
|
@ -105,7 +116,6 @@ fun TimelineItemVoiceView(
|
|||
seekEnabled = !context.isScreenReaderEnabled(),
|
||||
onSeek = { state.eventSink(VoiceMessageEvents.Seek(it)) },
|
||||
)
|
||||
Spacer(Modifier.width(extraPadding.getDpSize()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +247,7 @@ internal fun TimelineItemVoiceViewPreview(
|
|||
TimelineItemVoiceView(
|
||||
state = timelineItemVoiceViewParameters.state,
|
||||
content = timelineItemVoiceViewParameters.content,
|
||||
extraPadding = noExtraPadding,
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +260,7 @@ internal fun TimelineItemVoiceViewUnifiedPreview() = ElementPreview {
|
|||
TimelineItemVoiceView(
|
||||
state = it.state,
|
||||
content = it.content,
|
||||
extraPadding = noExtraPadding,
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* 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.layout
|
||||
|
||||
import android.text.Layout
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.text.roundToPx
|
||||
import io.element.android.wysiwyg.compose.EditorStyledText
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* A layout with 2 children: the [content] and the [overlay].
|
||||
*
|
||||
* It will try to place the [overlay] on top of the [content] if possible, avoiding the area of it that is non-overlapping.
|
||||
* If the [overlay] can't be placed on top of the [content], it will be placed to the right of it, if it fits, otherwise, to its bottom in a new row.
|
||||
*
|
||||
* @param overlay The 'overlay' component of the layout, which will be positioned relative to the [content].
|
||||
* @param modifier The modifier for the layout.
|
||||
* @param spacing The spacing between the [content] and the [overlay]. Defaults to `0.dp`.
|
||||
* @param overlayOffset The offset of the [overlay] from the bottom right corner of the [content].
|
||||
* @param shrinkContent Whether the content should be shrunk to fit the available width or not. Defaults to `false`.
|
||||
* @param content The 'content' component of the layout.
|
||||
*/
|
||||
@Composable
|
||||
fun ContentAvoidingLayout(
|
||||
overlay: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
spacing: Dp = 0.dp,
|
||||
overlayOffset: DpOffset = DpOffset.Zero,
|
||||
shrinkContent: Boolean = false,
|
||||
content: @Composable ContentAvoidingLayoutScope.() -> Unit,
|
||||
) {
|
||||
val scope = remember { ContentAvoidingLayoutScopeInstance() }
|
||||
|
||||
Layout(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
scope.content()
|
||||
overlay()
|
||||
}
|
||||
) { measurables, constraints ->
|
||||
assert(measurables.size == 2) { "ContentAvoidingLayout must have exactly 2 children" }
|
||||
|
||||
// Measure the `overlay` view first, in case we need to shrink the `content`
|
||||
val overlayPlaceable = measurables.last().measure(Constraints(minWidth = 0, maxWidth = constraints.maxWidth))
|
||||
val contentConstraints = if (shrinkContent) {
|
||||
Constraints(minWidth = 0, maxWidth = constraints.maxWidth - overlayPlaceable.width)
|
||||
} else {
|
||||
Constraints(minWidth = 0, maxWidth = constraints.maxWidth)
|
||||
}
|
||||
val contentPlaceable = measurables.first().measure(contentConstraints)
|
||||
|
||||
var layoutWidth = contentPlaceable.width
|
||||
var layoutHeight = contentPlaceable.height
|
||||
|
||||
val data = scope.data
|
||||
|
||||
// Free space = width of the whole component - width of its non overlapping contents
|
||||
val freeSpace = max(contentPlaceable.width - data.nonOverlappingContentWidth, 0)
|
||||
|
||||
when {
|
||||
// When the content + the overlay don't fit in the available max width, we need to move the overlay to a new row
|
||||
!shrinkContent && data.nonOverlappingContentWidth + overlayPlaceable.width > constraints.maxWidth -> {
|
||||
layoutHeight += overlayPlaceable.height + overlayOffset.y.roundToPx()
|
||||
}
|
||||
// If the content is smaller than the available max width, we can move the overlay to the right of the content
|
||||
contentPlaceable.width < constraints.maxWidth -> {
|
||||
// If both the content and the overlay plus the padding can fit inside the current layoutWidth, there is no need to increase it
|
||||
if (freeSpace < overlayPlaceable.width + spacing.roundToPx()) {
|
||||
// Otherwise, we need to increase it by the width of the overlay + some padding adjustments
|
||||
val calculatedWidth = max(data.nonOverlappingContentWidth + overlayPlaceable.width + spacing.roundToPx(), contentPlaceable.width)
|
||||
layoutWidth = min(calculatedWidth, constraints.maxWidth)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
layoutWidth = max(layoutWidth, constraints.minWidth)
|
||||
layoutHeight = max(layoutHeight, constraints.minHeight)
|
||||
|
||||
layout(layoutWidth, layoutHeight) {
|
||||
contentPlaceable.placeRelative(0, 0)
|
||||
overlayPlaceable.placeRelative(layoutWidth - overlayPlaceable.width, layoutHeight - overlayPlaceable.height + overlayOffset.y.roundToPx())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class to hold the content layout data.
|
||||
* This is used to pass the data from the content to the [ContentAvoidingLayout].
|
||||
*
|
||||
* @param contentWidth The full width of the content in pixels.
|
||||
* @param contentHeight The full height of the content in pixels.
|
||||
* @param nonOverlappingContentWidth The width of the part of the content that can't overlap with the timestamp.
|
||||
* @param nonOverlappingContentHeight The height of the part of the content that can't overlap with the timestamp.
|
||||
*/
|
||||
@Suppress("DataClassShouldBeImmutable")
|
||||
data class ContentAvoidingLayoutData(
|
||||
var contentWidth: Int = 0,
|
||||
var contentHeight: Int = 0,
|
||||
var nonOverlappingContentWidth: Int = contentWidth,
|
||||
var nonOverlappingContentHeight: Int = contentHeight,
|
||||
)
|
||||
|
||||
/**
|
||||
* A scope for the [ContentAvoidingLayout].
|
||||
*/
|
||||
interface ContentAvoidingLayoutScope {
|
||||
|
||||
/**
|
||||
* It should be called when the content layout changes, so it can update the [ContentAvoidingLayoutData] and measure and layout the content properly.
|
||||
*/
|
||||
fun onContentLayoutChanged(data: ContentAvoidingLayoutData)
|
||||
}
|
||||
|
||||
private class ContentAvoidingLayoutScopeInstance(
|
||||
val data: ContentAvoidingLayoutData = ContentAvoidingLayoutData(),
|
||||
) : ContentAvoidingLayoutScope {
|
||||
override fun onContentLayoutChanged(data: ContentAvoidingLayoutData) {
|
||||
this.data.contentWidth = data.contentWidth
|
||||
this.data.contentHeight = data.contentHeight
|
||||
this.data.nonOverlappingContentWidth = data.nonOverlappingContentWidth
|
||||
this.data.nonOverlappingContentHeight = data.nonOverlappingContentHeight
|
||||
}
|
||||
}
|
||||
|
||||
object ContentAvoidingLayout {
|
||||
/**
|
||||
* Measures the last line of a [TextLayoutResult] and calls [onContentLayoutChanged] with the [ContentAvoidingLayoutData].
|
||||
*
|
||||
* This is supposed to be used in the `onTextLayout` parameter of a Text based component.
|
||||
*/
|
||||
@Composable
|
||||
internal fun measureLastTextLine(
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
extraWidth: Dp = 0.dp,
|
||||
): ((TextLayoutResult) -> Unit) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val extraWidthPx = extraWidth.roundToPx()
|
||||
return { textLayout: TextLayoutResult ->
|
||||
// We need to add the external extra width so it's not taken into account as 'free space'
|
||||
val lastLineWidth = when (layoutDirection) {
|
||||
LayoutDirection.Ltr -> textLayout.getLineRight(textLayout.lineCount - 1).roundToInt()
|
||||
LayoutDirection.Rtl -> textLayout.getLineLeft(textLayout.lineCount - 1).roundToInt()
|
||||
}
|
||||
val lastLineHeight = textLayout.getLineBottom(textLayout.lineCount - 1).roundToInt()
|
||||
onContentLayoutChanged(
|
||||
ContentAvoidingLayoutData(
|
||||
contentWidth = textLayout.size.width + extraWidthPx,
|
||||
contentHeight = textLayout.size.height,
|
||||
nonOverlappingContentWidth = lastLineWidth + extraWidthPx,
|
||||
nonOverlappingContentHeight = lastLineHeight,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the last line of a [Layout] and calls [onContentLayoutChanged] with the [ContentAvoidingLayoutData].
|
||||
*
|
||||
* This is supposed to be used in the `onTextLayout` parameter of an [EditorStyledText] component.
|
||||
*/
|
||||
@Composable
|
||||
internal fun measureLegacyLastTextLine(
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
extraWidth: Dp = 0.dp,
|
||||
): ((Layout) -> Unit) {
|
||||
val extraWidthPx = extraWidth.roundToPx()
|
||||
return { textLayout: Layout ->
|
||||
// We need to add the external extra width so it's not taken into account as 'free space'
|
||||
val lastLineWidth = textLayout.getLineWidth(textLayout.lineCount - 1).roundToInt()
|
||||
val lastLineHeight = textLayout.getLineBottom(textLayout.lineCount - 1)
|
||||
onContentLayoutChanged(
|
||||
ContentAvoidingLayoutData(
|
||||
contentWidth = textLayout.width + extraWidthPx,
|
||||
contentHeight = textLayout.height,
|
||||
nonOverlappingContentWidth = lastLineWidth + extraWidthPx,
|
||||
nonOverlappingContentHeight = lastLineHeight,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class TimelineItemAudioContent(
|
||||
|
|
@ -29,7 +30,7 @@ data class TimelineItemAudioContent(
|
|||
) : TimelineItemEventContent {
|
||||
|
||||
val fileExtensionAndSize =
|
||||
io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize(
|
||||
formatFileExtensionAndSize(
|
||||
fileExtension,
|
||||
formattedFileSize
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import android.graphics.Paint
|
|||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import android.text.style.ReplacementSpan
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class MentionSpan(
|
||||
|
|
@ -32,51 +34,63 @@ class MentionSpan(
|
|||
val typeface: Typeface = Typeface.DEFAULT,
|
||||
) : ReplacementSpan() {
|
||||
|
||||
companion object {
|
||||
private const val MAX_LENGTH = 20
|
||||
}
|
||||
|
||||
private var actualText: CharSequence? = null
|
||||
private var textWidth = 0
|
||||
private var cachedRect: RectF = RectF()
|
||||
private val backgroundPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = backgroundColor
|
||||
}
|
||||
|
||||
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
|
||||
val mentionText = getActualText(text, start)
|
||||
var actualEnd = end
|
||||
if (mentionText != text.toString()) {
|
||||
actualEnd = end + 1
|
||||
}
|
||||
val mentionText = getActualText(text, start, end)
|
||||
paint.typeface = typeface
|
||||
return paint.measureText(mentionText, start, actualEnd).roundToInt() + startPadding + endPadding
|
||||
textWidth = paint.measureText(mentionText, 0, mentionText.length).roundToInt()
|
||||
return textWidth + startPadding + endPadding
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
|
||||
val mentionText = getActualText(text, start)
|
||||
var actualEnd = end
|
||||
if (mentionText != text.toString()) {
|
||||
actualEnd = end + 1
|
||||
}
|
||||
val textWidth = paint.measureText(mentionText, start, actualEnd)
|
||||
val mentionText = getActualText(text, start, end)
|
||||
|
||||
// Extra vertical space to add below the baseline (y). This helps us center the span vertically
|
||||
val extraVerticalSpace = y + paint.ascent() + paint.descent() - top
|
||||
val rect = RectF(x, top.toFloat(), x + textWidth + startPadding + endPadding, y.toFloat() + extraVerticalSpace)
|
||||
paint.color = backgroundColor
|
||||
canvas.drawRoundRect(rect, rect.height() / 2, rect.height() / 2, paint)
|
||||
if (cachedRect.isEmpty) {
|
||||
cachedRect = RectF(x, top.toFloat(), x + textWidth + startPadding + endPadding, y.toFloat() + extraVerticalSpace)
|
||||
}
|
||||
|
||||
val rect = cachedRect
|
||||
val radius = rect.height() / 2
|
||||
canvas.drawRoundRect(rect, radius, radius, backgroundPaint)
|
||||
paint.color = textColor
|
||||
paint.typeface = typeface
|
||||
canvas.drawText(mentionText, start, actualEnd, x + startPadding, y.toFloat(), paint)
|
||||
canvas.drawText(mentionText, 0, mentionText.length, x + startPadding, y.toFloat(), paint)
|
||||
}
|
||||
|
||||
private fun getActualText(text: CharSequence?, start: Int): String {
|
||||
return when (type) {
|
||||
Type.USER -> {
|
||||
val mentionText = text.toString()
|
||||
if (start in mentionText.indices && mentionText[start] != '@') {
|
||||
mentionText.replaceRange(start, start, "@")
|
||||
} else {
|
||||
mentionText
|
||||
private fun getActualText(text: CharSequence?, start: Int, end: Int): CharSequence {
|
||||
if (actualText != null) return actualText!!
|
||||
return buildString {
|
||||
val mentionText = text.orEmpty()
|
||||
when (type) {
|
||||
Type.USER -> {
|
||||
if (start in mentionText.indices && mentionText[start] != '@') {
|
||||
append("@")
|
||||
}
|
||||
}
|
||||
Type.ROOM -> {
|
||||
if (start in mentionText.indices && mentionText[start] != '#') {
|
||||
append("#")
|
||||
}
|
||||
}
|
||||
}
|
||||
Type.ROOM -> {
|
||||
val mentionText = text.toString()
|
||||
if (start in mentionText.indices && mentionText[start] != '#') {
|
||||
mentionText.replaceRange(start, start, "#")
|
||||
} else {
|
||||
mentionText
|
||||
}
|
||||
append(mentionText.substring(start, min(end, start + MAX_LENGTH)))
|
||||
if (end - start > MAX_LENGTH) {
|
||||
append("…")
|
||||
}
|
||||
actualText = this
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bee7c8c0c233a2ef943e8804c77597f70672c2fe23f697295889ce06f8ba7a07
|
||||
size 139476
|
||||
oid sha256:fd8440df56bc810e9d01167580eca5409e1e0458c463f0e1d039d092ff35ed49
|
||||
size 139471
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b780f7b6ade56b031f25829bbe313c1e71d53f3554ebdee61faa46b2f92c218f
|
||||
size 135788
|
||||
oid sha256:eb450d60e438e212aeecb8e82771b54d1eeeeaf1063a0bb80c8f4c070e181ba7
|
||||
size 135783
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c691a70d0111d0b395e76e7ba94b382ffd470ea066e229bf182f8fbf631c899b
|
||||
size 18242
|
||||
oid sha256:795ff3e304af3dbfb08dbe5f6baf866fce06301d722ea8044be609e89288ea82
|
||||
size 18245
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:851fc21681b724affa557d219b8e7be86b6058b788a5f2983f0db96f113f00eb
|
||||
size 62632
|
||||
oid sha256:79b9fa345741c9ac7d0692d8a703366a956e65ec8de90ed56565de74792e0463
|
||||
size 62366
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d9a54f34c27eb88d05eeb6305e73d5e6b0d1c347601a2d3a3426520c2ab1703e
|
||||
size 65272
|
||||
oid sha256:9c86dd7eab9b07bd226b5f05c0f50fde475118504be789659f1536cdccb3ec06
|
||||
size 65149
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bd2f473ac5685ea4dfbb565204e3d3f1b608f0b44a06c382f562b97846b1d75e
|
||||
size 69546
|
||||
oid sha256:8e266c57ad969e67ab24546573cf7e67b58734b3dc24f18ee872b043c6279f4f
|
||||
size 69476
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb97680e0e8a74be5369ce8d443f4dbde8c8db5b3b5c62c8f91795e913f70497
|
||||
size 72429
|
||||
oid sha256:db4dd5c7b1d012a52f0b20f8e94d1ec01d8615b4a4db40c831ea986c31cbd4ba
|
||||
size 72370
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4c9cb9e39505d30bd562f26f61f30f68b037b1c3ac542e8499f7f602687d508a
|
||||
size 62930
|
||||
oid sha256:7197e5e329c23f8647f9345d43ea4d90396fc7b98f55409f26ba286e202de2a1
|
||||
size 62958
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2f26f5518b23013e254efea64ca3318cf390fe9af407b4ca29428df2dae15058
|
||||
size 64959
|
||||
oid sha256:9e91d3af4ac0adb02c9354388d6492807211cad7ed133593d71acc21608a260f
|
||||
size 65309
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf37662355ec12d52d2a8a88ef58fd9743b7fe278050d6d0a5504983c8844031
|
||||
size 69625
|
||||
oid sha256:928a380efa661ceb95cbb839339a142aa6c1fae747c1136f35e7545434d6876c
|
||||
size 69676
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b5be47550b5fcd0ffe3def6a4448e47c96641224100ed82acb977ee033527714
|
||||
size 71424
|
||||
oid sha256:971000889f1e354115f785865a609e822b2ef89f98020142be6a68ae06ce3feb
|
||||
size 71775
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9c4c522daf904898d38b32196dc43628697816381c210ba404321df0b409f2c
|
||||
size 85265
|
||||
oid sha256:8fa46a02487955106780200ff91b13c3f2c8585f83fefc43358f0abab5a635fd
|
||||
size 85229
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9717496e144f7a27919723d037d61668289b5349f2885bd5fc1489606c726625
|
||||
size 81546
|
||||
oid sha256:7452b71faf637d017c3a29b3df7fdb0d37847111f88b31702170f51921e52bb9
|
||||
size 81520
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f7f88ed6f4c84ebe5c9dd6c46474fac299b2b2e8edce0bfebe49064bcaab5fe7
|
||||
size 27226
|
||||
oid sha256:96b80b6e3570e64e8db04b6d166b649cc129490c14b5d83650a5bf8513dee611
|
||||
size 27243
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bd8ab01406157ba78a9923611a9d21d5afe6d1d433f3aa3cf263b1507fd7eee7
|
||||
size 26774
|
||||
oid sha256:b392be78f2186bce8a7bb53e3bf51d645630696a81695dee74cf6fa62c90a4c3
|
||||
size 26693
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e7aafa97412bf18c9820aa158d0e1c2917cc471fa1977bf2ef6cc68d3a5bbc42
|
||||
size 31933
|
||||
oid sha256:3ffb669d3a8c2d24a701fb11e0595db4bb60b97c537cf60a68e7214b62c024e3
|
||||
size 31735
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a162bad1922fb62ab2da4fe396fed4192800e21b9bccff1c5b309a8cd482994a
|
||||
size 26169
|
||||
oid sha256:77172e55f8d5407c83c0f307d17b2997f01f0033b523abbc9cb38b8a3acebca2
|
||||
size 26038
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8198c156b3dcb61583d2543b3d2f4e62adbd54b9ac8579b9c2fbd9a8c6f8bc41
|
||||
size 25604
|
||||
oid sha256:f3bea7d555cb10a2b8884a1c6f0a7372ecf2fdcf3448780f60d4d3e1df8b2d09
|
||||
size 25531
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4beebf86fc936884447204fa81ef2104e25d60bde1581d077468d2ed6f3cc1ad
|
||||
size 31052
|
||||
oid sha256:0eff0cb1de73346a3d988974c4f6c5aa5d4087e502d5a6f69c8314c5d24a414e
|
||||
size 30944
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7db830480ca028fc17f731b2f5e507b43697f908809ef496461b6974bdfb2706
|
||||
size 167384
|
||||
oid sha256:7a5eb70047242fc8d6405e5ad907fa80aafec03b6478a7fe699dce5fa314aeb0
|
||||
size 167383
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d63fd4fe914328f70880b3157f13bd48e583c5d57edc27dcdede82044514f699
|
||||
size 171789
|
||||
oid sha256:ecbbf370005e21fa05aca5221edc143e26bbdaf4b6190ecf0839c20a607d65e9
|
||||
size 171793
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:36b8b5a7dd154dc69732320c35c8e0a8a77fdc03365b0ea311d03cdf3c7f71cc
|
||||
size 150907
|
||||
oid sha256:bb7ec80a57bbdfb1d4038fb10b00b8ef16bf1900b9419087972f83b85a402bc8
|
||||
size 150784
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:00c5efab417077f1a6c0dfdbd6f63b0faa5e9ee3174f0b22aab1dc632330e6bb
|
||||
size 165109
|
||||
oid sha256:72ed717024a4996d8f4baca3e052346e9983f182b51d52ba33c4b20c631cf8e6
|
||||
size 165137
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:46a9fc41736c83b40c697beed7cbf3c14b3eeeb889d229e77d85cf68b93ac67e
|
||||
size 154122
|
||||
oid sha256:f3da730f83e91c9f40d2f54a20257ec9093f88fb2862d4d5d92d346ff3fa1fe2
|
||||
size 154132
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e7b3b360caf897eaf402091d6ec0c9ae28683e6c94d9e9bdc44c2a5c06b5da04
|
||||
size 152475
|
||||
oid sha256:d5fd5fe7fc6cef7c18d424728b113f516f04f7e39acc5055e243710563f235d9
|
||||
size 152466
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:07843d212b755e8e4a19d270b06d3afcda0e5382f5ccc098c19e5268fba585c6
|
||||
size 161056
|
||||
oid sha256:a1a56e97ce7d7f06c34dc35c5038812b5c864f62b4714816a8f31bfe977152a5
|
||||
size 161077
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:28844b117cda36b3afdd7c791f52949d7a0095863d6c606090fa3e200fc6a125
|
||||
size 151427
|
||||
oid sha256:719e764612a2187f513b25c2ccf58d7345200bb16892f6476cab1c718dc626e8
|
||||
size 151422
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4536e2b04119145314c34eb0289ca2fa0028c72269fb235447f56cc6ebabb85e
|
||||
size 152188
|
||||
oid sha256:6b70b68ef3d1bf59272655b8492addff1186a9e43af208ca135cda310821c029
|
||||
size 151998
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:34da0e10f040d85752c06cb9af84ef02a3f93a64dfd56d4fcd9e10423e0729d5
|
||||
size 154430
|
||||
oid sha256:cc899b2b8c5bb1ec64c8009ed452de7e3da7a82a696e13310a9efdeeb51f7b4f
|
||||
size 154423
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1ab9006744fade5db0e20d5d5add106125a78a22c37c5643bb2c14a95923d901
|
||||
size 162320
|
||||
oid sha256:d1d24cabd468fcf84ba3f66293a4d97f0848eb66f5e78eb336ad6e48f41f1c61
|
||||
size 162353
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c6d9f8e62850eb1bd7cf1bf89436b18b70e976ef3c6a3c33d880d6ac2eccac29
|
||||
size 151678
|
||||
oid sha256:3bbecc345837136e1248903612eeffae683e2f83e538732df0a881424075cff7
|
||||
size 151576
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:99da12740d48a70bc54fb07ac4c765dd442d94883502fc1b6c07ef345ad56ad2
|
||||
size 165103
|
||||
oid sha256:c5a92dcc9e3e6ed3c0bb052269b52840a1cab7dde7fcdcb7ae5e761a27d5b768
|
||||
size 165082
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a59b0f1fb3a46bedf01520ec1167dd1d33d73d6da5a9b9ad690f13eaff11608d
|
||||
size 169346
|
||||
oid sha256:ed0008be941b783f431746d31a656ae2b1e746adb3f5b3588b4409f4ecf37a83
|
||||
size 169369
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ccbf09b9dd72f8bfd9ed0a9ea7af3990d513452614dc04ff13b0389ec097a2e5
|
||||
size 149329
|
||||
oid sha256:7ddf130451611b2cdc54612f75759fcd61decd351290636cbbec0a0b04b2f187
|
||||
size 149336
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0937e4b1616c76b40ef2699577ce765ba7c01f5c22f12677b5c161a4899b2021
|
||||
size 163218
|
||||
oid sha256:e1fab858b3fdb75e534d53fcc8fab14a88253e3dfd160a5a862dbdd39e848a10
|
||||
size 163173
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f41aeb0c842979624d2453e8c95974a2ed3971115f5ef56e6b90841d94e1a7e8
|
||||
size 152790
|
||||
oid sha256:531c7da7fdb2b99f479b3c7d0b2b6748f740b1aed46954e3bd93e50673cec196
|
||||
size 152780
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a0bfc14e7432e00bae59d44362034398eaaa5676bf591128870deebf622758eb
|
||||
size 151065
|
||||
oid sha256:86939e8497a74b1e3ec1b15d620a782ca40779d1e3e9560d8071e7e96c8316d5
|
||||
size 151050
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3fdb4183f704b8f1cda0712f2389f3b46e9e47a7ff59f954d2cc494928ecb381
|
||||
size 159382
|
||||
oid sha256:437d87586d7bbc77b14ba5d124bb1eee5b7de8efbf7ef93f164e627a22d70649
|
||||
size 159385
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:41c59480defc44111dba7e9fb3ee020f55f20265cd24ee6045a52438ade062a4
|
||||
size 149840
|
||||
oid sha256:7d5a2866a3c25390472e08a4ca408b9efabe217c7ef2b86e58cfddb170493681
|
||||
size 149810
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fd3c1ca7b2ac2f73355b3ca71e354a2829008ced7494be0ea2212f35ede6e0cd
|
||||
size 150566
|
||||
oid sha256:98294c2aa6ee9b0beb059de3536e55577575fc3553d1063ba4e693c025bd2bd0
|
||||
size 150576
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9071c32211b55d05947071631d2da06bb1b17f87fa087b478f92ca9faedda3c0
|
||||
size 153189
|
||||
oid sha256:6051b34869f71b0c61b06a64dca325c12a28a838094175589866e2942d4c5683
|
||||
size 153168
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e6e650e9054ed49ad539718843b36d6065954388cec1e41e362de5868d471398
|
||||
size 160496
|
||||
oid sha256:66f58b1382098c5cbe88a6d9f0579eb0243101d9c357a85745465f15c2cb84fb
|
||||
size 160479
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2cc8fb2604f10a423b39c75c0824632197b52304e06b5a33f3d8300ba3390327
|
||||
size 150050
|
||||
oid sha256:3f274a32f53436c90d72846de43fdeb302ce925c96e6c21d0a901eb65add97e9
|
||||
size 150066
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c4b726ff874141615824c929c22518245cd9ddcee331a2d1b1e953ce98940627
|
||||
size 190995
|
||||
oid sha256:94a87cc8a6b2efa1b2e4bee88f3a1ab8ee09b6a337c4ceb23247de7d18b9e41b
|
||||
size 190944
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b3a8db4dab06f907fd3a83de9c9dd81a9c6671a6e7e77d4af78e1ef1795336db
|
||||
size 187116
|
||||
oid sha256:f8d72a4bcb587cde92f22203116711e215cba8ecd201e2813c6ce80ec94109b5
|
||||
size 187089
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:94be80f7b3f2a4fb3cc7f89c5e013e1eb99154862a46420ab6d30cd18fb6587d
|
||||
size 53018
|
||||
oid sha256:5b8180b725f14e4387edb1c25b64f82af3045252fdb7efb14afc11f2069da495
|
||||
size 52876
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:47bc7cc50be3b70e1441debf5e140fd637b457e237d09b421e0fc3a7a8181da2
|
||||
size 74753
|
||||
oid sha256:53c938a23d05cfac715c3885356859cbf0fcec9a881906fb6c7c5a8a10a72330
|
||||
size 74675
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cc02bbef3944a10d19d926107ae27e1ce5abb82aa8532ff3f49e16d4239ef638
|
||||
size 54649
|
||||
oid sha256:552103a87a25523828740b180acb7a96682605a7b581c2797b0d1ffded92ad72
|
||||
size 54389
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:68bbb20edcec05fc3675ef998a90058c5cb9ea831656676c7f316be4c7ce03a6
|
||||
size 66909
|
||||
oid sha256:6a8a2abb329249a48ea9f994d713516ee3d9d91c5f5fe10e638627e795687e27
|
||||
size 66869
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3f9b93c292973432b32c53e33a621e7d7626fe838653550738a6a9dd3925cc82
|
||||
size 50687
|
||||
oid sha256:4f9d9dc90365878723c750535619d63c3c69e069870efef8b0e245ec24ab9602
|
||||
size 50658
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f7b1f9f6f91e2b4824270af423cfa637822462c21b2aa1660d61a64821084d28
|
||||
size 67754
|
||||
oid sha256:b8b4b26cdca3cc7384195046837ccd29dd59eefba94c2cafd4187dd5da6d2607
|
||||
size 67739
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f8c841112673d692b508315a9b73132ec38abe9a2b3f0aea43859d5cebcdb970
|
||||
size 57976
|
||||
oid sha256:4c93f5e3f77c4a68031244d282e24efec61ab28e6fe89bd5aeefd99f6a5d0c07
|
||||
size 58095
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8b0304f8178f2c1812f4ba9495597f2671a19c8c06864269557458e5b952a89c
|
||||
size 72699
|
||||
oid sha256:2e0072e4c5b6d93ac23e7dbeddffb980c833fe5ace55e3610823ced7e808b17d
|
||||
size 72625
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc9394387d75232efad1f6a571c40d1fba5f5d5de1c85a0f004c4a52e8ef2428
|
||||
size 88489
|
||||
oid sha256:93db3209fe5b97c0e214291f032b9d34f9eafb55775633f9c30cf923c05ee450
|
||||
size 88488
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e6d3d23219a9cae2a751e4f279fd2537bb6d90147d010bb60ecd0eabccf93af9
|
||||
size 74378
|
||||
oid sha256:bf7c20343e19f454e55b027f2e9d674d086db22e9f70ce223888cc1626d935cc
|
||||
size 74884
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e2723ba9980e32cc50b2850f2656ef4cc206328cd53dfeb98618eae3fced13a1
|
||||
size 106852
|
||||
oid sha256:d2a691bd1ec488bcc8cf40c490a30c1e3221dedadd887293a772db9758f47992
|
||||
size 106813
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aaf8182f15a45208c2559cc40225d3c48bb3517446f4a3521864fbac2a2b5458
|
||||
size 57621
|
||||
oid sha256:1508e297ab3c955b9784dda60b4e88db9e0a6d5c82f971b9144b8016e4a254fc
|
||||
size 56597
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:77c5f301bdff94528b1153318838a84bb85032cad0bc2ecfdd90fe0e7fb869bc
|
||||
size 51266
|
||||
oid sha256:5ff448008a5dc0f3c640be53436d3197630ebe761fb046fa5c3510bf9e2a2de8
|
||||
size 50999
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b41f17e7a2b5a167defabf689ec260f0efe44307d2a422f8c543897f7fea14c8
|
||||
size 71634
|
||||
oid sha256:3287968c74c01d2c4781da7d1ff958ccc3fddb51f84f2cbe74bb7921c7f65a19
|
||||
size 71627
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2193a11873fdf07b1d6fe990154c8506153b0beef7d971dcefe78d1e1b63a0a7
|
||||
size 52888
|
||||
oid sha256:8af811dc353b9bcf90838d7524ab6fd365f352e8d4372dc8125635b0bda6b9a0
|
||||
size 52659
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:54d20d4100b8ba70d76c236abd0be7ccf9dfb2bd432fe552b8a18bfce1ef5b84
|
||||
size 64244
|
||||
oid sha256:d8185fae5199f413dc912727c21501de821c586b0e0b899410587d5eb53e80e8
|
||||
size 64190
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1680c3164c780dafd46462d6770a6a84c29ea46e5a7adb6d834164cbc2bd14b1
|
||||
size 48945
|
||||
oid sha256:b9b465d0fef08eff7ced732123cfec8dab1f19acb4be7733f8661afa05ef65af
|
||||
size 48927
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:da410a09335165b25ee3ce4fe6b7f03a5bc67dc546a85ab45c06ce78a7adf56c
|
||||
size 65011
|
||||
oid sha256:d49b01d32e276ce9213605418e8ea494363edf3c76338fb0b4b24bfd1ffc1bb6
|
||||
size 64925
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c94aab9ca99a3cc6b04ce1041251c2f666619728c6421c3dd12e691cfb3bb365
|
||||
size 55787
|
||||
oid sha256:d7c475add38b97b849970d22e39137fa8e8a2f0bf7c14ce957828fe56adc5b92
|
||||
size 55867
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bae3ea763fdf8d118909f2c22d4eafda5ad4ceda3b24c846002e10e6032c196e
|
||||
size 69826
|
||||
oid sha256:de84923f8ed54914c8ab30b17823ddfd43ac845a3e349e597931b2735a6ce424
|
||||
size 69696
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a1b12ee386b4cd9601fae1b1c4ed1779defd7a10ca929bb5cd683f7039cf9fc1
|
||||
size 83922
|
||||
oid sha256:d6b01a2966ee15c1305454ab0f33a0b333864a941dbcaf6059e1479ff6e50555
|
||||
size 83899
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:12642b59eb842993428ae31724cf885647e243eb36e37778350879bac9571b0e
|
||||
size 72187
|
||||
oid sha256:0b5012f37f6dd279b35ef15b56d4b6bca7d8b16b387a2c5e8f57351dd0ef862b
|
||||
size 72166
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7200a3147b8b0dab653bf225e8b3e1d93963b79852d02d3948194d50ffafb6ab
|
||||
size 100870
|
||||
oid sha256:a558964dd3e41908761ba945a1e9715b3ed1396257380e990c8a5a2839955173
|
||||
size 100821
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:09280f50fb09e766b03aef83f267f91f24688b435321de29f797fa10a4e3fe72
|
||||
size 56093
|
||||
oid sha256:cafb964b37fc82c3179d8abd0ec2ad7f66e2fa2e47a09e563608a9b7a46f0946
|
||||
size 55177
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d0752f68d9ab404f2669f631df280a4f48bcec3565b85f9876cd8bddf030530c
|
||||
size 57041
|
||||
oid sha256:067292421d6bf51a7858de98270d6671564aa5d42428210ae0c4e9afb7361027
|
||||
size 57090
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ceeaad4f95c818f1e16995e7d5bd4e1cc5ea82e7a6a7560d88b30cce33b779de
|
||||
size 55971
|
||||
oid sha256:ce476cb344a6cc208afc81c6e7824e02371ea98b8ca0913945adf646892a119d
|
||||
size 56007
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a9cfb621dd9ae156f53d9f8ec0a2b06e9fb5c6955139494e42badd2b0e9d224
|
||||
size 58717
|
||||
oid sha256:d393a30845d9054279cee7024323da2b683ded73ac1cd631a7be61dac5679710
|
||||
size 58763
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2472128fa67e6b12600779b438fd04c4a54a300b9b67231fab8bd70dc5c9e399
|
||||
size 52787
|
||||
oid sha256:19b9eafcb993e59955e0aece53067c9fb291515769f562bdaa361338cb04e352
|
||||
size 52730
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:66349c80a6788cebaf782bbf47d05612f0bf18bac1ce4ae8b4cf0feaf682aa0f
|
||||
size 57056
|
||||
oid sha256:7732293593581afdc475d04611513b397dedd3d90d8e4f421bd8fa494dbdc05c
|
||||
size 57107
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e0264c1fe5cc506dc79177056fa0d3ffc13d772ecbfda0dd78a4c15283bc98a6
|
||||
size 58937
|
||||
oid sha256:f3d72f6b56bbf584c764d8b7c9264296d2e376d4fa78b849e17a97b3587ccb4c
|
||||
size 58951
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5c4a0a5eff3cdafd895069be1eba53929d9ce90e2b6e3f0766ce14d85e38544a
|
||||
size 57545
|
||||
oid sha256:e2fbf447d1a40417c856dd48c7262d4326f6537db9271f5796adc3bc0a0c2cbe
|
||||
size 57516
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2f146264ed00c4537970fc3e8722de8a0e378faed86b4436b8c666a70b9d08b6
|
||||
size 54838
|
||||
oid sha256:4036f63f388553365b52c030cd66e1064bb47afac2284794590c10c8bee7b48b
|
||||
size 54888
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8f734a28b795e80593b5aa661468c190d816d3f91e47cdc7a0e59a0b5d2f3b7d
|
||||
size 54685
|
||||
oid sha256:7e882c35861c4856b58bd2729253896ab2451a40d76f36a05edb7fa223e77fe3
|
||||
size 54713
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d635a43a26b042918890f33d3b520a17dd66a26c56816db35104690e39e00241
|
||||
size 61803
|
||||
oid sha256:c527021fe0b5d92db91c8551599fccbae012196fc4006acf5873e4eb5cfbb5ed
|
||||
size 61775
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:42b443f7e333c3c9aa21bca80e6423e324e9371b3d95057691b8ac6a4afa7a89
|
||||
size 42919
|
||||
oid sha256:d8ee2bc4943fed6ae1d7d0a66261c6e1b92d02312d7635fea254104c0ebf5b5a
|
||||
size 42948
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:34b376b868b36ca264f40fa2df6bf188c48b47fb0721d40a3723721c7c7006b3
|
||||
size 42079
|
||||
oid sha256:57c4c864d6fd2e4d39b21187b6d3f64db339e7c191ff4bba17f35d1fa8744fc5
|
||||
size 42118
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b1d8a3d517a76f8f31797cea326f910498fb01d58936099a8ac108f05047d5c1
|
||||
size 55090
|
||||
oid sha256:2bce95c37798213f515946bebe1880dc41079781f56b0f6d719b72d7d56ed1ed
|
||||
size 55161
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:15d87200255f29af6fe4b2bc96230398e651ad000eb9c787080fd2796e910a4c
|
||||
size 54019
|
||||
oid sha256:51f40086adfeae6ec8c3d168ced6fcf8543af2f8bacfb5e99f3a0b4ed22efafd
|
||||
size 54097
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:766090cf3f25c0204f545a641e9b0f2c6f286ab9bc657ec4414874627d6ee9d8
|
||||
size 56528
|
||||
oid sha256:51813755ea691aae411e8e92841f7e27414c86b3d265c3efb64eb975e0396f8c
|
||||
size 56588
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1e1d7f48513465f04747e69fcf28c479e69be8423f25cae0d34698ccdc80371f
|
||||
size 47408
|
||||
oid sha256:b4f827de075241e12bf3c4259d288e5f9fd78aa3c45c43985fa14dc481048ebb
|
||||
size 47311
|
||||
|
|
|
|||
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