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