Render media captions formatting in the media viewer (#6729)

* Render media captions formatting in the media viewer

* Update screenshots

* Trigger actions

* Remove unused imports and reformat code

---------

Co-authored-by: ElementBot <android@element.io>
Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
bxdxnn 2026-05-08 17:30:32 +03:00 committed by GitHub
parent 643c746bc4
commit 06f3c649f5
26 changed files with 182 additions and 22 deletions

View file

@ -646,6 +646,7 @@ class MessagesFlowNode(
filename = content.filename, filename = content.filename,
fileSize = content.fileSize, fileSize = content.fileSize,
caption = content.caption, caption = content.caption,
formattedCaption = content.formattedCaption,
mimeType = content.mimeType, mimeType = content.mimeType,
formattedFileSize = content.formattedFileSize, formattedFileSize = content.formattedFileSize,
fileExtension = content.fileExtension, fileExtension = content.fileExtension,

View file

@ -17,6 +17,7 @@ import kotlinx.parcelize.Parcelize
data class MediaInfo( data class MediaInfo(
val filename: String, val filename: String,
val caption: String?, val caption: String?,
val formattedCaption: CharSequence? = null,
val mimeType: String, val mimeType: String,
val fileSize: Long?, val fileSize: Long?,
val formattedFileSize: String, val formattedFileSize: String,
@ -33,6 +34,7 @@ data class MediaInfo(
fun anImageMediaInfo( fun anImageMediaInfo(
senderId: UserId? = UserId("@alice:server.org"), senderId: UserId? = UserId("@alice:server.org"),
caption: String? = null, caption: String? = null,
formattedCaption: CharSequence? = null,
senderName: String? = null, senderName: String? = null,
dateSent: String? = null, dateSent: String? = null,
dateSentFull: String? = null, dateSentFull: String? = null,
@ -40,6 +42,7 @@ fun anImageMediaInfo(
filename = "an image file.jpg", filename = "an image file.jpg",
fileSize = 4 * 1024 * 1024, fileSize = 4 * 1024 * 1024,
caption = caption, caption = caption,
formattedCaption = formattedCaption,
mimeType = MimeTypes.Jpeg, mimeType = MimeTypes.Jpeg,
formattedFileSize = "4MB", formattedFileSize = "4MB",
fileExtension = "jpg", fileExtension = "jpg",
@ -54,6 +57,7 @@ fun anImageMediaInfo(
fun aVideoMediaInfo( fun aVideoMediaInfo(
caption: String? = null, caption: String? = null,
formattedCaption: CharSequence? = null,
senderName: String? = null, senderName: String? = null,
dateSent: String? = null, dateSent: String? = null,
dateSentFull: String? = null, dateSentFull: String? = null,
@ -62,6 +66,7 @@ fun aVideoMediaInfo(
filename = "a video file.mp4", filename = "a video file.mp4",
fileSize = 14 * 1024 * 1024, fileSize = 14 * 1024 * 1024,
caption = caption, caption = caption,
formattedCaption = formattedCaption,
mimeType = MimeTypes.Mp4, mimeType = MimeTypes.Mp4,
formattedFileSize = "14MB", formattedFileSize = "14MB",
fileExtension = "mp4", fileExtension = "mp4",
@ -77,6 +82,7 @@ fun aVideoMediaInfo(
fun aPdfMediaInfo( fun aPdfMediaInfo(
filename: String = "a pdf file.pdf", filename: String = "a pdf file.pdf",
caption: String? = null, caption: String? = null,
formattedCaption: CharSequence? = null,
senderName: String? = null, senderName: String? = null,
dateSent: String? = null, dateSent: String? = null,
dateSentFull: String? = null, dateSentFull: String? = null,
@ -84,6 +90,7 @@ fun aPdfMediaInfo(
filename = filename, filename = filename,
fileSize = 23 * 1024 * 1024, fileSize = 23 * 1024 * 1024,
caption = caption, caption = caption,
formattedCaption = formattedCaption,
mimeType = MimeTypes.Pdf, mimeType = MimeTypes.Pdf,
formattedFileSize = "23MB", formattedFileSize = "23MB",
fileExtension = "pdf", fileExtension = "pdf",
@ -105,6 +112,7 @@ fun anApkMediaInfo(
filename = "an apk file.apk", filename = "an apk file.apk",
fileSize = 50 * 1024 * 1024, fileSize = 50 * 1024 * 1024,
caption = null, caption = null,
formattedCaption = null,
mimeType = MimeTypes.Apk, mimeType = MimeTypes.Apk,
formattedFileSize = "50MB", formattedFileSize = "50MB",
fileExtension = "apk", fileExtension = "apk",
@ -120,6 +128,7 @@ fun anApkMediaInfo(
fun anAudioMediaInfo( fun anAudioMediaInfo(
filename: String = "an audio file.mp3", filename: String = "an audio file.mp3",
caption: String? = null, caption: String? = null,
formattedCaption: CharSequence? = null,
senderName: String? = null, senderName: String? = null,
dateSent: String? = null, dateSent: String? = null,
dateSentFull: String? = null, dateSentFull: String? = null,
@ -129,6 +138,7 @@ fun anAudioMediaInfo(
filename = filename, filename = filename,
fileSize = 7 * 1024 * 1024, fileSize = 7 * 1024 * 1024,
caption = caption, caption = caption,
formattedCaption = formattedCaption,
mimeType = MimeTypes.Mp3, mimeType = MimeTypes.Mp3,
formattedFileSize = "7MB", formattedFileSize = "7MB",
fileExtension = "mp3", fileExtension = "mp3",
@ -144,6 +154,7 @@ fun anAudioMediaInfo(
fun aVoiceMediaInfo( fun aVoiceMediaInfo(
filename: String = "a voice file.ogg", filename: String = "a voice file.ogg",
caption: String? = null, caption: String? = null,
formattedCaption: CharSequence? = null,
senderName: String? = null, senderName: String? = null,
dateSent: String? = null, dateSent: String? = null,
dateSentFull: String? = null, dateSentFull: String? = null,
@ -153,6 +164,7 @@ fun aVoiceMediaInfo(
filename = filename, filename = filename,
fileSize = 3 * 1024 * 1024, fileSize = 3 * 1024 * 1024,
caption = caption, caption = caption,
formattedCaption = formattedCaption,
mimeType = MimeTypes.Ogg, mimeType = MimeTypes.Ogg,
formattedFileSize = "3MB", formattedFileSize = "3MB",
fileExtension = "ogg", fileExtension = "ogg",
@ -168,6 +180,7 @@ fun aVoiceMediaInfo(
fun aTxtMediaInfo( fun aTxtMediaInfo(
filename: String = "a text file.txt", filename: String = "a text file.txt",
caption: String? = null, caption: String? = null,
formattedCaption: CharSequence? = null,
senderName: String? = null, senderName: String? = null,
dateSent: String? = null, dateSent: String? = null,
dateSentFull: String? = null, dateSentFull: String? = null,
@ -175,6 +188,7 @@ fun aTxtMediaInfo(
filename = filename, filename = filename,
fileSize = 2 * 1024, fileSize = 2 * 1024,
caption = caption, caption = caption,
formattedCaption = formattedCaption,
mimeType = MimeTypes.PlainText, mimeType = MimeTypes.PlainText,
formattedFileSize = "2kB", formattedFileSize = "2kB",
fileExtension = "txt", fileExtension = "txt",

View file

@ -25,6 +25,9 @@ android {
setupDependencyInjection() setupDependencyInjection()
dependencies { dependencies {
implementation(libs.matrix.richtexteditor.compose)
implementation(libs.matrix.richtexteditor)
implementation(projects.libraries.textcomposer.impl)
implementation(libs.coroutines.core) implementation(libs.coroutines.core)
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer)

View file

@ -32,6 +32,7 @@ class DefaultMediaViewerEntryPoint : MediaViewerEntryPoint {
filename = filename, filename = filename,
fileSize = null, fileSize = null,
caption = null, caption = null,
formattedCaption = null,
mimeType = mimeType, mimeType = mimeType,
formattedFileSize = "", formattedFileSize = "",
fileExtension = "", fileExtension = "",

View file

@ -98,6 +98,7 @@ class EventItemFactory(
filename = type.filename, filename = type.filename,
fileSize = type.info?.size, fileSize = type.info?.size,
caption = type.caption, caption = type.caption,
formattedCaption = type.formattedCaption?.body,
mimeType = type.info?.mimetype.orEmpty(), mimeType = type.info?.mimetype.orEmpty(),
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
fileExtension = fileExtensionExtractor.extractFromName(type.filename), fileExtension = fileExtensionExtractor.extractFromName(type.filename),
@ -118,6 +119,7 @@ class EventItemFactory(
filename = type.filename, filename = type.filename,
fileSize = type.info?.size, fileSize = type.info?.size,
caption = type.caption, caption = type.caption,
formattedCaption = type.formattedCaption?.body,
mimeType = type.info?.mimetype.orEmpty(), mimeType = type.info?.mimetype.orEmpty(),
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
fileExtension = fileExtensionExtractor.extractFromName(type.filename), fileExtension = fileExtensionExtractor.extractFromName(type.filename),
@ -139,6 +141,7 @@ class EventItemFactory(
filename = type.filename, filename = type.filename,
fileSize = type.info?.size, fileSize = type.info?.size,
caption = type.caption, caption = type.caption,
formattedCaption = type.formattedCaption?.body,
mimeType = type.info?.mimetype.orEmpty(), mimeType = type.info?.mimetype.orEmpty(),
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
fileExtension = fileExtensionExtractor.extractFromName(type.filename), fileExtension = fileExtensionExtractor.extractFromName(type.filename),
@ -160,6 +163,7 @@ class EventItemFactory(
filename = type.filename, filename = type.filename,
fileSize = type.info?.size, fileSize = type.info?.size,
caption = type.caption, caption = type.caption,
formattedCaption = type.formattedCaption?.body,
mimeType = type.info?.mimetype.orEmpty(), mimeType = type.info?.mimetype.orEmpty(),
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
fileExtension = fileExtensionExtractor.extractFromName(type.filename), fileExtension = fileExtensionExtractor.extractFromName(type.filename),
@ -181,6 +185,7 @@ class EventItemFactory(
filename = type.filename, filename = type.filename,
fileSize = type.info?.size, fileSize = type.info?.size,
caption = type.caption, caption = type.caption,
formattedCaption = type.formattedCaption?.body,
mimeType = type.info?.mimetype.orEmpty(), mimeType = type.info?.mimetype.orEmpty(),
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
fileExtension = fileExtensionExtractor.extractFromName(type.filename), fileExtension = fileExtensionExtractor.extractFromName(type.filename),
@ -202,6 +207,7 @@ class EventItemFactory(
filename = type.filename, filename = type.filename,
fileSize = type.info?.size, fileSize = type.info?.size,
caption = type.caption, caption = type.caption,
formattedCaption = type.formattedCaption?.body,
mimeType = type.info?.mimetype.orEmpty(), mimeType = type.info?.mimetype.orEmpty(),
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(), formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
fileExtension = fileExtensionExtractor.extractFromName(type.filename), fileExtension = fileExtensionExtractor.extractFromName(type.filename),

View file

@ -97,6 +97,7 @@ class AndroidLocalMediaFactory(
filename = fileName, filename = fileName,
fileSize = fileSize, fileSize = fileSize,
caption = caption, caption = caption,
formattedCaption = null,
formattedFileSize = calculatedFormattedFileSize, formattedFileSize = calculatedFormattedFileSize,
fileExtension = fileExtension, fileExtension = fileExtension,
senderId = senderId, senderId = senderId,

View file

@ -195,6 +195,86 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
) )
) )
}, },
anImageMediaInfo(
senderName = "Bob",
dateSent = "22 NOV, 2024",
formattedCaption = "This is a <strong>bold</strong> caption",
).let {
aMediaViewerState(
listOf(
aMediaViewerPageData(
downloadedMedia = AsyncData.Success(
LocalMedia(Uri.EMPTY, it)
),
mediaInfo = it,
)
)
)
},
anImageMediaInfo(
senderName = "Charlie",
dateSent = "23 NOV, 2024",
formattedCaption = "This is an <em>italic</em> caption",
).let {
aMediaViewerState(
listOf(
aMediaViewerPageData(
downloadedMedia = AsyncData.Success(
LocalMedia(Uri.EMPTY, it)
),
mediaInfo = it,
)
)
)
},
anImageMediaInfo(
senderName = "Diana",
dateSent = "24 NOV, 2024",
formattedCaption = "This is a <code>code</code> caption",
).let {
aMediaViewerState(
listOf(
aMediaViewerPageData(
downloadedMedia = AsyncData.Success(
LocalMedia(Uri.EMPTY, it)
),
mediaInfo = it,
)
)
)
},
anImageMediaInfo(
senderName = "Eve",
dateSent = "25 NOV, 2024",
formattedCaption = "<blockquote>This is a quote caption</blockquote>",
).let {
aMediaViewerState(
listOf(
aMediaViewerPageData(
downloadedMedia = AsyncData.Success(
LocalMedia(Uri.EMPTY, it)
),
mediaInfo = it,
)
)
)
},
anImageMediaInfo(
senderName = "Frank",
dateSent = "26 NOV, 2024",
formattedCaption = "This caption has <strong>bold</strong>, <em>italic</em>, and <code>code</code> formatting.",
).let {
aMediaViewerState(
listOf(
aMediaViewerPageData(
downloadedMedia = AsyncData.Success(
LocalMedia(Uri.EMPTY, it)
),
mediaInfo = it,
)
)
)
},
) )
} }

View file

@ -31,8 +31,11 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -59,10 +62,12 @@ import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
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 androidx.core.text.toSpannable
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.features.viewfolder.api.TextFileViewer
import io.element.android.libraries.androidutils.text.safeLinkify
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.audio.api.AudioFocus import io.element.android.libraries.audio.api.AudioFocus
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo
@ -91,7 +96,9 @@ import io.element.android.libraries.mediaviewer.impl.local.LocalMediaView
import io.element.android.libraries.mediaviewer.impl.local.PlayableState import io.element.android.libraries.mediaviewer.impl.local.PlayableState
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
import io.element.android.libraries.mediaviewer.impl.util.bgCanvasWithTransparency import io.element.android.libraries.mediaviewer.impl.util.bgCanvasWithTransparency
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.wysiwyg.compose.EditorStyledText
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import me.saket.telephoto.zoomable.OverzoomEffect import me.saket.telephoto.zoomable.OverzoomEffect
import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.ZoomSpec
@ -242,6 +249,7 @@ fun MediaViewerView(
MediaViewerBottomBar( MediaViewerBottomBar(
showDivider = dataForPage.mediaInfo.mimeType.isMimeTypeVideo(), showDivider = dataForPage.mediaInfo.mimeType.isMimeTypeVideo(),
caption = dataForPage.mediaInfo.caption, caption = dataForPage.mediaInfo.caption,
formattedCaption = dataForPage.mediaInfo.formattedCaption,
onHeightChange = { bottomPaddingInPixels = it }, onHeightChange = { bottomPaddingInPixels = it },
) )
} }
@ -545,6 +553,7 @@ private fun MediaViewerTopBar(
@Composable @Composable
private fun MediaViewerBottomBar( private fun MediaViewerBottomBar(
caption: String?, caption: String?,
formattedCaption: CharSequence?,
showDivider: Boolean, showDivider: Boolean,
onHeightChange: (Int) -> Unit, onHeightChange: (Int) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -557,7 +566,7 @@ private fun MediaViewerBottomBar(
onHeightChange(it.height) onHeightChange(it.height)
}, },
) { ) {
if (caption != null) { if (caption != null || formattedCaption != null) {
if (showDivider) { if (showDivider) {
HorizontalDivider() HorizontalDivider()
} }
@ -568,15 +577,28 @@ private fun MediaViewerBottomBar(
.fillMaxWidth() .fillMaxWidth()
.heightIn(max = if (hasCompactHeightWindowSize()) maxCaptionHeightLandscape else maxCaptionHeightPortrait), .heightIn(max = if (hasCompactHeightWindowSize()) maxCaptionHeightLandscape else maxCaptionHeightPortrait),
) { ) {
Text( val textToRender = when {
modifier = Modifier formattedCaption != null -> formattedCaption
.fillMaxWidth() caption != null -> caption.safeLinkify().toSpannable()
.padding(16.dp) else -> null
.verticalScroll(scrollState) }
.navigationBarsPadding(), if (textToRender != null) {
text = caption, CompositionLocalProvider(
style = ElementTheme.typography.fontBodyLgRegular, LocalContentColor provides ElementTheme.colors.textPrimary,
) LocalTextStyle provides ElementTheme.typography.fontBodyLgRegular
) {
EditorStyledText(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.verticalScroll(scrollState)
.navigationBarsPadding(),
text = textToRender,
style = ElementRichTextEditorStyle.textStyle(),
releaseOnDetach = false,
)
}
}
if (showBottomShadow) { if (showBottomShadow) {
Box( Box(
modifier = Modifier modifier = Modifier

View file

@ -235,6 +235,7 @@ class TimelineMediaGalleryDataSourceTest {
filename = "body.jpg", filename = "body.jpg",
fileSize = 888L, fileSize = 888L,
caption = "body.jpg caption", caption = "body.jpg caption",
formattedCaption = "formatted",
mimeType = MimeTypes.Jpeg, mimeType = MimeTypes.Jpeg,
formattedFileSize = "888 Bytes", formattedFileSize = "888 Bytes",
fileExtension = "jpg", fileExtension = "jpg",

View file

@ -41,6 +41,7 @@ class FakeLocalMediaFactory(
filename = safeName, filename = safeName,
fileSize = null, fileSize = null,
caption = null, caption = null,
formattedCaption = null,
mimeType = mimeType ?: fallbackMimeType, mimeType = mimeType ?: fallbackMimeType,
formattedFileSize = formattedFileSize ?: fallbackFileSize, formattedFileSize = formattedFileSize ?: fallbackFileSize,
fileExtension = fileExtensionExtractor.extractFromName(safeName), fileExtension = fileExtensionExtractor.extractFromName(safeName),

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5aa3bca6cd248ac4725fb35aa11a465029dc534b8b167aedbf3e9bc240577e9c oid sha256:4e8ce597c240a7e72b6811537b6fd24e1bd38714db0ce074377ab3f16eaf0436
size 654171 size 656958

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:46d390cfe8d41536cea5e90cb38aa547969ae24d82027c02cfb71c4fbc780247
size 667948

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0bd4f0133fc3a4a159e3eda1f715e4140464b7c67558c551216546d4688f21bc
size 669143

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:178de2176a3932665897486ecbee622083af2d5939f4f1f7f0bdc4667a61e36a
size 668061

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bc974dfc6206f389153477d287a69c401ddc154a529cb892227a743acee8ac50
size 668212

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e7ac632f2062aa4a13ee46fd2fef6d9e6be25270f4790247131231163670cbe6
size 670668

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:bd5b76aefb0fde0605556e78c7286bf8ee8dd465def0e3095ebc97ce3427eafa oid sha256:21b8289db279172f042fa902bdbdb70d87104d342a1140357bb1ac2bc18f6590
size 666239 size 668482

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f6870b9c1a5aad4257aa4bd7b13d0be5bea281778061d71c711495aadfaafdb8 oid sha256:f72964447f2fa66e8df38fcff4a84ada1cf62e1646a2e8a00c61f8f151abfa8c
size 206057 size 206507

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6bddbe3b0e66e0a2ce49b39321057c94427933de81663f680237a398e9929ba3 oid sha256:2adfc04e0f7999ba861bf931b853f068067387558e81464e99caba1c2445a3a7
size 442729 size 444811

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0487a6dbc3a2f3bfb79a709c782fb692ef93f37607b4483095ce76f08954580e
size 396582

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:29833e4103ecf4bd3e1c5da384d3bcc20031073a14261971825e97bc9f1dad23
size 397593

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b544362587868c37edc424b675c6758e7ffcff359fa12c1cfcb874a0502e25de
size 397284

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:51e183ca6d518d803471ad4eed0ee932397c32a4c1ffb064714b39f13ea2003f
size 396825

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7b6623e0e784a93d6a3aa220b620efe7d1f9e040aedac7f34d01b24262fe101b
size 401770

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3bd4a96daaa24c01b7d0007fdd14460dc80c65c13d0ccc7a887b04fd90e9fa99 oid sha256:d56f08f1c37575de90a67ebfeadaf1c891c9ac9e4925af7d048922df9fec5aa9
size 396805 size 396815

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c0249f33aca3e50713c87a3826e71985991f0996998132c42a374c6169800023 oid sha256:e3a148746b4f7428688a634f2b1299f0393bf669a665c94fa9af12d7839e72a0
size 130728 size 130834