Detect RTL text in events and adjust the text rendering (#6994)
* Add `TextDirection.detect` to check the overall LTR or RTL direction of texts * Calculate if the contents of events have a different direction than the layout to reverse the layout direction if needed. LTR content, RTL text -> mismatched direction, reverse. RTL content, LTR text -> also mismatched, reversed too. * Measure text in `measureLastTextLine` based on its direction, not the layout's, to avoid some issues with incorrectly calculated free space. * Tweak the paddings in `TimelineEventTimestampView` a bit if the layout and text directions don't match * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
46460e1c27
commit
6068aeaf01
+9
-1
@@ -43,6 +43,7 @@ fun TimelineEventTimestampView(
|
||||
event: TimelineItem.Event,
|
||||
eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isLayoutDirectionMismatched: Boolean = false,
|
||||
) {
|
||||
val formattedTime = event.sentTime
|
||||
val hasError = event.failedToSend
|
||||
@@ -77,9 +78,16 @@ fun TimelineEventTimestampView(
|
||||
else -> Modifier
|
||||
}
|
||||
}
|
||||
|
||||
val padding = if (!isLayoutDirectionMismatched) {
|
||||
PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)
|
||||
} else {
|
||||
PaddingValues(end = TimelineEventTimestampViewDefaults.spacing)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing))
|
||||
.padding(padding)
|
||||
// For a better click target, make the corners rounded
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.then(clickableModifier)
|
||||
|
||||
+191
-16
@@ -39,7 +39,9 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.platform.ViewConfiguration
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
@@ -49,9 +51,12 @@ import androidx.compose.ui.semantics.hideFromAccessibility
|
||||
import androidx.compose.ui.semantics.isTraversalGroup
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.traversalIndex
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.constraintlayout.compose.ConstrainScope
|
||||
@@ -68,12 +73,14 @@ import io.element.android.features.messages.impl.timeline.components.receipt.Rea
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
|
||||
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.TimelineItemTextContent
|
||||
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
|
||||
@@ -91,6 +98,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.modifiers.niceClickable
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewWithExtraLargeHeight
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
|
||||
import io.element.android.libraries.designsystem.swipe.SwipeableActionsState
|
||||
@@ -106,6 +114,7 @@ import io.element.android.libraries.matrix.api.timeline.item.EmbeddedEventInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
|
||||
@@ -122,7 +131,9 @@ import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
||||
import io.element.android.libraries.ui.utils.text.detect
|
||||
import io.element.android.wysiwyg.link.Link
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
@@ -649,23 +660,44 @@ private fun MessageEventBubbleContent(
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
TimestampPosition.Aligned ->
|
||||
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::onContentLayoutChange) },
|
||||
overlay = {
|
||||
TimelineEventTimestampView(
|
||||
event = event,
|
||||
eventSink = eventSink,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
)
|
||||
TimestampPosition.Aligned -> @Composable {
|
||||
val originalLayoutDirection = LocalLayoutDirection.current
|
||||
// Detect if the direction of the text content (if any) does not match the layout direction, to place the content and timestamp correctly
|
||||
val contentDirection = if (event.content is TimelineItemTextContent) {
|
||||
remember(event.content.body) {
|
||||
when (TextDirection.detect(event.content.body)) {
|
||||
TextDirection.Ltr, TextDirection.ContentOrLtr -> LayoutDirection.Ltr
|
||||
TextDirection.Rtl, TextDirection.ContentOrRtl -> LayoutDirection.Rtl
|
||||
else -> originalLayoutDirection
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
originalLayoutDirection
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides contentDirection) {
|
||||
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::onContentLayoutChange) },
|
||||
overlay = {
|
||||
// Use the original layout direction for the timestamp
|
||||
CompositionLocalProvider(LocalLayoutDirection provides originalLayoutDirection) {
|
||||
TimelineEventTimestampView(
|
||||
event = event,
|
||||
eventSink = eventSink,
|
||||
isLayoutDirectionMismatched = originalLayoutDirection != contentDirection,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
TimestampPosition.Below ->
|
||||
Column(modifier) {
|
||||
content {}
|
||||
@@ -879,6 +911,149 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview {
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewWithExtraLargeHeight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowRtlContentPreview() = ElementPreview {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.LightGray)
|
||||
.padding(8.dp),
|
||||
text = "LTR layout direction",
|
||||
textAlign = TextAlign.Center,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
)
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
sequenceOf(false, true).forEach { isMine ->
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "ظَة وَدَاع يَسْتَغْرِب فِيهَ"
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
)
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "ظَة وَدَاع يَسْتَغْرِب فِيهَ",
|
||||
isEdited = true,
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.Middle,
|
||||
messageShield = MessageShield.UnknownDevice(isCritical = true),
|
||||
)
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "ظَة وَدَاع \nيَسْتَغْرِب فِيهَ"
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.Middle,
|
||||
)
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "ظَة وَدَاع يَسْتَغْرِب فِيهَا اَلشَّاعِر أَنْ لَا يَبْكِي مِنْ أَلَم اَلْفِرَاق،" +
|
||||
" وَيَصِف حَالَة اَلْمُودِعِينَ"
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.LightGray)
|
||||
.padding(8.dp),
|
||||
text = "RTL layout direction",
|
||||
textAlign = TextAlign.Center,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
)
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
||||
sequenceOf(false, true).forEach { isMine ->
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "ظَة وَدَاع يَسْتَغْرِب فِيهَا اَلشَّاعِر أَنْ لَا يَبْكِي مِنْ أَلَم اَلْفِرَاق،" +
|
||||
" وَيَصِف حَالَة اَلْمُودِعِينَ",
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
)
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "Testing\nLTR Line\nBreaks.",
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.Middle,
|
||||
)
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "Testing a very long LTR text in an RTL layout."
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.Middle,
|
||||
)
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "Testing LTR in RTL layout.",
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
messageShield = MessageShield.UnknownDevice(isCritical = true),
|
||||
)
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Sender with a super long name that should ellipsize",
|
||||
isMine = isMine,
|
||||
content = aTimelineItemTextContent(
|
||||
body = "Testing LTR in RTL layout.",
|
||||
isEdited = true,
|
||||
),
|
||||
timelineItemReactions = TimelineItemReactions(persistentListOf()),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
messageShield = MessageShield.UnknownDevice(isCritical = true),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ThreadSummaryViewPreview() {
|
||||
|
||||
+6
-6
@@ -15,13 +15,13 @@ import androidx.compose.runtime.mutableStateOf
|
||||
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.text.style.ResolvedTextDirection
|
||||
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.core.data.tryOrNull
|
||||
import io.element.android.libraries.designsystem.text.roundToPx
|
||||
import io.element.android.wysiwyg.compose.EditorStyledText
|
||||
import kotlin.math.max
|
||||
@@ -149,13 +149,13 @@ object ContentAvoidingLayout {
|
||||
onContentLayoutChange: (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 textDirection = tryOrNull { textLayout.getParagraphDirection(0) }
|
||||
val lastLineWidth = when (textDirection) {
|
||||
ResolvedTextDirection.Rtl -> textLayout.getLineLeft(textLayout.lineCount - 1).roundToInt()
|
||||
else -> textLayout.getLineRight(textLayout.lineCount - 1).roundToInt()
|
||||
}
|
||||
val lastLineHeight = textLayout.getLineBottom(textLayout.lineCount - 1).roundToInt()
|
||||
onContentLayoutChange(
|
||||
|
||||
@@ -130,6 +130,7 @@ androidx_compose_material3 = { module = "androidx.compose.material3:material3",
|
||||
androidx_compose_material3_windowsizeclass = { module = "androidx.compose.material3:material3-window-size-class" }
|
||||
androidx_compose_material3_adaptive = "androidx.compose.material3:material3-adaptive-android:1.0.0-alpha06"
|
||||
androidx_compose_ui = { module = "androidx.compose.ui:ui" }
|
||||
androidx_compose_ui_text = { module = "androidx.compose.ui:ui-text" }
|
||||
androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" }
|
||||
androidx_compose_ui_tooling_preview = { module = "androidx.compose.ui:ui-tooling-preview" }
|
||||
androidx_compose_ui_test_manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
||||
|
||||
@@ -19,6 +19,7 @@ android {
|
||||
dependencies {
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.services.toolbox.impl)
|
||||
implementation(libs.androidx.compose.ui.text)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.ui.utils.text
|
||||
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
|
||||
/**
|
||||
* Detect if a text is RTL or not, based on its content.
|
||||
*/
|
||||
fun TextDirection.Companion.detect(text: String): TextDirection {
|
||||
return if (text.any(::isRtlChar)) TextDirection.Rtl else TextDirection.Ltr
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if a character is an RTL character or not, based on its Unicode directionality.
|
||||
*/
|
||||
fun isRtlChar(char: Char): Boolean = when (Character.getDirectionality(char)) {
|
||||
Character.DIRECTIONALITY_RIGHT_TO_LEFT,
|
||||
Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC,
|
||||
Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING,
|
||||
Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE -> true
|
||||
else -> false
|
||||
}
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:86eac241e9084cc830ff13d231908da2eb0dff246aa5cac64dddaac68a5d5f13
|
||||
size 295298
|
||||
oid sha256:4420dfe9f6f95411093eb1c17825a389a31d7a5d5ef146129e5872cdd5d87aff
|
||||
size 295295
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d6441c572f64d99bc119d649e5e6422fe516b9d097f310ccbd99a9ddded9609f
|
||||
size 612494
|
||||
oid sha256:3f358da2b9b0521c14e525bcb2bd41c29c96aacaf5f007546e6d362bde2ee3fb
|
||||
size 612526
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e3bb005a88e9a4d2bb850478ee8ac2c4f31d32625bd6d2abf455fade461cd399
|
||||
size 607806
|
||||
oid sha256:54e71f45db31e8664c42f8c05feb91ae839589e00cb89d12088c15c85fb9f25f
|
||||
size 607824
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9e6d26823bf16cb281760fa157685edcfef3937dc7b13a7bd22387d77fc6b9b
|
||||
size 54561
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:76e5ae44362792d570cd82f3910c8bd0acde509b8418c5892e0385e7d636f042
|
||||
size 370223
|
||||
oid sha256:8344427a0fe87cbf0883ff53a9ee4dcf90976a7011f6618d85961df48b2c53a9
|
||||
size 370228
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2f0d24b8011564ee7a8b2f8c2e6a24eda668ee42d451d862f884acb7926aa06c
|
||||
size 368529
|
||||
oid sha256:c00e504593d8eb61b598f05f46ee4d591757975a80c0b4227b240847d2cd4868
|
||||
size 368510
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:991deacdfb7bc371a2df960a2364ae3b2187e588aa5afe7f39409e367040dd9c
|
||||
size 363893
|
||||
oid sha256:a4ac79f3b8fb4e231244bd96b244a4f2526d6ed28d558f9d50734d83bbb74992
|
||||
size 363909
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2cd357a1fbaa425eca95587bff2f768a45e773f983f75664875653ba1900e534
|
||||
size 361625
|
||||
oid sha256:c1e15400fa2c5ee6087316501445c551a3932b65e16de31e68acc5efbd16a559
|
||||
size 361603
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4c410e853089cc2c4a2c48a00dd9a8babc8095aed72bb4625057444710559c6c
|
||||
size 368129
|
||||
oid sha256:41a0e404dea7f52359b24b3ec3926b1405429b165cfed44c63b3922ea6d3cb46
|
||||
size 368114
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a92f8af94682facac66d4aacb6e49f345561c15be2f8b689041b78aeaaae831
|
||||
size 345062
|
||||
oid sha256:6ca8cf82aa8ab1bdad56a27584b27789402aeb7ad8fadd038280a376fb75a72c
|
||||
size 345094
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f0c6dafaa41849af00d5097a85bd88e3a054a0a9e41c0466a08e6ff8dd6102b2
|
||||
size 356857
|
||||
oid sha256:dce23463c335d01eb67293cfcede5d85e96ab2ba035cc9a188d42e8e6866bc63
|
||||
size 356831
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2528df873713755f542f61cd794080929586c1bd5f316c0378ce9e837c21d50b
|
||||
size 397145
|
||||
oid sha256:426d1c844a8c88281c5fa4c61916d93278fdfd38f8cdf2dc54bf1a821f2de3eb
|
||||
size 397167
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9daeee1aaa04247da3c8bb5aaae9947aab1ed2d2057995e53d6ebdb63e5cd356
|
||||
size 355848
|
||||
oid sha256:ffa68c8398d789444151855496e5df4ffa03218a383db6ea46fd4f979829b823
|
||||
size 355831
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7b23d3a37774283067a8919b8c332b1bb5a79e90dceb7216b484b985491282e5
|
||||
size 355389
|
||||
oid sha256:3ec1b0244dab22414c6719b3309426e13128d0c70fb67dc66cd8a60d42521fd0
|
||||
size 355411
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a92bcf5885322e11a706071969fb674b1f6e60e021af153aaa125baa4a602ae
|
||||
size 366285
|
||||
oid sha256:9a85db2f5958db867578663533f204b2e803372d470da890fd8056ffed0cf46c
|
||||
size 366254
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:54372845e922d047408dd960d048b9b5eae2263d589117d8e7d197741aa34f32
|
||||
size 342827
|
||||
oid sha256:4bee12a6bdb18fea5ac6997671bfcb903296da7278cf69b2456330066ca31dfb
|
||||
size 342870
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6524cc87257cc9d3f135904f4e14594b56df695e71fe576418d2319c472d710b
|
||||
size 355285
|
||||
oid sha256:9e2d8caf5aaae7d3406c703288e491f328552f0acd18e56afd5265b4ceaf1cd1
|
||||
size 355272
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e642229c58169775fe702769a306bbef22698516ae6f7ac75a761213c3f195fa
|
||||
size 395446
|
||||
oid sha256:217ac95bf476c53d2dc6d3d0143b21312bf38b8ab86c8dfd4f9360582b584f9d
|
||||
size 395476
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5ed24d190f10c079f8cf176404372d40eb25cc78ce595a22febc540315057b86
|
||||
size 354075
|
||||
oid sha256:dabad9dcd5b8f0d4bb964a0187ad7c94b023b04552dacd29a077daddfd48d550
|
||||
size 354052
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e614f4a8657637c584a283d69124236d00fe1251c2b8d739c5688a57a6fd152e
|
||||
size 353693
|
||||
oid sha256:756f0cff90ddd61adce2f467c0384d74d430f6d51c091e4980e45bcc9ee6f0fd
|
||||
size 353705
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309
|
||||
size 52770
|
||||
oid sha256:28a95d4b7ebca92c4e0467f3ecd9acdaf7f6257604d190f5d088a9a25e87d637
|
||||
size 55305
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5a2e306087a967849f61872b56a83f194d1a76df5723f0bbae6e6e2f4a98c63
|
||||
size 55725
|
||||
oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98
|
||||
size 53966
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5
|
||||
size 3853
|
||||
oid sha256:e8a3ad99763929d2783147814e44fa9ec7e7efb7b99949e3580f23c38fb5d971
|
||||
size 9398
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1342b6427f5b2e3075ae3ccee60bf1d3b11afaccf3e152643d6ef2d65c21b54e
|
||||
size 9108
|
||||
oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5
|
||||
size 3853
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:410a8596d4c70b9a3856d959d6d71ade8e831e8581ff410c9900957a93b3eb50
|
||||
size 8114
|
||||
oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c
|
||||
size 3664
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c
|
||||
size 3664
|
||||
oid sha256:0f3a0cb5d9bae6725a1c527f3d9575aabf85d3edf63a28d9faa23f4f0b65e2d0
|
||||
size 7624
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:58b9491aaa5554b89d86c62a5c9c44975367538c6baa5562f8dc5768f76d8264
|
||||
size 8705
|
||||
oid sha256:4811667dec0f32accc5e2c8a5dd5c0b34017fcff8d1e0376ee7a4708426bca0f
|
||||
size 9838
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d57df7110371c9224e232b707d6a1b10ce14afcb56c803a78220792f2a0d4cf2
|
||||
size 11354
|
||||
oid sha256:ef0dc4d53daeb986f378b6e4d85928b4b6729336c89cf21e642c88c53688a3ab
|
||||
size 10292
|
||||
|
||||
Reference in New Issue
Block a user