mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 03:45:21 +00:00
limit to 1 MB
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package com.bitchat.android.features.media
|
||||
|
||||
object MediaConstraints {
|
||||
// Global configurable media size limit (in bytes)
|
||||
// Change this value to adjust the max size for all media transfers
|
||||
const val MAX_MEDIA_BYTES: Long = 1L * 1024L * 1024L // 1 MB
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.bitchat.android.features.media
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.widget.Toast
|
||||
import java.io.File
|
||||
|
||||
object MediaSizeLimiter {
|
||||
private fun formatBytes(bytes: Long): String {
|
||||
val units = arrayOf("B", "KB", "MB", "GB")
|
||||
var size = bytes.toDouble()
|
||||
var unit = 0
|
||||
while (size >= 1024 && unit < units.size - 1) {
|
||||
size /= 1024.0
|
||||
unit++
|
||||
}
|
||||
return "%.1f %s".format(size, units[unit])
|
||||
}
|
||||
|
||||
private fun toastTooLarge(context: Context, label: String) {
|
||||
val maxLabel = formatBytes(MediaConstraints.MAX_MEDIA_BYTES)
|
||||
Toast.makeText(context, "$label is too large to send (max $maxLabel)", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun queryContentLength(context: Context, uri: Uri): Long? {
|
||||
// Try OpenableColumns.SIZE first
|
||||
val sizeFromQuery = try {
|
||||
context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)?.use { c ->
|
||||
val idx = c.getColumnIndex(OpenableColumns.SIZE)
|
||||
if (idx >= 0 && c.moveToFirst()) c.getLong(idx) else null
|
||||
}
|
||||
} catch (_: Exception) { null }
|
||||
|
||||
if (sizeFromQuery != null && sizeFromQuery >= 0) return sizeFromQuery
|
||||
|
||||
// Fallback to file descriptor statSize
|
||||
val sizeFromFd = try {
|
||||
context.contentResolver.openFileDescriptor(uri, "r")?.use { it.statSize }
|
||||
} catch (_: Exception) { null }
|
||||
|
||||
return sizeFromFd?.takeIf { it >= 0 }
|
||||
}
|
||||
|
||||
// Returns false if too large and shows a toast
|
||||
fun enforceUriPrecheck(context: Context, uri: Uri, label: String): Boolean {
|
||||
val len = queryContentLength(context, uri)
|
||||
if (len != null && len > MediaConstraints.MAX_MEDIA_BYTES) {
|
||||
toastTooLarge(context, label)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns false if too large. Optionally deletes the file if too large.
|
||||
fun enforcePathPostCheck(context: Context, path: String, label: String, deleteIfTooLarge: Boolean = true): Boolean {
|
||||
return try {
|
||||
val file = File(path)
|
||||
val len = file.length()
|
||||
if (len > MediaConstraints.MAX_MEDIA_BYTES) {
|
||||
if (deleteIfTooLarge) runCatching { file.delete() }
|
||||
toastTooLarge(context, label)
|
||||
false
|
||||
} else true
|
||||
} catch (_: Exception) { true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,18 +150,8 @@ class ChatViewModel(
|
||||
}
|
||||
|
||||
fun cancelMediaSend(messageId: String) {
|
||||
val transferId = synchronized(transferMessageMap) { messageTransferMap[messageId] }
|
||||
if (transferId != null) {
|
||||
val cancelled = meshService.cancelFileTransfer(transferId)
|
||||
if (cancelled) {
|
||||
// Remove the message from chat upon explicit cancel
|
||||
messageManager.removeMessageById(messageId)
|
||||
synchronized(transferMessageMap) {
|
||||
transferMessageMap.remove(transferId)
|
||||
messageTransferMap.remove(messageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delegate to MediaSendingManager which maintains the transferId<->messageId mapping
|
||||
mediaSendingManager.cancelMediaSend(messageId)
|
||||
}
|
||||
|
||||
private fun loadAndInitialize() {
|
||||
|
||||
@@ -20,7 +20,6 @@ class MediaSendingManager(
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "MediaSendingManager"
|
||||
private const val MAX_FILE_SIZE = 50 * 1024 * 1024 // 50MB limit
|
||||
}
|
||||
|
||||
// Track in-flight transfer progress: transferId -> messageId and reverse
|
||||
@@ -39,8 +38,8 @@ class MediaSendingManager(
|
||||
}
|
||||
Log.d(TAG, "📁 File exists: size=${file.length()} bytes, name=${file.name}")
|
||||
|
||||
if (file.length() > MAX_FILE_SIZE) {
|
||||
Log.e(TAG, "❌ File too large: ${file.length()} bytes (max: $MAX_FILE_SIZE)")
|
||||
if (file.length() > com.bitchat.android.features.media.MediaConstraints.MAX_MEDIA_BYTES) {
|
||||
Log.e(TAG, "❌ File too large: ${file.length()} bytes (max: ${com.bitchat.android.features.media.MediaConstraints.MAX_MEDIA_BYTES})")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -74,8 +73,8 @@ class MediaSendingManager(
|
||||
}
|
||||
Log.d(TAG, "📁 File exists: size=${file.length()} bytes, name=${file.name}")
|
||||
|
||||
if (file.length() > MAX_FILE_SIZE) {
|
||||
Log.e(TAG, "❌ File too large: ${file.length()} bytes (max: $MAX_FILE_SIZE)")
|
||||
if (file.length() > com.bitchat.android.features.media.MediaConstraints.MAX_MEDIA_BYTES) {
|
||||
Log.e(TAG, "❌ File too large: ${file.length()} bytes (max: ${com.bitchat.android.features.media.MediaConstraints.MAX_MEDIA_BYTES})")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,8 +111,8 @@ class MediaSendingManager(
|
||||
}
|
||||
Log.d(TAG, "📁 File exists: size=${file.length()} bytes, name=${file.name}")
|
||||
|
||||
if (file.length() > MAX_FILE_SIZE) {
|
||||
Log.e(TAG, "❌ File too large: ${file.length()} bytes (max: $MAX_FILE_SIZE)")
|
||||
if (file.length() > com.bitchat.android.features.media.MediaConstraints.MAX_MEDIA_BYTES) {
|
||||
Log.e(TAG, "❌ File too large: ${file.length()} bytes (max: ${com.bitchat.android.features.media.MediaConstraints.MAX_MEDIA_BYTES})")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -276,6 +275,11 @@ class MediaSendingManager(
|
||||
val cancelled = meshService.cancelFileTransfer(transferId)
|
||||
if (cancelled) {
|
||||
// Remove the message from chat upon explicit cancel
|
||||
// Also attempt to delete the associated outgoing file
|
||||
runCatching { findMessagePathById(messageId) }.
|
||||
getOrNull()?.let { path ->
|
||||
try { java.io.File(path).takeIf { it.exists() }?.delete() } catch (_: Exception) {}
|
||||
}
|
||||
messageManager.removeMessageById(messageId)
|
||||
synchronized(transferMessageMap) {
|
||||
transferMessageMap.remove(transferId)
|
||||
@@ -285,6 +289,20 @@ class MediaSendingManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun findMessagePathById(messageId: String): String? {
|
||||
// Check main messages
|
||||
state.getMessagesValue().firstOrNull { it.id == messageId }?.content?.let { return it }
|
||||
// Check private chats
|
||||
state.getPrivateChatsValue().values.forEach { list ->
|
||||
list.firstOrNull { it.id == messageId }?.content?.let { return it }
|
||||
}
|
||||
// Check channels
|
||||
state.getChannelMessagesValue().values.forEach { list ->
|
||||
list.firstOrNull { it.id == messageId }?.content?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress for a transfer
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitchat.android.features.media.MediaSizeLimiter
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -28,10 +29,12 @@ fun FilePickerButton(
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri: Uri? ->
|
||||
if (uri != null) {
|
||||
// Pre-check size via resolver metadata
|
||||
if (!MediaSizeLimiter.enforceUriPrecheck(context, uri, "File")) return@rememberLauncherForActivityResult
|
||||
// Persist temporary read permission so we can copy
|
||||
try { context.contentResolver.takePersistableUriPermission(uri, android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (_: Exception) {}
|
||||
val path = FileUtils.copyFileForSending(context, uri)
|
||||
if (!path.isNullOrBlank()) onFileReady(path)
|
||||
if (!path.isNullOrBlank() && MediaSizeLimiter.enforcePathPostCheck(context, path, label = "File", deleteIfTooLarge = true)) onFileReady(path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitchat.android.features.media.ImageUtils
|
||||
import com.bitchat.android.features.media.MediaSizeLimiter
|
||||
|
||||
@Composable
|
||||
fun ImagePickerButton(
|
||||
@@ -25,7 +26,7 @@ fun ImagePickerButton(
|
||||
) { uri: android.net.Uri? ->
|
||||
if (uri != null) {
|
||||
val outPath = ImageUtils.downscaleAndSaveToAppFiles(context, uri)
|
||||
if (!outPath.isNullOrBlank()) onImageReady(outPath)
|
||||
if (!outPath.isNullOrBlank() && MediaSizeLimiter.enforcePathPostCheck(context, outPath, label = "Image", deleteIfTooLarge = true)) onImageReady(outPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +42,3 @@ fun ImagePickerButton(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user