diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index ea27df8ab6..062b590acf 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -119,6 +119,7 @@ dependencies { // Image implementation 'androidx.exifinterface:exifinterface:1.1.0' + implementation 'id.zelory:compressor:3.0.0' // Database implementation 'com.github.Zhuinden:realm-monarchy:0.5.1' diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt index 5f32458dd2..afa3dda496 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt @@ -51,26 +51,24 @@ interface SendService { /** * Method to send a media asynchronously. * @param attachment the media to send - * @param compressBeforeSending set to true to compress media before sending them + * @param compressBeforeSending set to true to compress images before sending them * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @return a [Cancelable] */ fun sendMedia(attachment: ContentAttachmentData, - // TODO Change to a Compression Level Enum compressBeforeSending: Boolean, roomIds: Set): Cancelable /** * Method to send a list of media asynchronously. * @param attachments the list of media to send - * @param compressBeforeSending set to true to compress media before sending them + * @param compressBeforeSending set to true to compress images before sending them * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @return a [Cancelable] */ fun sendMedias(attachments: List, - // TODO Change to a Compression Level Enum compressBeforeSending: Boolean, roomIds: Set): Cancelable diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index 334e1f5e5b..03504b6279 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -17,9 +17,12 @@ package im.vector.matrix.android.internal.session.content import android.content.Context +import android.graphics.BitmapFactory import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass +import id.zelory.compressor.Compressor +import id.zelory.compressor.constraint.default import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toContent @@ -42,7 +45,13 @@ import java.io.File import java.io.FileInputStream import javax.inject.Inject -internal class UploadContentWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { +private data class NewImageAttributes( + val newWidth: Int?, + val newHeight: Int?, + val newFileSize: Int +) + +internal class UploadContentWorker(val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( @@ -73,6 +82,8 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : val attachment = params.attachment + var newImageAttributes: NewImageAttributes? = null + val attachmentFile = try { File(attachment.path) } catch (e: Exception) { @@ -88,8 +99,35 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : )) ) } + .let {originalFile -> + if (attachment.type == ContentAttachmentData.Type.IMAGE) { + if (params.compressBeforeSending) { + Compressor.compress(context, originalFile) { + default( + width = MAX_IMAGE_SIZE, + height = MAX_IMAGE_SIZE + ) + }.also { compressedFile -> + // Update the params + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeFile(compressedFile.absolutePath, options) + val fileSize = compressedFile.length().toInt() - // TODO Use compressBeforeSending + newImageAttributes = NewImageAttributes( + options.outWidth, + options.outHeight, + fileSize + ) + } + } else { + // TODO Fix here the image rotation issue + originalFile + } + } else { + // Other type + originalFile + } + } var uploadedThumbnailUrl: String? = null var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null @@ -168,7 +206,12 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : .uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener) } - handleSuccess(params, contentUploadResponse.contentUri, uploadedFileEncryptedFileInfo, uploadedThumbnailUrl, uploadedThumbnailEncryptedFileInfo) + handleSuccess(params, + contentUploadResponse.contentUri, + uploadedFileEncryptedFileInfo, + uploadedThumbnailUrl, + uploadedThumbnailEncryptedFileInfo, + newImageAttributes) } catch (t: Throwable) { Timber.e(t) handleFailure(params, t) @@ -195,7 +238,8 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : attachmentUrl: String, encryptedFileInfo: EncryptedFileInfo?, thumbnailUrl: String?, - thumbnailEncryptedFileInfo: EncryptedFileInfo?): Result { + thumbnailEncryptedFileInfo: EncryptedFileInfo?, + newImageAttributes: NewImageAttributes?): Result { Timber.v("handleSuccess $attachmentUrl, work is stopped $isStopped") params.events .mapNotNull { it.eventId } @@ -205,7 +249,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : val updatedEvents = params.events .map { - updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo) + updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes) } val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isRoomEncrypted) @@ -216,10 +260,11 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : url: String, encryptedFileInfo: EncryptedFileInfo?, thumbnailUrl: String? = null, - thumbnailEncryptedFileInfo: EncryptedFileInfo?): Event { + thumbnailEncryptedFileInfo: EncryptedFileInfo?, + newImageAttributes: NewImageAttributes?): Event { val messageContent: MessageContent = event.content.toModel() ?: return event val updatedContent = when (messageContent) { - is MessageImageContent -> messageContent.update(url, encryptedFileInfo) + is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newImageAttributes) is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo) is MessageFileContent -> messageContent.update(url, encryptedFileInfo) is MessageAudioContent -> messageContent.update(url, encryptedFileInfo) @@ -229,10 +274,16 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : } private fun MessageImageContent.update(url: String, - encryptedFileInfo: EncryptedFileInfo?): MessageImageContent { + encryptedFileInfo: EncryptedFileInfo?, + newImageAttributes: NewImageAttributes?): MessageImageContent { return copy( url = if (encryptedFileInfo == null) url else null, - encryptedFileInfo = encryptedFileInfo?.copy(url = url) + encryptedFileInfo = encryptedFileInfo?.copy(url = url), + info = info?.copy( + width = newImageAttributes?.newWidth ?: info.width, + height = newImageAttributes?.newHeight ?: info.height, + size = newImageAttributes?.newFileSize ?: info.size + ) ) } @@ -265,4 +316,8 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : encryptedFileInfo = encryptedFileInfo?.copy(url = url) ) } + + companion object { + private const val MAX_IMAGE_SIZE = 640 + } } diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 57e17265d1..13149aa1aa 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -314,6 +314,11 @@ SOFTWARE.
Copyright (c) 2012-2016 Dan Wheeler and Dropbox, Inc. +
  • + Compressor +
    + Copyright (c) 2016 Zetra. +
  • com.otaliastudios:autocomplete
    diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt index cb55d076ce..ee0755c6ee 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -124,6 +124,7 @@ class AttachmentsPreviewFragment @Inject constructor( attachmentBigPreviewController.setData(state) attachmentPreviewerBigList.scrollToPosition(state.currentAttachmentIndex) attachmentPreviewerMiniatureList.scrollToPosition(state.currentAttachmentIndex) + attachmentPreviewerSendImageOriginalSize.text = resources.getQuantityString(R.plurals.send_images_with_original_size, state.attachments.size) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 7a64953986..ff72b004f9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -508,7 +508,7 @@ class RoomDetailFragment @Inject constructor( AttachmentsPreviewActivity.REQUEST_CODE -> { val sendData = AttachmentsPreviewActivity.getOutput(data) val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data) - roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, keepOriginalSize)) + roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize)) } REACTION_SELECT_REQUEST_CODE -> { val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return @@ -1352,7 +1352,7 @@ class RoomDetailFragment @Inject constructor( override fun onContentAttachmentsReady(attachments: List) { val grouped = attachments.toGroupedContentAttachmentData() if (grouped.notPreviewables.isNotEmpty()) { - // Send the not previewable attachment right now (?) + // Send the not previewable attachments right now (?) roomDetailViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false)) } if (grouped.previewables.isNotEmpty()) { diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt index d78ebcae31..c6eba274d4 100644 --- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt @@ -142,7 +142,7 @@ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: if (proposeMediaEdition) { val grouped = attachmentData.toGroupedContentAttachmentData() if (grouped.notPreviewables.isNotEmpty()) { - // Send the not previewable attachment right now (?) + // Send the not previewable attachments right now (?) // Pick the first room to send the media selectedRoomIds.firstOrNull() ?.let { roomId -> session.getRoom(roomId) }