From dcb9bcd11e73e457af9580e77a7eb51302737e6b Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Jun 2023 22:50:01 +0200 Subject: [PATCH 1/7] Timeline: fix unknown aspectRatio --- .../components/event/TimelineItemAspectRatioBox.kt | 11 ++++++----- .../components/event/TimelineItemImageView.kt | 7 +------ .../event/TimelineItemContentMessageFactory.kt | 4 ++-- .../timeline/model/event/TimelineItemImageContent.kt | 2 +- .../timeline/model/event/TimelineItemVideoContent.kt | 2 +- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt index 043019cc7f..b1240f9236 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt @@ -24,22 +24,23 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import kotlin.math.min @Composable fun TimelineItemAspectRatioBox( height: Int?, - aspectRatio: Float, + aspectRatio: Float?, modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.TopStart, content: @Composable BoxScope.() -> Unit, ) { - // TODO should probably be moved to an ElementTheme.dimensions - val maxHeight = min(300, height ?: 0) + val maxHeight = minOf(300, maxOf(100, height ?: Int.MAX_VALUE)) + val aspectRatioModifier = aspectRatio?.let { + Modifier.aspectRatio(it) + } ?: Modifier Box( modifier = modifier .heightIn(max = maxHeight.dp) - .aspectRatio(aspectRatio, matchHeightConstraintsFirst = true), + .then(aspectRatioModifier), contentAlignment = contentAlignment, content = content ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt index f85f9d58e0..f6c1c069b9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt @@ -16,7 +16,6 @@ package io.element.android.features.messages.impl.timeline.components.event -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -28,24 +27,20 @@ import io.element.android.libraries.designsystem.components.BlurHashAsyncImage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.matrix.ui.media.MediaRequestData -import kotlin.math.max @Composable fun TimelineItemImageView( content: TimelineItemImageContent, modifier: Modifier = Modifier, ) { - // TODO place this value somewhere else? - val minHeight = max(100, content.height ?: 0) TimelineItemAspectRatioBox( - height = minHeight, + height = content.height, aspectRatio = content.aspectRatio, modifier = modifier ) { BlurHashAsyncImage( model = MediaRequestData(content.preferredMediaSource, MediaRequestData.Kind.File(content.body, content.mimeType)), blurHash = content.blurhash, - modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Fit, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 0e407003e8..18250a05a7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -102,11 +102,11 @@ class TimelineItemContentMessageFactory @Inject constructor( } } - private fun aspectRatioOf(width: Long?, height: Long?): Float { + private fun aspectRatioOf(width: Long?, height: Long?): Float? { return if (height != null && width != null) { width.toFloat() / height.toFloat() } else { - 0.7f + null } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt index 286d3412f1..342e0a336b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt @@ -29,7 +29,7 @@ data class TimelineItemImageContent( val blurhash: String?, val width: Int?, val height: Int?, - val aspectRatio: Float + val aspectRatio: Float? ) : TimelineItemEventContent { override val type: String = "TimelineItemImageContent" diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt index a76be8c13c..14ec0a972d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt @@ -23,7 +23,7 @@ data class TimelineItemVideoContent( val duration: Long, val videoSource: MediaSource, val thumbnailSource: MediaSource?, - val aspectRatio: Float, + val aspectRatio: Float?, val blurHash: String?, val height: Int?, val width: Int?, From b6e7228ce049f814acf1a58a09a6dc133f5d9498 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 16 Jun 2023 13:52:07 +0200 Subject: [PATCH 2/7] Fix MediaPreProcessor for images/videos sent as file --- .../MessageComposerPresenterTest.kt | 8 +- .../media/MediaMetaDataRetriever.kt | 6 +- .../libraries/mediaupload/api/MediaSender.kt | 17 +- .../mediaupload/api/MediaUploadInfo.kt | 11 +- .../mediaupload/AndroidMediaPreProcessor.kt | 219 ++++++++---------- .../libraries/mediaupload/ImageCompressor.kt | 15 +- .../libraries/mediaupload/ThumbnailFactory.kt | 122 ++++++++++ 7 files changed, 251 insertions(+), 147 deletions(-) create mode 100644 libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index 8fc9b702bc..f646e1efb8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -50,7 +50,7 @@ import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaUploadInfo -import io.element.android.libraries.mediaupload.api.ThumbnailProcessingInfo +import io.element.android.libraries.mediaupload.api.ThumbnailResult import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode import io.mockk.mockk @@ -301,7 +301,7 @@ class MessageComposerPresenterTest { thumbnailSource = null, blurhash = null, ), - thumbnailInfo = ThumbnailProcessingInfo( + thumbnailInfo = ThumbnailResult( file = File("/some/path"), info = ThumbnailInfo( width = null, @@ -309,7 +309,6 @@ class MessageComposerPresenterTest { mimetype = null, size = null, ), - blurhash = "", ) ) ) @@ -344,7 +343,7 @@ class MessageComposerPresenterTest { thumbnailSource = null, blurhash = null, ), - thumbnailInfo = ThumbnailProcessingInfo( + thumbnailInfo = ThumbnailResult( file = File("/some/path"), info = ThumbnailInfo( width = null, @@ -352,7 +351,6 @@ class MessageComposerPresenterTest { mimetype = null, size = null, ), - blurhash = "", ) ) ) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt index 3b29787285..8f942957e0 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt @@ -20,5 +20,9 @@ import android.media.MediaMetadataRetriever /** [MediaMetadataRetriever] only implements `AutoClosable` since API 29, so we need to execute this to have the same in older APIs. */ inline fun MediaMetadataRetriever.runAndRelease(block: MediaMetadataRetriever.() -> T): T { - return block().also { release() } + return try { + block() + } finally { + release() + } } diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index 585670d939..5de8239e38 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -44,15 +44,26 @@ class MediaSender @Inject constructor( ): Result { return when (info) { is MediaUploadInfo.Image -> { - sendImage(info.file, info.thumbnailInfo.file, info.info) + sendImage( + file = info.file, + thumbnailFile = info.thumbnailFile, + imageInfo = info.info + ) } is MediaUploadInfo.Video -> { - sendVideo(info.file, info.thumbnailInfo.file, info.info) + sendVideo( + file = info.file, + thumbnailFile = info.thumbnailFile, + videoInfo = info.info + ) } is MediaUploadInfo.AnyFile -> { - sendFile(info.file, info.info) + sendFile( + file = info.file, + fileInfo = info.info + ) } else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $info")) } diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt index 47fa26ae79..5da3d36c44 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt @@ -19,7 +19,6 @@ package io.element.android.libraries.mediaupload.api import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo -import io.element.android.libraries.matrix.api.media.ThumbnailInfo import io.element.android.libraries.matrix.api.media.VideoInfo import java.io.File @@ -27,14 +26,8 @@ sealed interface MediaUploadInfo { val file: File - data class Image(override val file: File, val info: ImageInfo, val thumbnailInfo: ThumbnailProcessingInfo) : MediaUploadInfo - data class Video(override val file: File, val info: VideoInfo, val thumbnailInfo: ThumbnailProcessingInfo) : MediaUploadInfo + data class Image(override val file: File, val info: ImageInfo, val thumbnailFile: File) : MediaUploadInfo + data class Video(override val file: File, val info: VideoInfo, val thumbnailFile: File) : MediaUploadInfo data class Audio(override val file: File, val info: AudioInfo) : MediaUploadInfo data class AnyFile(override val file: File, val info: FileInfo) : MediaUploadInfo } - -data class ThumbnailProcessingInfo( - val file: File, - val info: ThumbnailInfo, - val blurhash: String, -) diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt index 4882000307..dc56846b87 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt @@ -17,7 +17,7 @@ package io.element.android.libraries.mediaupload import android.content.Context -import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.media.MediaMetadataRetriever import android.net.Uri import androidx.exifinterface.media.ExifInterface @@ -37,26 +37,21 @@ import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo -import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.media.ThumbnailInfo import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaUploadInfo -import io.element.android.libraries.mediaupload.api.ThumbnailProcessingInfo import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream import java.io.File import java.io.InputStream import javax.inject.Inject -import kotlin.time.Duration.Companion.seconds @ContributesBinding(AppScope::class) class AndroidMediaPreProcessor @Inject constructor( @ApplicationContext private val context: Context, + private val thumbnailFactory: ThumbnailFactory, private val imageCompressor: ImageCompressor, private val videoCompressor: VideoCompressor, private val coroutineDispatchers: CoroutineDispatchers, @@ -69,23 +64,6 @@ class AndroidMediaPreProcessor @Inject constructor( * values may surpass this limit. (i.e.: an image of `480x3000px` would have `inSampleSize=1` and be sent as is). */ private const val IMAGE_SCALE_REF_SIZE = 640 - - /** - * Max width of thumbnail images. - * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/?ref=blog.gitter.im#thumbnails). - */ - private const val THUMB_MAX_WIDTH = 800 - - /** - * Max height of thumbnail images. - * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/?ref=blog.gitter.im#thumbnails). - */ - private const val THUMB_MAX_HEIGHT = 600 - - /** - * Frame of the video to be used for generating a thumbnail. - */ - private val VIDEO_THUMB_FRAME = 5.seconds.inWholeMicroseconds } private val contentResolver = context.contentResolver @@ -95,40 +73,34 @@ class AndroidMediaPreProcessor @Inject constructor( mimeType: String, deleteOriginal: Boolean, compressIfPossible: Boolean, - ): Result = runCatching { - val shouldBeCompressed = compressIfPossible && - (mimeType.isMimeTypeImage() && mimeType != MimeTypes.Gif) || - mimeType.isMimeTypeVideo() - - val result = if (shouldBeCompressed) { - when { - mimeType.isMimeTypeImage() -> processImage(uri) - mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType) + ): Result = withContext(coroutineDispatchers.computation) { + runCatching { + val result = when { + mimeType.isMimeTypeImage() -> processImage(uri, mimeType, compressIfPossible && mimeType != MimeTypes.Gif) + mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, compressIfPossible) mimeType.isMimeTypeAudio() -> processAudio(uri, mimeType) - else -> error("Cannot compress file of type: $mimeType") + else -> processFile(uri, mimeType) } - } else { - val file = copyToTmpFile(uri) - // Remove image metadata here too - if (mimeType.isMimeTypeImage() && mimeType != MimeTypes.Gif) { - removeSensitiveImageMetadata(file) + if (deleteOriginal) { + tryOrNull { + contentResolver.delete(uri, null, null) + } } - val info = FileInfo( - mimetype = mimeType, - size = file.length(), - thumbnailInfo = null, - thumbnailSource = null, - ) - MediaUploadInfo.AnyFile(file, info) + result.postProcess(uri) } - if (deleteOriginal) { - tryOrNull { - contentResolver.delete(uri, null, null) - } - } - result.postProcess(uri) }.mapFailure { MediaPreProcessor.Failure(it) } + private suspend fun processFile(uri: Uri, mimeType: String): MediaUploadInfo { + val file = copyToTmpFile(uri) + val info = FileInfo( + mimetype = mimeType, + size = file.length(), + thumbnailInfo = null, + thumbnailSource = null, + ) + return MediaUploadInfo.AnyFile(file, info) + } + private fun MediaUploadInfo.postProcess(uri: Uri): MediaUploadInfo { val name = context.getFileName(uri) ?: return this val renamedFile = File(context.cacheDir, name).also { @@ -142,40 +114,83 @@ class AndroidMediaPreProcessor @Inject constructor( } } - private suspend fun processImage(uri: Uri): MediaUploadInfo { - val compressedFileResult = contentResolver.openInputStream(uri).use { input -> - imageCompressor.compressToTmpFile( - inputStream = requireNotNull(input), - resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE), - ).getOrThrow() + private suspend fun processImage(uri: Uri, mimeType: String, shouldBeCompressed: Boolean): MediaUploadInfo { + + suspend fun processImageWithCompression(): MediaUploadInfo { + val compressionResult = contentResolver.openInputStream(uri).use { input -> + imageCompressor.compressToTmpFile( + inputStream = requireNotNull(input), + resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE), + ).getOrThrow() + } + val thumbnailResult: ThumbnailResult = thumbnailFactory.createImageThumbnail(compressionResult.file) + val imageInfo = compressionResult.toImageInfo( + mimeType = mimeType, + thumbnailResult = thumbnailResult + ) + removeSensitiveImageMetadata(compressionResult.file) + return MediaUploadInfo.Image( + file = compressionResult.file, + info = imageInfo, + thumbnailFile = thumbnailResult.file + ) } - removeSensitiveImageMetadata(compressedFileResult.file) + suspend fun processImageWithoutCompression(): MediaUploadInfo { + val file = copyToTmpFile(uri) + val thumbnailResult: ThumbnailResult = thumbnailFactory.createImageThumbnail(file) + val imageInfo = contentResolver.openInputStream(uri).use { input -> + val bitmap = BitmapFactory.decodeStream(input, null, null)!! + ImageInfo( + width = bitmap.width.toLong(), + height = bitmap.height.toLong(), + mimetype = mimeType, + size = file.length(), + thumbnailInfo = thumbnailResult.info, + thumbnailSource = null, + blurhash = thumbnailResult.blurhash, + ) + } + removeSensitiveImageMetadata(file) + return MediaUploadInfo.Image( + file = file, + info = imageInfo, + thumbnailFile = thumbnailResult.file + ) + } - val thumbnailResult = compressedFileResult.file.inputStream().use { generateImageThumbnail(it) } - val processingResult = compressedFileResult.toImageInfo(MimeTypes.Jpeg, thumbnailResult.file.path, thumbnailResult.info) - return MediaUploadInfo.Image(compressedFileResult.file, processingResult, thumbnailResult) + return if (shouldBeCompressed) { + processImageWithCompression() + } else { + processImageWithoutCompression() + } } - private suspend fun processVideo(uri: Uri, mimeType: String?): MediaUploadInfo { - val thumbnailInfo = extractVideoThumbnail(uri) - val resultFile = videoCompressor.compress(uri) - .onEach { - // TODO handle progress - } - .filterIsInstance() - .first() - .file - - val videoProcessingInfo = extractVideoMetadata(resultFile, mimeType, thumbnailInfo.file.path, thumbnailInfo) - return MediaUploadInfo.Video(resultFile, videoProcessingInfo, thumbnailInfo) + private suspend fun processVideo(uri: Uri, mimeType: String?, shouldBeCompressed: Boolean): MediaUploadInfo { + val resultFile = if (shouldBeCompressed) { + videoCompressor.compress(uri) + .onEach { + // TODO handle progress + } + .filterIsInstance() + .first() + .file + } else { + copyToTmpFile(uri) + } + val thumbnailInfo = thumbnailFactory.createVideoThumbnail(resultFile) + val videoInfo = extractVideoMetadata(resultFile, mimeType, thumbnailInfo) + return MediaUploadInfo.Video( + file = resultFile, + info = videoInfo, + thumbnailFile = thumbnailInfo.file + ) } private suspend fun processAudio(uri: Uri, mimeType: String?): MediaUploadInfo { val file = copyToTmpFile(uri) return MediaMetadataRetriever().runAndRelease { setDataSource(context, Uri.fromFile(file)) - val info = AudioInfo( duration = extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L, size = file.length(), @@ -186,15 +201,6 @@ class AndroidMediaPreProcessor @Inject constructor( } } - private suspend fun generateImageThumbnail(inputStream: InputStream): ThumbnailProcessingInfo { - val thumbnailResult = imageCompressor - .compressToTmpFile( - inputStream = inputStream, - resizeMode = ResizeMode.Strict(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT), - ).getOrThrow() - return thumbnailResult.toThumbnailProcessingInfo(MimeTypes.Jpeg) - } - private fun removeSensitiveImageMetadata(file: File) { // Remove GPS info, user comments and subject location tags val exifInterface = ExifInterface(file) @@ -215,7 +221,7 @@ class AndroidMediaPreProcessor @Inject constructor( } } - private fun extractVideoMetadata(file: File, mimeType: String?, thumbnailUrl: String?, thumbnailInfo: ThumbnailProcessingInfo?): VideoInfo = + private fun extractVideoMetadata(file: File, mimeType: String?, thumbnailResult: ThumbnailResult): VideoInfo = MediaMetadataRetriever().runAndRelease { setDataSource(context, Uri.fromFile(file)) VideoInfo( @@ -224,51 +230,26 @@ class AndroidMediaPreProcessor @Inject constructor( height = extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toLong() ?: 0L, mimetype = mimeType, size = file.length(), - thumbnailInfo = thumbnailInfo?.info, - thumbnailSource = thumbnailUrl?.let { MediaSource(it) }, - blurhash = thumbnailInfo?.blurhash, + thumbnailInfo = thumbnailResult.info, + // Will be computed by the rust sdk + thumbnailSource = null, + blurhash = thumbnailResult.blurhash, ) } - private suspend fun extractVideoThumbnail(uri: Uri): ThumbnailProcessingInfo = - MediaMetadataRetriever().runAndRelease { - setDataSource(context, uri) - val bitmap = requireNotNull(getFrameAtTime(VIDEO_THUMB_FRAME)) - val inputStream = ByteArrayOutputStream().use { - bitmap.compress(Bitmap.CompressFormat.JPEG, 80, it) - ByteArrayInputStream(it.toByteArray()) - } - - val result = imageCompressor.compressToTmpFile( - inputStream = inputStream, - resizeMode = ResizeMode.Strict(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT), - ) - result.getOrThrow().toThumbnailProcessingInfo(MimeTypes.Jpeg) - } - private suspend fun copyToTmpFile(uri: Uri): File { return contentResolver.openInputStream(uri)?.use { createTmpFileWithInput(it) } ?: error("Could not copy the contents of $uri to a temporary file") } } -fun ImageCompressionResult.toImageInfo(mimeType: String, thumbnailUrl: String?, thumbnailInfo: ThumbnailInfo?) = ImageInfo( +fun ImageCompressionResult.toImageInfo(mimeType: String, thumbnailResult: ThumbnailResult) = ImageInfo( width = width.toLong(), height = height.toLong(), mimetype = mimeType, size = size, - thumbnailInfo = thumbnailInfo, - thumbnailSource = thumbnailUrl?.let { MediaSource(it) }, - blurhash = blurhash, -) - -fun ImageCompressionResult.toThumbnailProcessingInfo(mimeType: String) = ThumbnailProcessingInfo( - file = file, - info = ThumbnailInfo( - width = width.toLong(), - height = height.toLong(), - mimetype = mimeType, - size = size, - ), - blurhash = blurhash, + thumbnailInfo = thumbnailResult.info, + // Will be computed by the rust sdk + thumbnailSource = null, + blurhash = thumbnailResult.blurhash, ) diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt index 96d5e2ea63..2b8669fe42 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt @@ -42,27 +42,23 @@ class ImageCompressor @Inject constructor( * @return a [Result] containing the resulting [ImageCompressionResult] with the temporary [File] and some metadata. */ suspend fun compressToTmpFile( - inputStream: InputStream, - resizeMode: ResizeMode, - format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, - desiredQuality: Int = 80, + inputStream: InputStream, + resizeMode: ResizeMode, + format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, + desiredQuality: Int = 80, ): Result = withContext(Dispatchers.IO) { runCatching { val compressedBitmap = compressToBitmap(inputStream, resizeMode).getOrThrow() - val blurhash = BlurHash.encode(compressedBitmap, 3, 3) - // Encode bitmap to the destination temporary file val tmpFile = context.createTmpFile(extension = "jpeg") tmpFile.outputStream().use { compressedBitmap.compress(format, desiredQuality, it) } - ImageCompressionResult( file = tmpFile, width = compressedBitmap.width, height = compressedBitmap.height, - size = tmpFile.length(), - blurhash = blurhash + size = tmpFile.length() ) } } @@ -116,7 +112,6 @@ data class ImageCompressionResult( val width: Int, val height: Int, val size: Long, - val blurhash: String, ) sealed interface ResizeMode { diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt new file mode 100644 index 0000000000..f0010cf9a7 --- /dev/null +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt @@ -0,0 +1,122 @@ +/* + * 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.libraries.mediaupload + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.media.MediaMetadataRetriever +import android.media.ThumbnailUtils +import android.os.Build +import android.os.CancellationSignal +import android.provider.MediaStore +import android.util.Size +import androidx.core.net.toUri +import com.vanniktech.blurhash.BlurHash +import io.element.android.libraries.androidutils.file.createTmpFile +import io.element.android.libraries.androidutils.media.runAndRelease +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.media.ThumbnailInfo +import kotlinx.coroutines.suspendCancellableCoroutine +import java.io.File +import javax.inject.Inject +import kotlin.coroutines.resume + +/** + * Max width of thumbnail images. + * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/?ref=blog.gitter.im#thumbnails). + */ +private const val THUMB_MAX_WIDTH = 800 + +/** + * Max height of thumbnail images. + * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/?ref=blog.gitter.im#thumbnails). + */ +private const val THUMB_MAX_HEIGHT = 600 + +/** + * Frame of the video to be used for generating a thumbnail. + */ +private const val VIDEO_THUMB_FRAME = 0L + +class ThumbnailFactory @Inject constructor( + @ApplicationContext private val context: Context, +) { + + @SuppressLint("NewApi") + suspend fun createImageThumbnail(file: File): ThumbnailResult { + return createThumbnail { cancellationSignal -> + // This API works correctly with GIF + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ThumbnailUtils.createImageThumbnail( + file, + Size(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT), + cancellationSignal + ) + } else { + ThumbnailUtils.createImageThumbnail( + file.path, + MediaStore.Images.Thumbnails.MINI_KIND, + ) + } + } + } + + suspend fun createVideoThumbnail(file: File): ThumbnailResult { + return createThumbnail { + MediaMetadataRetriever().runAndRelease { + setDataSource(context, file.toUri()) + getFrameAtTime(VIDEO_THUMB_FRAME) + } + } + } + + private suspend fun createThumbnail(bitmapThumbnailFactory: (CancellationSignal) -> Bitmap?): ThumbnailResult = suspendCancellableCoroutine { continuation -> + val cancellationSignal = CancellationSignal() + continuation.invokeOnCancellation { + cancellationSignal.cancel() + } + val bitmapThumbnail: Bitmap? = bitmapThumbnailFactory(cancellationSignal) + val thumbnailFile = context.createTmpFile(extension = "jpeg") + thumbnailFile.outputStream().use { outputStream -> + bitmapThumbnail?.compress(Bitmap.CompressFormat.JPEG, 80, outputStream) + } + val blurhash = bitmapThumbnail?.let { + BlurHash.encode(it, 3, 3) + } + val thumbnailResult = ThumbnailResult( + file = thumbnailFile, + info = ThumbnailInfo( + height = bitmapThumbnail?.height?.toLong(), + width = bitmapThumbnail?.width?.toLong(), + mimetype = MimeTypes.Jpeg, + size = thumbnailFile.length() + ), + blurhash = blurhash + ) + bitmapThumbnail?.recycle() + continuation.resume(thumbnailResult) + + } +} + +data class ThumbnailResult( + val file: File, + val info: ThumbnailInfo, + val blurhash: String?, +) From 551a97ac2e4d6bc7cb18358a13d9e4edab2bea61 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 16 Jun 2023 13:59:59 +0200 Subject: [PATCH 3/7] Media: fix detekt --- .../element/android/libraries/mediaupload/ThumbnailFactory.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt index f0010cf9a7..a9ed6319cb 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt @@ -86,12 +86,12 @@ class ThumbnailFactory @Inject constructor( } } - private suspend fun createThumbnail(bitmapThumbnailFactory: (CancellationSignal) -> Bitmap?): ThumbnailResult = suspendCancellableCoroutine { continuation -> + private suspend fun createThumbnail(bitmapFactory: (CancellationSignal) -> Bitmap?): ThumbnailResult = suspendCancellableCoroutine { continuation -> val cancellationSignal = CancellationSignal() continuation.invokeOnCancellation { cancellationSignal.cancel() } - val bitmapThumbnail: Bitmap? = bitmapThumbnailFactory(cancellationSignal) + val bitmapThumbnail: Bitmap? = bitmapFactory(cancellationSignal) val thumbnailFile = context.createTmpFile(extension = "jpeg") thumbnailFile.outputStream().use { outputStream -> bitmapThumbnail?.compress(Bitmap.CompressFormat.JPEG, 80, outputStream) From 7f8250106c022b2cf321c699f7bd2402defdf187 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 16 Jun 2023 14:23:52 +0200 Subject: [PATCH 4/7] Media : Fix test compilation --- .../MessageComposerPresenterTest.kt | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index f646e1efb8..7e0cbeeba3 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -36,7 +36,6 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.media.ImageInfo -import io.element.android.libraries.matrix.api.media.ThumbnailInfo import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE @@ -50,7 +49,6 @@ import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaUploadInfo -import io.element.android.libraries.mediaupload.api.ThumbnailResult import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode import io.mockk.mockk @@ -301,15 +299,7 @@ class MessageComposerPresenterTest { thumbnailSource = null, blurhash = null, ), - thumbnailInfo = ThumbnailResult( - file = File("/some/path"), - info = ThumbnailInfo( - width = null, - height = null, - mimetype = null, - size = null, - ), - ) + thumbnailFile = File("/some/path") ) ) ) @@ -343,15 +333,7 @@ class MessageComposerPresenterTest { thumbnailSource = null, blurhash = null, ), - thumbnailInfo = ThumbnailResult( - file = File("/some/path"), - info = ThumbnailInfo( - width = null, - height = null, - mimetype = null, - size = null, - ), - ) + thumbnailFile = File("/some/path") ) ) ) From 07a2d309c45818f1dbccd6fde4c7729359b5b12f Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 19 Jun 2023 21:38:54 +0200 Subject: [PATCH 5/7] Timeline media: kind of align with other messenger apps --- .../event/TimelineItemAspectRatioBox.kt | 17 +++++++++-------- .../components/event/TimelineItemImageView.kt | 3 +-- .../components/event/TimelineItemVideoView.kt | 4 +--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt index b1240f9236..f733913bc8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt @@ -25,22 +25,23 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +private const val MAX_HEIGHT_IN_DP = 360f +private const val MIN_ASPECT_RATIO = 0.6f +private const val MAX_ASPECT_RATIO = 4f +private const val DEFAULT_ASPECT_RATIO = 1.33f + @Composable fun TimelineItemAspectRatioBox( - height: Int?, aspectRatio: Float?, modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.TopStart, - content: @Composable BoxScope.() -> Unit, + content: @Composable (BoxScope.() -> Unit), ) { - val maxHeight = minOf(300, maxOf(100, height ?: Int.MAX_VALUE)) - val aspectRatioModifier = aspectRatio?.let { - Modifier.aspectRatio(it) - } ?: Modifier + val safeAspectRatio = (aspectRatio ?: DEFAULT_ASPECT_RATIO).coerceIn(MIN_ASPECT_RATIO, MAX_ASPECT_RATIO) Box( modifier = modifier - .heightIn(max = maxHeight.dp) - .then(aspectRatioModifier), + .heightIn(max = MAX_HEIGHT_IN_DP.dp) + .aspectRatio(safeAspectRatio, true), contentAlignment = contentAlignment, content = content ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt index f6c1c069b9..6c7b51ddfa 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt @@ -34,14 +34,13 @@ fun TimelineItemImageView( modifier: Modifier = Modifier, ) { TimelineItemAspectRatioBox( - height = content.height, aspectRatio = content.aspectRatio, modifier = modifier ) { BlurHashAsyncImage( model = MediaRequestData(content.preferredMediaSource, MediaRequestData.Kind.File(content.body, content.mimeType)), blurHash = content.blurhash, - contentScale = ContentScale.Fit, + contentScale = ContentScale.Crop, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt index f3bd4129d1..aeb2e7145e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt @@ -43,7 +43,6 @@ fun TimelineItemVideoView( modifier: Modifier = Modifier, ) { TimelineItemAspectRatioBox( - height = content.height, aspectRatio = content.aspectRatio, modifier = modifier, contentAlignment = Alignment.Center, @@ -51,8 +50,7 @@ fun TimelineItemVideoView( BlurHashAsyncImage( model = MediaRequestData(content.thumbnailSource, MediaRequestData.Kind.File(content.body, content.mimeType)), blurHash = content.blurHash, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Fit, + contentScale = ContentScale.Crop, ) Box( modifier = Modifier.roundedBackground(), From 11ccd8d35c0a5346fba73091406d22827711899d Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 20 Jun 2023 08:56:35 +0000 Subject: [PATCH 6/7] Update screenshots --- ...elineItemImageViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...elineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...lineItemImageViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...lineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...elineItemVideoViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...elineItemVideoViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...lineItemVideoViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...lineItemVideoViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index b1badf5a3f..affcb49660 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa17dd70b3c5eaaa37fbc5eed53997dd627090d9a5a3ffa518b9688647449a8e -size 99065 +oid sha256:c40273c36eb1479f284e75fa91d4a75b1ae97edd0242dda37a2d4a8f10394928 +size 138700 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index f5ff66af17..cb4b57910b 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4593d265265cea31c20b545250d20df54dfcc877d6e54eae28d1e84a1c693f16 -size 147443 +oid sha256:d7c47c713c74766c39d3b349d9e421ea588261e043105a5475cd8e68af782806 +size 185325 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index b1badf5a3f..affcb49660 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa17dd70b3c5eaaa37fbc5eed53997dd627090d9a5a3ffa518b9688647449a8e -size 99065 +oid sha256:c40273c36eb1479f284e75fa91d4a75b1ae97edd0242dda37a2d4a8f10394928 +size 138700 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index f5ff66af17..cb4b57910b 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4593d265265cea31c20b545250d20df54dfcc877d6e54eae28d1e84a1c693f16 -size 147443 +oid sha256:d7c47c713c74766c39d3b349d9e421ea588261e043105a5475cd8e68af782806 +size 185325 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 7407445780..5361cd1a24 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4e329e21d49bd79b633913edbbc1d5c63024874d866d96d399b1a8ffa5c1f18 -size 99209 +oid sha256:06617cda0f93ce0ea26b2a77a243371cdaf4f9a2956d8159ecb7196f7f6fe082 +size 139282 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index c803700190..0e14867b04 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5eb1c967db078a0333ef3fe26f94692264d0158d74d78768df32cc7c641faee3 -size 147537 +oid sha256:55d2d11ba729a68b1b62e9a69c3308594839fdb3612ff3db59850f0b346c28da +size 186118 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 7407445780..5361cd1a24 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4e329e21d49bd79b633913edbbc1d5c63024874d866d96d399b1a8ffa5c1f18 -size 99209 +oid sha256:06617cda0f93ce0ea26b2a77a243371cdaf4f9a2956d8159ecb7196f7f6fe082 +size 139282 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index c803700190..0e14867b04 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemVideoViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5eb1c967db078a0333ef3fe26f94692264d0158d74d78768df32cc7c641faee3 -size 147537 +oid sha256:55d2d11ba729a68b1b62e9a69c3308594839fdb3612ff3db59850f0b346c28da +size 186118 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 8ccc3512a1..2ae60ffbaf 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed788e9f85e922a81e077c2ad82b89fa822d9dec6d95442ad5aa5365535a87b9 -size 193815 +oid sha256:ead933d9b666f988a1c30997919ab364dacc4602c0cc62c03b14c63bd1b978d7 +size 226100 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 0d0752eeb4..2cdede3cc8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:169f1a6dd0d433a900c43867a3c2feacd3143fc8e58029d4f24eeb4e5a41d2f2 -size 193901 +oid sha256:2d753eeb5d8e83643f858e5190cde015321e32959111f6a45e70a60f790e94c8 +size 226976 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 9327685441..6351229d4c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8943194bce27da10d2f047e5617cbb45548170cc1f8c9606e62dae20fde44f5 -size 194104 +oid sha256:7647550e41280854b7dd68110250d8f217acc8bef70a25ca0ebdf28994802d63 +size 225809 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index 373800dd3b..2c51bbc761 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc4c738cf6e3c8961d0e97b8adc24d0ba8765cd8a8d1799b9783a5b1f098fc25 -size 194152 +oid sha256:35150cf00f6e4d899417ff5f456e8fb9168424a75def962f4df40155d3819104 +size 226734 From 51cb334185485efdc7aec03502768784bd0ed3c9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 20 Jun 2023 15:59:04 +0200 Subject: [PATCH 7/7] Screenshots