diff --git a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt index ee8d745a0dae650654833125ddc2a2fd47eca1ff..3d7a701b626674fa3c63281660f6325f24c417e6 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt +++ b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt @@ -9,16 +9,16 @@ import android.widget.TextView import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.RecyclerView import org.futo.circles.core.extensions.gone -import org.futo.circles.core.extensions.loadEncryptedIntoWithAspect +import org.futo.circles.core.extensions.loadEncryptedThumbOrFullIntoWithAspect import org.futo.circles.core.extensions.setIsVisible import org.futo.circles.core.extensions.visible import org.futo.circles.core.list.ViewBindingHolder import org.futo.circles.core.list.context import org.futo.circles.core.model.MediaContent +import org.futo.circles.core.model.MediaType import org.futo.circles.core.model.PollContent import org.futo.circles.core.model.Post import org.futo.circles.core.model.TextContent -import org.futo.circles.core.model.MediaType import org.futo.circles.databinding.ViewPollPostBinding import org.futo.circles.databinding.ViewTextMediaPostBinding import org.futo.circles.feature.timeline.post.markdown.MarkdownParser @@ -97,12 +97,12 @@ class TextMediaPostViewHolder( bindMediaCaption(content) bindMediaCover(content) binding.vMediaContent.videoGroup.setIsVisible(content.getMediaType() == MediaType.Video) - binding.vMediaContent.tvDuration.text = content.mediaContentInfo.duration + binding.vMediaContent.tvDuration.text = content.mediaFileData.duration } private fun bindMediaCaption(content: MediaContent) { binding.tvTextContent.apply { - val caption = content.mediaContentInfo.caption + val caption = content.caption setIsVisible(caption != null) caption?.let { setText(markwon.toMarkdown(it), TextView.BufferType.SPANNABLE) } } @@ -111,17 +111,13 @@ class TextMediaPostViewHolder( private fun bindMediaCover(content: MediaContent) { val image = binding.vMediaContent.ivCover image.post { - val size = content.calculateSize(image.width) + val size = content.thumbnailOrFullSize(image.width) image.updateLayoutParams { width = size.width height = size.height } } - content.mediaFileData.loadEncryptedIntoWithAspect( - image, - content.aspectRatio, - content.mediaContentInfo.thumbHash - ) + content.loadEncryptedThumbOrFullIntoWithAspect(image) } } diff --git a/app/src/main/java/org/futo/circles/view/PostLayout.kt b/app/src/main/java/org/futo/circles/view/PostLayout.kt index 76730536964e11957585919d12436f2f14338bf8..7d4685fa7948ddecabb9e31e0d7fcac4410452ee 100644 --- a/app/src/main/java/org/futo/circles/view/PostLayout.kt +++ b/app/src/main/java/org/futo/circles/view/PostLayout.kt @@ -113,7 +113,7 @@ class PostLayout( private fun setMentionBorder(content: PostContent) { val hasMention = when (content) { - is MediaContent -> content.mediaContentInfo.caption?.let { + is MediaContent -> content.caption?.let { MarkdownParser.hasCurrentUserMention(it) } ?: false diff --git a/app/src/main/java/org/futo/circles/view/PreviewPostView.kt b/app/src/main/java/org/futo/circles/view/PreviewPostView.kt index 1283add2ef0f577420721457e89be77b693287e2..06264d5c6ee84a872fb651b9d734fc366dbdce93 100644 --- a/app/src/main/java/org/futo/circles/view/PreviewPostView.kt +++ b/app/src/main/java/org/futo/circles/view/PreviewPostView.kt @@ -11,6 +11,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.updateLayoutParams import androidx.core.widget.doAfterTextChanged import org.futo.circles.core.extensions.loadEncryptedIntoWithAspect +import org.futo.circles.core.extensions.loadEncryptedThumbOrFullIntoWithAspect import org.futo.circles.core.extensions.loadImage import org.futo.circles.core.extensions.notEmptyDisplayName import org.futo.circles.core.extensions.setIsVisible @@ -103,7 +104,7 @@ class PreviewPostView( fun setMediaFromExistingPost(mediaContent: MediaContent) { canEditMedia = false - val caption = mediaContent.mediaContentInfo.caption ?: "" + val caption = mediaContent.caption ?: "" setText(caption) val uri = Uri.parse(mediaContent.mediaFileData.fileUrl) val mediaType = mediaContent.getMediaType() @@ -113,7 +114,7 @@ class PreviewPostView( val isVideo = mediaType == MediaType.Video binding.lMediaContent.videoGroup.setIsVisible(isVideo) if (isVideo) - binding.lMediaContent.tvDuration.text = mediaContent.mediaContentInfo.duration + binding.lMediaContent.tvDuration.text = mediaContent.mediaFileData.duration listener?.onPostContentAvailable(true) } @@ -173,17 +174,13 @@ class PreviewPostView( private fun loadMediaCover(mediaContent: MediaContent) { val image = binding.lMediaContent.ivCover image.post { - val size = mediaContent.calculateSize(image.width) + val size = mediaContent.thumbnailOrFullSize(image.width) image.updateLayoutParams { width = size.width height = size.height } } - mediaContent.mediaFileData.loadEncryptedIntoWithAspect( - image, - mediaContent.aspectRatio, - mediaContent.mediaContentInfo.thumbHash - ) + mediaContent.loadEncryptedThumbOrFullIntoWithAspect(image) } private fun requestFocusOnText() { diff --git a/core/build.gradle b/core/build.gradle index 4aa407705596b700e19521a7b7abb9c608562149..513d4067e542f7de62aee906ecf9ad0fef701021 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -69,7 +69,7 @@ dependencies { kapt "com.google.dagger:hilt-compiler:$rootProject.ext.hilt_version" //Matrix - api 'org.matrix.android:matrix-sdk-android:1.5.30.12' + api 'org.matrix.android:matrix-sdk-android:1.5.30.14' //Retrofit2 def retrofit_version = '2.9.0' diff --git a/core/src/main/java/org/futo/circles/core/extensions/MediaContentDataExtensions.kt b/core/src/main/java/org/futo/circles/core/extensions/MediaContentDataExtensions.kt index e7e429feb8ebe54ecba867947029e6783ca23b69..e5f593ed19ffb5e1d70792752835df472e99e13d 100644 --- a/core/src/main/java/org/futo/circles/core/extensions/MediaContentDataExtensions.kt +++ b/core/src/main/java/org/futo/circles/core/extensions/MediaContentDataExtensions.kt @@ -1,20 +1,23 @@ package org.futo.circles.core.extensions -import android.util.Size import android.widget.ImageView +import org.futo.circles.core.model.MediaContent import org.futo.circles.core.model.MediaFileData +fun MediaContent.loadEncryptedThumbOrFullIntoWithAspect(imageView: ImageView) { + val fileContent = thumbnailFileData ?: mediaFileData + fileContent.loadEncryptedIntoWithAspect(imageView, thumbHash) +} + fun MediaFileData.loadEncryptedIntoWithAspect( imageView: ImageView, - aspectRatio: Float, thumbHash: String? = null ) { imageView.post { if (fileUrl.startsWith(UriContentScheme)) { imageView.loadImage(fileUrl) } else { - val imageWith = imageView.width - val size = Size(imageWith, (imageWith / aspectRatio).toInt()) + val size = calculateSize(imageView.width) imageView.loadEncryptedImage(this, size, thumbHash = thumbHash) } } diff --git a/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt b/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt index d613c575836c364701a958ff92f34251754722ba..26d4b57036d735c07286253232982306913be308 100644 --- a/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt +++ b/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt @@ -3,7 +3,6 @@ package org.futo.circles.core.mapping import com.bumptech.glide.request.target.Target import org.futo.circles.core.MediaCaptionFieldKey import org.futo.circles.core.model.MediaContent -import org.futo.circles.core.model.MediaContentInfo import org.futo.circles.core.model.MediaFileData import org.futo.circles.core.model.MediaType import org.futo.circles.core.model.PostContentType @@ -16,22 +15,13 @@ import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -fun TimelineEvent.toMediaContent(mediaType: MediaType): MediaContent { - val messageContentInfo = root.getClearContent().let { - when (mediaType) { - MediaType.Image -> it.toModel<MessageImageContent>() - .toMediaContentInfo(getCaption()) - - MediaType.Video -> it.toModel<MessageVideoContent>() - .toMediaContentInfo(getCaption()) - } - } - return MediaContent( - type = if (mediaType == MediaType.Image) PostContentType.IMAGE_CONTENT else PostContentType.VIDEO_CONTENT, - mediaFileData = toMediaContentData(mediaType), - mediaContentInfo = messageContentInfo - ) -} +fun TimelineEvent.toMediaContent(mediaType: MediaType): MediaContent = MediaContent( + type = if (mediaType == MediaType.Image) PostContentType.IMAGE_CONTENT else PostContentType.VIDEO_CONTENT, + caption = getCaption(), + mediaFileData = toMediaFileData(mediaType), + thumbnailFileData = toThumbnailFileData(mediaType), + thumbHash = getThumbHash(mediaType) +) private fun TimelineEvent.getCaption(): String? { val lastContent = @@ -39,37 +29,81 @@ private fun TimelineEvent.getCaption(): String? { return lastContent?.get(MediaCaptionFieldKey)?.toString() } -private fun MessageImageContent?.toMediaContentInfo(caption: String?): MediaContentInfo = - MediaContentInfo( - caption = caption, - thumbnailUrl = this?.info?.thumbnailFile?.url ?: "", - width = this?.info?.width ?: Target.SIZE_ORIGINAL, - height = this?.info?.height ?: Target.SIZE_ORIGINAL, - duration = "", - thumbHash = this?.info?.blurHash - ) +private fun TimelineEvent.getThumbHash(mediaType: MediaType) = when (mediaType) { + MediaType.Image -> { + val info = root.getClearContent()?.toModel<MessageImageContent>()?.info + info?.thumbHash ?: info?.blurHash + } -private fun MessageVideoContent?.toMediaContentInfo(caption: String?): MediaContentInfo = - MediaContentInfo( - caption = caption, - thumbnailUrl = this?.videoInfo?.thumbnailFile?.url ?: "", - width = this?.videoInfo?.width ?: Target.SIZE_ORIGINAL, - height = this?.videoInfo?.height ?: Target.SIZE_ORIGINAL, - duration = VideoUtils.getVideoDurationString(this?.videoInfo?.duration?.toLong() ?: 0L), - thumbHash = this?.videoInfo?.blurHash - ) + MediaType.Video -> { + val info = root.getClearContent()?.toModel<MessageVideoContent>()?.videoInfo + info?.thumbHash ?: info?.blurHash + } +} -private fun TimelineEvent.toMediaContentData(mediaType: MediaType): MediaFileData { - val messageContent = root.getClearContent().let { - when (mediaType) { - MediaType.Image -> it.toModel<MessageImageContent>() - MediaType.Video -> it.toModel<MessageVideoContent>() - } +private fun TimelineEvent.toMediaFileData(mediaType: MediaType): MediaFileData { + val content = root.getClearContent() + return when (mediaType) { + MediaType.Image -> content.toModel<MessageImageContent>().toMediaFileData() + MediaType.Video -> content.toModel<MessageVideoContent>().toMediaFileData() } +} + +private fun MessageImageContent?.toMediaFileData() = MediaFileData( + fileName = this?.getFileName() ?: "", + mimeType = this?.mimeType ?: "", + fileUrl = this?.getFileUrl() ?: "", + elementToDecrypt = this?.encryptedFileInfo?.toElementToDecrypt(), + width = this?.info?.width ?: Target.SIZE_ORIGINAL, + height = this?.info?.height ?: Target.SIZE_ORIGINAL, + duration = "" +) + +private fun MessageVideoContent?.toMediaFileData() = MediaFileData( + fileName = this?.getFileName() ?: "", + mimeType = this?.mimeType ?: "", + fileUrl = this?.getFileUrl() ?: "", + elementToDecrypt = this?.encryptedFileInfo?.toElementToDecrypt(), + width = this?.videoInfo?.width ?: Target.SIZE_ORIGINAL, + height = this?.videoInfo?.height ?: Target.SIZE_ORIGINAL, + duration = VideoUtils.getVideoDurationString(this?.videoInfo?.duration?.toLong() ?: 0L) +) + +private fun MessageImageContent.toThumbnailFileData(): MediaFileData? { + val imageInfo = info ?: return null + val file = imageInfo.thumbnailFile?.toElementToDecrypt() ?: return null + val url = imageInfo.thumbnailFile?.url ?: imageInfo.thumbnailUrl + val mimeType = imageInfo.thumbnailInfo?.mimeType ?: "" + return MediaFileData( + fileName = getFileName(), + mimeType = mimeType, + fileUrl = url ?: "", + elementToDecrypt = file, + width = imageInfo.thumbnailInfo?.width ?: Target.SIZE_ORIGINAL, + height = imageInfo.thumbnailInfo?.height ?: Target.SIZE_ORIGINAL, + duration = "" + ) +} + +private fun MessageVideoContent.toThumbnailFileData(): MediaFileData? { + val videoInfo = videoInfo ?: return null + val file = videoInfo.thumbnailFile?.toElementToDecrypt() ?: return null + val url = videoInfo.thumbnailFile?.url ?: videoInfo.thumbnailUrl + val mimeType = videoInfo.thumbnailInfo?.mimeType ?: "" return MediaFileData( - fileName = messageContent?.getFileName() ?: "", - mimeType = messageContent?.mimeType ?: "", - fileUrl = messageContent?.getFileUrl() ?: "", - elementToDecrypt = messageContent?.encryptedFileInfo?.toElementToDecrypt(), + fileName = getFileName(), + mimeType = mimeType, + fileUrl = url ?: "", + elementToDecrypt = file, + width = videoInfo.thumbnailInfo?.width ?: Target.SIZE_ORIGINAL, + height = videoInfo.thumbnailInfo?.height ?: Target.SIZE_ORIGINAL, + duration = "" ) +} + +private fun TimelineEvent.toThumbnailFileData(mediaType: MediaType) = root.getClearContent().let { + when (mediaType) { + MediaType.Image -> it.toModel<MessageImageContent>()?.toThumbnailFileData() + MediaType.Video -> it.toModel<MessageVideoContent>()?.toThumbnailFileData() + } } \ No newline at end of file diff --git a/core/src/main/java/org/futo/circles/core/model/MediaFileData.kt b/core/src/main/java/org/futo/circles/core/model/MediaFileData.kt index a6298fc13d476f32abbbc764adaf5bfe78a26260..d414cfeb9c67c579d476e9b2500cff125e504e05 100644 --- a/core/src/main/java/org/futo/circles/core/model/MediaFileData.kt +++ b/core/src/main/java/org/futo/circles/core/model/MediaFileData.kt @@ -1,10 +1,18 @@ package org.futo.circles.core.model +import android.util.Size import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt data class MediaFileData( val fileName: String, val mimeType: String, val fileUrl: String, - val elementToDecrypt: ElementToDecrypt? -) \ No newline at end of file + val elementToDecrypt: ElementToDecrypt?, + val width: Int, + val height: Int, + val duration: String +) { + val aspectRatio = width.toFloat() / height.toFloat() + fun calculateSize(width: Int) = Size(width, (width / aspectRatio).toInt()) + +} \ No newline at end of file diff --git a/core/src/main/java/org/futo/circles/core/model/PostContent.kt b/core/src/main/java/org/futo/circles/core/model/PostContent.kt index b0386f96d6da11f768e33a0919ec3eb9f853c7fe..7fd70f87f85f68f31491c49b1da81bde774c2f69 100644 --- a/core/src/main/java/org/futo/circles/core/model/PostContent.kt +++ b/core/src/main/java/org/futo/circles/core/model/PostContent.kt @@ -23,24 +23,21 @@ data class TextContent( data class MediaContent( override val type: PostContentType, + val caption: String?, val mediaFileData: MediaFileData, - val mediaContentInfo: MediaContentInfo, + val thumbnailFileData: MediaFileData?, + val thumbHash: String? ) : PostContent(type) { - val aspectRatio = mediaContentInfo.width.toFloat() / mediaContentInfo.height.toFloat() - fun calculateSize(width: Int) = Size(width, (width / aspectRatio).toInt()) + + fun thumbnailOrFullSize(width: Int) = thumbnailFileData?.let { + Size(width, (width / it.aspectRatio).toInt()) + } ?: Size(width, (width / mediaFileData.aspectRatio).toInt()) + + fun getMediaType(): MediaType = if (type == PostContentType.VIDEO_CONTENT) MediaType.Video else MediaType.Image } -data class MediaContentInfo( - val caption: String?, - val thumbnailUrl: String, - val width: Int, - val height: Int, - val duration: String, - val thumbHash: String? -) - data class PollContent( val question: String, val state: PollState, diff --git a/core/src/main/java/org/futo/circles/core/picker/gallery/media/list/GalleryMediaItemViewHolder.kt b/core/src/main/java/org/futo/circles/core/picker/gallery/media/list/GalleryMediaItemViewHolder.kt index b7bb9b2ddd22c286b0d9d38c663f63d0e44009dd..86b2f52f06e019304887b3f2719a2ba646dfba6d 100644 --- a/core/src/main/java/org/futo/circles/core/picker/gallery/media/list/GalleryMediaItemViewHolder.kt +++ b/core/src/main/java/org/futo/circles/core/picker/gallery/media/list/GalleryMediaItemViewHolder.kt @@ -11,7 +11,7 @@ import androidx.recyclerview.widget.RecyclerView import org.futo.circles.core.R import org.futo.circles.core.databinding.ListItemGalleryMediaBinding import org.futo.circles.core.databinding.ListItemGalleryMediaMultiselectBinding -import org.futo.circles.core.extensions.loadEncryptedIntoWithAspect +import org.futo.circles.core.extensions.loadEncryptedThumbOrFullIntoWithAspect import org.futo.circles.core.extensions.onClick import org.futo.circles.core.extensions.setIsVisible import org.futo.circles.core.list.ViewBindingHolder @@ -32,24 +32,20 @@ abstract class GridMediaItemViewHolder(view: View) : RecyclerView.ViewHolder(vie private fun bindCover(id: String, mediaContent: MediaContent) { ivCover.transitionName = id ivCover.post { - val size = mediaContent.calculateSize(ivCover.width) + val size = mediaContent.thumbnailOrFullSize(ivCover.width) ivCover.updateLayoutParams { width = size.width height = size.height } } - mediaContent.mediaFileData.loadEncryptedIntoWithAspect( - ivCover, - mediaContent.aspectRatio, - mediaContent.mediaContentInfo.thumbHash - ) + mediaContent.loadEncryptedThumbOrFullIntoWithAspect(ivCover) } private fun bindVideoParams( mediaContent: MediaContent ) { videoGroup.setIsVisible(mediaContent.type == PostContentType.VIDEO_CONTENT) - tvDuration.text = mediaContent.mediaContentInfo.duration + tvDuration.text = mediaContent.mediaFileData.duration } } diff --git a/core/src/main/java/org/futo/circles/core/timeline/post/SendMessageDataSource.kt b/core/src/main/java/org/futo/circles/core/timeline/post/SendMessageDataSource.kt index 07d69cd95b82c461367dd59336c33a2e2b542f76..f392f365b3a44b65a5cf737dc697dc35add99349 100644 --- a/core/src/main/java/org/futo/circles/core/timeline/post/SendMessageDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/timeline/post/SendMessageDataSource.kt @@ -85,14 +85,12 @@ class SendMessageDataSource @Inject constructor(@ApplicationContext private val MediaType.Image -> uri.toImageContentAttachmentData(context) MediaType.Video -> uri.toVideoContentAttachmentData(context) } ?: return null - val shouldCompress = - if (compressBeforeSending) content.mimeType != WEBP_MIME_TYPE else false val additionalContent = mutableMapOf<String, Any>() caption?.let { additionalContent[MediaCaptionFieldKey] = it } return roomForMessage.sendService().sendMedia( content, - shouldCompress, + compressBeforeSending, emptySet(), rootThreadEventId = threadEventId, additionalContent = additionalContent @@ -128,8 +126,4 @@ class SendMessageDataSource @Inject constructor(@ApplicationContext private val roomForMessage.relationService() .editPoll(event, pollContent.pollType, pollContent.question, pollContent.options) } - - companion object { - private const val WEBP_MIME_TYPE = "image/webp" - } } \ No newline at end of file diff --git a/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/full_screen/media_item/FullScreenMediaFragment.kt b/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/full_screen/media_item/FullScreenMediaFragment.kt index a6330513007a9b3ae5778663a1fb8ea8dbd73aa2..4f47dd89a074b2cdc9b8094419daa6a7d155a2d6 100644 --- a/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/full_screen/media_item/FullScreenMediaFragment.kt +++ b/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/full_screen/media_item/FullScreenMediaFragment.kt @@ -76,11 +76,7 @@ class FullScreenMediaFragment : Fragment(R.layout.fragment_full_screen_media) { transitionName = null gone() } - it.mediaFileData.loadEncryptedIntoWithAspect( - binding.ivImage, - it.aspectRatio, - it.mediaContentInfo.thumbHash - ) + it.mediaFileData.loadEncryptedIntoWithAspect(binding.ivImage, it.thumbHash) binding.ivImage.post { parentFragment?.startPostponedEnterTransition() } } viewModel.videoLiveData.observeData(this) {