mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 12:53:18 +00:00
Fix IndexOutOfBounds crash when sending media with an undecodable preview (#7050)
* android, desktop: skip media with no decodable preview to fix IndexOutOfBounds on send processPickedMedia appended to MediaPreview.content unconditionally for videos (and size-ok animated images) but only appended to images when a preview bitmap existed. getBitmapFromVideo returns a null preview for undecodable/corrupted videos without throwing, desyncing the two lists; sendMessageAsync then indexes images[index] past its end and crashes. Pair both appends behind a non-null bitmap so the lists stay equal-length and index-aligned, and skip only the bad item so the rest of the picked batch still sends. A video skipped this way shows showVideoDecodingException(), guarded by hasAlertsShown() so it neither stacks across multiple bad items nor duplicates the alert already shown on getBitmapFromVideo's exception path. Image decode failures are already surfaced earlier by getBitmapFromUri. * docs: add plan justifying media preview alignment fix
This commit is contained in:
1 parent
a4cfa2ae97
commit
cf6a60882f
2 files changed
+119
-5
No files matched your search
+19
-5
@@ -330,7 +330,7 @@ suspend fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text:
|
||||
val imagesPreview = ArrayList<String>()
|
||||
uris.forEach { uri ->
|
||||
var bitmap: ImageBitmap?
|
||||
when {
|
||||
val uploadContent: UploadContent? = when {
|
||||
isImage(uri) -> {
|
||||
// Image
|
||||
val drawable = getDrawableFromUri(uri)
|
||||
@@ -340,16 +340,19 @@ suspend fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text:
|
||||
// It's a gif or webp
|
||||
val fileSize = getFileSize(uri)
|
||||
if (fileSize != null && fileSize <= maxFileSize) {
|
||||
content.add(UploadContent.AnimatedImage(uri))
|
||||
UploadContent.AnimatedImage(uri)
|
||||
} else {
|
||||
bitmap = null
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.large_file),
|
||||
String.format(generalGetString(MR.strings.maximum_supported_file_size), formatBytes(maxFileSize))
|
||||
)
|
||||
null
|
||||
}
|
||||
} else if (bitmap != null) {
|
||||
content.add(UploadContent.SimpleImage(uri))
|
||||
UploadContent.SimpleImage(uri)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@@ -357,11 +360,22 @@ suspend fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text:
|
||||
val res = getBitmapFromVideo(uri, withAlertOnException = true)
|
||||
bitmap = res.preview
|
||||
val durationMs = res.duration
|
||||
content.add(UploadContent.Video(uri, durationMs?.div(1000)?.toInt() ?: 0))
|
||||
UploadContent.Video(uri, durationMs?.div(1000)?.toInt() ?: 0)
|
||||
}
|
||||
}
|
||||
if (bitmap != null) {
|
||||
// content and imagesPreview must stay index-aligned and equal-length: both consumers
|
||||
// (ComposeImageView and sendMessageAsync) cross-index one list by the other's index.
|
||||
// Only pair them when a preview bitmap exists; otherwise skip the media entirely.
|
||||
if (bitmap != null && uploadContent != null) {
|
||||
content.add(uploadContent)
|
||||
imagesPreview.add(resizeImageToStrSize(bitmap, maxDataSize = 14000))
|
||||
} else if (uploadContent is UploadContent.Video && !AlertManager.shared.hasAlertsShown()) {
|
||||
// A corrupted/undecodable video can yield a null preview frame without throwing, so
|
||||
// getBitmapFromVideo shows no alert. Skip it (other picked media still send) and tell
|
||||
// the user instead of dropping it silently. hasAlertsShown guards against stacking the
|
||||
// alert across multiple bad items and against duplicating the one already shown on the
|
||||
// exception path. Image decode failures are already surfaced by getBitmapFromUri above.
|
||||
showVideoDecodingException()
|
||||
}
|
||||
}
|
||||
if (imagesPreview.isNotEmpty()) {
|
||||
|
||||
Reference in new issue
Block a user