mirror of
https://github.com/SimpleMobileTools/Simple-SMS-Messenger.git
synced 2025-02-10 16:50:42 +01:00
feat: compress images before sending
This commit is contained in:
parent
72833f6f16
commit
664346e8a9
@ -195,6 +195,16 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<activity-alias
|
||||
android:name=".activities.SplashActivity.Red"
|
||||
android:enabled="false"
|
||||
|
@ -13,6 +13,7 @@ import android.os.Handler
|
||||
import android.provider.Telephony
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
@ -54,6 +55,7 @@ import org.greenrobot.eventbus.ThreadMode
|
||||
class ThreadActivity : SimpleActivity() {
|
||||
private val MIN_DATE_TIME_DIFF_SECS = 300
|
||||
private val PICK_ATTACHMENT_INTENT = 1
|
||||
private val TAG = "ThreadActivity"
|
||||
|
||||
private var threadId = 0L
|
||||
private var currentSIMCardIndex = 0
|
||||
@ -65,7 +67,8 @@ class ThreadActivity : SimpleActivity() {
|
||||
private var privateContacts = ArrayList<SimpleContact>()
|
||||
private var messages = ArrayList<Message>()
|
||||
private val availableSIMCards = ArrayList<SIMCard>()
|
||||
private var attachmentUris = LinkedHashSet<Uri>()
|
||||
private var attachmentSelections = mutableMapOf<String, AttachmentSelection>()
|
||||
private val imageCompressor by lazy { ImageCompressor(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -569,7 +572,7 @@ class ThreadActivity : SimpleActivity() {
|
||||
conversationsDB.markRead(threadId)
|
||||
}
|
||||
|
||||
if (i == cnt - 1 && (message.type == Telephony.Sms.MESSAGE_TYPE_SENT )) {
|
||||
if (i == cnt - 1 && (message.type == Telephony.Sms.MESSAGE_TYPE_SENT)) {
|
||||
items.add(ThreadSent(message.id, delivered = message.status == Telephony.Sms.STATUS_COMPLETE))
|
||||
}
|
||||
}
|
||||
@ -592,23 +595,67 @@ class ThreadActivity : SimpleActivity() {
|
||||
}
|
||||
|
||||
private fun addAttachment(uri: Uri) {
|
||||
if (attachmentUris.contains(uri)) {
|
||||
val originalUriString = uri.toString()
|
||||
if (attachmentSelections.containsKey(originalUriString)) {
|
||||
return
|
||||
}
|
||||
|
||||
attachmentUris.add(uri)
|
||||
attachmentSelections[originalUriString] = AttachmentSelection(uri, false)
|
||||
val attachmentView = addAttachmentView(originalUriString, uri)
|
||||
val mimeType = contentResolver.getType(uri)
|
||||
Log.e(TAG, "Selected image: mimetype=$mimeType uri=$uri")
|
||||
if (mimeType == null) {
|
||||
Log.e(TAG, "addAttachment: null mime type for uri: $uri")
|
||||
return
|
||||
}
|
||||
|
||||
if (mimeType.isImageMimeType()) {
|
||||
Log.d(TAG, "addAttachment: attachment is an image mimetype=$mimeType")
|
||||
val byteArray = contentResolver.openInputStream(uri)?.readBytes()
|
||||
if (byteArray == null) {
|
||||
Log.e(TAG, "addAttachment: null stream for: $uri")
|
||||
return
|
||||
}
|
||||
|
||||
val selection = attachmentSelections[originalUriString]
|
||||
attachmentSelections[originalUriString] = selection!!.copy(isPending = true)
|
||||
checkSendMessageAvailability()
|
||||
attachmentView.thread_attachment_progress.beVisible()
|
||||
imageCompressor.compressImage(byteArray, mimeType, IMAGE_COMPRESS_SIZE) { compressedUri ->
|
||||
runOnUiThread {
|
||||
if (compressedUri != null) {
|
||||
Log.e(TAG, "Compressed successfully compressedUri=$compressedUri")
|
||||
attachmentSelections[originalUriString] = AttachmentSelection(compressedUri, false)
|
||||
loadAttachmentPreview(attachmentView, compressedUri)
|
||||
} else {
|
||||
Log.e(TAG, "addAttachment: Failed to compress image: uri=$uri")
|
||||
}
|
||||
checkSendMessageAvailability()
|
||||
attachmentView.thread_attachment_progress.beGone()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "addAttachment: not an image")
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAttachmentView(originalUri: String, uri: Uri): View {
|
||||
thread_attachments_holder.beVisible()
|
||||
val attachmentView = layoutInflater.inflate(R.layout.item_attachment, null).apply {
|
||||
thread_attachments_wrapper.addView(this)
|
||||
thread_remove_attachment.setOnClickListener {
|
||||
thread_attachments_wrapper.removeView(this)
|
||||
attachmentUris.remove(uri)
|
||||
if (attachmentUris.isEmpty()) {
|
||||
attachmentSelections.remove(originalUri)
|
||||
if (attachmentSelections.isEmpty()) {
|
||||
thread_attachments_holder.beGone()
|
||||
}
|
||||
}
|
||||
}
|
||||
loadAttachmentPreview(attachmentView, uri)
|
||||
return attachmentView
|
||||
}
|
||||
|
||||
private fun loadAttachmentPreview(attachmentView: View, uri: Uri) {
|
||||
val roundedCornersRadius = resources.getDimension(R.dimen.medium_margin).toInt()
|
||||
val options = RequestOptions()
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
@ -636,7 +683,7 @@ class ThreadActivity : SimpleActivity() {
|
||||
}
|
||||
|
||||
private fun checkSendMessageAvailability() {
|
||||
if (thread_type_message.text.isNotEmpty() || attachmentUris.isNotEmpty()) {
|
||||
if (thread_type_message.text.isNotEmpty() || (attachmentSelections.isNotEmpty() && !attachmentSelections.values.any { it.isPending })) {
|
||||
thread_send_message.isClickable = true
|
||||
thread_send_message.alpha = 0.9f
|
||||
} else {
|
||||
@ -647,7 +694,7 @@ class ThreadActivity : SimpleActivity() {
|
||||
|
||||
private fun sendMessage() {
|
||||
val msg = thread_type_message.value
|
||||
if (msg.isEmpty() && attachmentUris.isEmpty()) {
|
||||
if (msg.isEmpty() && attachmentSelections.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -673,15 +720,19 @@ class ThreadActivity : SimpleActivity() {
|
||||
val transaction = Transaction(this, settings)
|
||||
val message = com.klinker.android.send_message.Message(msg, numbers.toTypedArray())
|
||||
|
||||
if (attachmentUris.isNotEmpty()) {
|
||||
for (uri in attachmentUris) {
|
||||
if (attachmentSelections.isNotEmpty()) {
|
||||
for (selection in attachmentSelections.values) {
|
||||
Log.d(TAG, "sendMessage:attachmentUri=$selection")
|
||||
try {
|
||||
val byteArray = contentResolver.openInputStream(uri)?.readBytes() ?: continue
|
||||
val mimeType = contentResolver.getType(uri) ?: continue
|
||||
val byteArray = contentResolver.openInputStream(selection.uri)?.readBytes() ?: continue
|
||||
val mimeType = contentResolver.getType(selection.uri) ?: continue
|
||||
message.addMedia(byteArray, mimeType)
|
||||
Log.d(TAG, "sendMessage: byteArray: ${byteArray.size} -- mimeType=$mimeType")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "sendMessage: ", e)
|
||||
showErrorToast(e)
|
||||
} catch (e: Error) {
|
||||
Log.e(TAG, "sendMessage error: ", e)
|
||||
toast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
|
||||
}
|
||||
}
|
||||
@ -697,7 +748,7 @@ class ThreadActivity : SimpleActivity() {
|
||||
refreshedSinceSent = false
|
||||
transaction.sendNewMessage(message, threadId)
|
||||
thread_type_message.setText("")
|
||||
attachmentUris.clear()
|
||||
attachmentSelections.clear()
|
||||
thread_attachments_holder.beGone()
|
||||
thread_attachments_wrapper.removeAllViews()
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
package com.simplemobiletools.smsmessenger.extensions
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
fun Bitmap.CompressFormat.extension() = when (this) {
|
||||
Bitmap.CompressFormat.PNG -> "png"
|
||||
Bitmap.CompressFormat.WEBP -> "webp"
|
||||
else -> "jpg"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.simplemobiletools.smsmessenger.extensions
|
||||
|
||||
fun String.getExtensionFromMimeType(): String {
|
||||
return when (this) {
|
||||
"image/png" -> ".png"
|
||||
"image/apng" -> ".apng"
|
||||
"image/webp" -> ".webp"
|
||||
"image/svg+xml" -> ".svg"
|
||||
"image/gif" -> ".gif"
|
||||
else -> ".jpg"
|
||||
}
|
||||
}
|
||||
|
||||
fun String.isImageMimeType(): Boolean {
|
||||
return startsWith("image")
|
||||
}
|
@ -33,6 +33,8 @@ const val LOCK_SCREEN_SENDER_MESSAGE = 1
|
||||
const val LOCK_SCREEN_SENDER = 2
|
||||
const val LOCK_SCREEN_NOTHING = 3
|
||||
|
||||
const val IMAGE_COMPRESS_SIZE = 1_048_576L
|
||||
|
||||
fun refreshMessages() {
|
||||
EventBus.getDefault().post(Events.RefreshMessages())
|
||||
}
|
||||
|
@ -0,0 +1,115 @@
|
||||
package com.simplemobiletools.smsmessenger.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import android.media.ExifInterface
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.simplemobiletools.commons.extensions.getCompressionFormat
|
||||
import com.simplemobiletools.commons.extensions.getMyFileUri
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.smsmessenger.extensions.extension
|
||||
import com.simplemobiletools.smsmessenger.extensions.getExtensionFromMimeType
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
/**
|
||||
* Compress image to a given size based on
|
||||
* [Compressor](https://github.com/zetbaitsu/Compressor/)
|
||||
* */
|
||||
class ImageCompressor(private val context: Context) {
|
||||
companion object {
|
||||
private const val TAG = "ImageCompressor"
|
||||
}
|
||||
|
||||
private val outputDirectory = File(context.cacheDir, "compressed").apply {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
fun compressImage(byteArray: ByteArray, mimeType: String, compressSize: Long, callback: (compressedFileUri: Uri?) -> Unit) {
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
Log.d(TAG, "Attempting to compress image of length: ${byteArray.size} of mimetype=$mimeType to size=$compressSize")
|
||||
var destinationFile = File(outputDirectory, System.currentTimeMillis().toString().plus(mimeType.getExtensionFromMimeType()))
|
||||
Log.d(TAG, "compressImage: Saving file to: $destinationFile")
|
||||
destinationFile.writeBytes(byteArray)
|
||||
Log.d(TAG, "Written file to: $destinationFile")
|
||||
val constraint = SizeConstraint(compressSize)
|
||||
Log.d(TAG, "Starting compression...")
|
||||
while (constraint.isSatisfied(destinationFile).not()) {
|
||||
destinationFile = constraint.satisfy(destinationFile)
|
||||
Log.d(TAG, "Compressed, new size is ${destinationFile.length()}")
|
||||
}
|
||||
|
||||
Log.d(TAG, "Compression done, new size is ${destinationFile.length()}")
|
||||
callback.invoke(context.getMyFileUri(destinationFile))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "compressImage: ", e)
|
||||
callback.invoke(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun overWrite(imageFile: File, bitmap: Bitmap, format: Bitmap.CompressFormat = imageFile.path.getCompressionFormat(), quality: Int = 100): File {
|
||||
val result = if (format == imageFile.path.getCompressionFormat()) {
|
||||
imageFile
|
||||
} else {
|
||||
File("${imageFile.absolutePath.substringBeforeLast(".")}.${format.extension()}")
|
||||
}
|
||||
imageFile.delete()
|
||||
saveBitmap(bitmap, result, format, quality)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun saveBitmap(bitmap: Bitmap, destination: File, format: Bitmap.CompressFormat = destination.path.getCompressionFormat(), quality: Int = 100) {
|
||||
destination.parentFile?.mkdirs()
|
||||
var fileOutputStream: FileOutputStream? = null
|
||||
try {
|
||||
fileOutputStream = FileOutputStream(destination.absolutePath)
|
||||
bitmap.compress(format, quality, fileOutputStream)
|
||||
} finally {
|
||||
fileOutputStream?.run {
|
||||
flush()
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadBitmap(imageFile: File) = BitmapFactory.decodeFile(imageFile.absolutePath).run {
|
||||
determineImageRotation(imageFile, this)
|
||||
}
|
||||
|
||||
private fun determineImageRotation(imageFile: File, bitmap: Bitmap): Bitmap {
|
||||
val exif = ExifInterface(imageFile.absolutePath)
|
||||
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)
|
||||
val matrix = Matrix()
|
||||
when (orientation) {
|
||||
6 -> matrix.postRotate(90f)
|
||||
3 -> matrix.postRotate(180f)
|
||||
8 -> matrix.postRotate(270f)
|
||||
}
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
||||
}
|
||||
|
||||
private inner class SizeConstraint(
|
||||
private val maxFileSize: Long,
|
||||
private val stepSize: Int = 10,
|
||||
private val maxIteration: Int = 10,
|
||||
private val minQuality: Int = 10
|
||||
) {
|
||||
private var iteration: Int = 0
|
||||
|
||||
fun isSatisfied(imageFile: File): Boolean {
|
||||
return imageFile.length() <= maxFileSize || iteration >= maxIteration
|
||||
}
|
||||
|
||||
fun satisfy(imageFile: File): File {
|
||||
iteration++
|
||||
val quality = (100 - iteration * stepSize).takeIf { it >= minQuality } ?: minQuality
|
||||
return overWrite(imageFile, loadBitmap(imageFile), quality = quality)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.simplemobiletools.smsmessenger.models
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
data class AttachmentSelection(
|
||||
val uri: Uri,
|
||||
val isPending: Boolean,
|
||||
)
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/conversation_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
@ -8,7 +9,16 @@
|
||||
android:id="@+id/thread_attachment_preview"
|
||||
android:layout_width="@dimen/attachment_preview_size"
|
||||
android:layout_height="@dimen/attachment_preview_size"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/thread_attachment_progress"
|
||||
android:layout_width="@dimen/remove_attachment_size"
|
||||
android:layout_height="@dimen/remove_attachment_size"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thread_remove_attachment"
|
||||
@ -18,6 +28,7 @@
|
||||
android:layout_alignEnd="@+id/thread_attachment_preview"
|
||||
android:padding="@dimen/tiny_margin"
|
||||
android:src="@drawable/ic_cross_vector"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
4
app/src/main/res/xml/provider_paths.xml
Normal file
4
app/src/main/res/xml/provider_paths.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="compressed_files" path="compressed/"/>
|
||||
</paths>
|
Loading…
x
Reference in New Issue
Block a user