Compress image before sending

This commit is contained in:
Benoit Marty 2020-02-13 22:30:25 +01:00
parent 385fa317c0
commit e6bd09859f
7 changed files with 76 additions and 16 deletions

View File

@ -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'

View File

@ -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<String>): 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<ContentAttachmentData>,
// TODO Change to a Compression Level Enum
compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable

View File

@ -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
}
}

View File

@ -314,6 +314,11 @@ SOFTWARE.
<br/>
Copyright (c) 2012-2016 Dan Wheeler and Dropbox, Inc.
</li>
<li>
<b>Compressor</b>
<br/>
Copyright (c) 2016 Zetra.
</li>
<li>
<b>com.otaliastudios:autocomplete</b>
<br/>

View File

@ -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)
}
}

View File

@ -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<ContentAttachmentData>) {
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()) {

View File

@ -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) }