This commit is contained in:
1 file changed
+22
-7
@@ -3,6 +3,7 @@ package com.m57.hermescontrol.ui.chat
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import android.util.Base64OutputStream
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -33,11 +34,13 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
private const val TAG = "ChatViewModel"
|
private const val TAG = "ChatViewModel"
|
||||||
@@ -589,12 +592,11 @@ class ChatViewModel(
|
|||||||
val fileRefs = mutableListOf<String>()
|
val fileRefs = mutableListOf<String>()
|
||||||
|
|
||||||
for (attachment in attachments) {
|
for (attachment in attachments) {
|
||||||
val bytes = readContentUriBytes(attachment.uri)
|
val b64 = readContentUriBase64(attachment.uri)
|
||||||
if (bytes == null) {
|
if (b64 == null) {
|
||||||
Log.w(TAG, "Skipping unreadable attachment: ${attachment.name}")
|
Log.w(TAG, "Skipping unreadable attachment: ${attachment.name}")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val b64 = Base64.encodeToString(bytes, Base64.NO_WRAP)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (attachment.isImage) {
|
if (attachment.isImage) {
|
||||||
@@ -650,14 +652,27 @@ class ChatViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read bytes from a `content://` or `file://` URI via ContentResolver. */
|
/** Read and encode a `content://` or `file://` URI to Base64 via ContentResolver, avoiding large allocations. */
|
||||||
private fun readContentUriBytes(uriString: String): ByteArray? =
|
private suspend fun readContentUriBase64(uriString: String): String? =
|
||||||
try {
|
try {
|
||||||
val context = getApplication<Application>()
|
val context = getApplication<Application>()
|
||||||
val uri = Uri.parse(uriString)
|
val uri = Uri.parse(uriString)
|
||||||
context.contentResolver.openInputStream(uri)?.use { stream -> stream.readBytes() }
|
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
val b64os = Base64OutputStream(baos, Base64.NO_WRAP)
|
||||||
|
val buffer = ByteArray(1024 * 128) // 128KB chunk
|
||||||
|
var bytesRead: Int
|
||||||
|
|
||||||
|
while (stream.read(buffer).also { bytesRead = it } != -1) {
|
||||||
|
b64os.write(buffer, 0, bytesRead)
|
||||||
|
yield() // Prevent blocking the thread during large reads
|
||||||
|
}
|
||||||
|
b64os.close()
|
||||||
|
baos.toString("UTF-8")
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to read attachment bytes: ${e.message}", e)
|
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||||
|
Log.e(TAG, "Failed to read and encode attachment: ${e.message}", e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user