diff --git a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt index 41431cd1..68797e08 100644 --- a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt +++ b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt @@ -82,18 +82,42 @@ class MediaSendingManager( private var pendingAutomaticTimeoutRequestId: String? = null /** - * Enforce the send-size cap with a user-visible failure. - * Returns true if the file is oversized and the send was aborted. + * Enforce the send-size cap with a user-visible failure posted to the + * conversation the user is sending from. Returns true if the file is + * oversized and the send was aborted. */ - private fun rejectIfOversized(file: java.io.File): Boolean { + private fun rejectIfOversized( + file: java.io.File, + toPeerIDOrNull: String?, + channelOrNull: String? + ): Boolean { val size = file.length() if (size <= MAX_FILE_SIZE) return false Log.e(TAG, "❌ File too large: $size bytes (max: $MAX_FILE_SIZE)") val sizeMb = size / (1024 * 1024) val maxMb = MAX_FILE_SIZE / (1024 * 1024) - messageManager.addSystemMessage( - "cannot send ${file.name}: file is too large (${sizeMb} MB, max $maxMb MB)" - ) + val text = "cannot send ${file.name}: file is too large (${sizeMb} MB, max $maxMb MB)" + when { + toPeerIDOrNull != null -> { + val sys = BitchatMessage( + sender = "system", + content = text, + timestamp = Date(), + isRelay = false + ) + messageManager.addPrivateMessageNoUnread(toPeerIDOrNull, sys) + } + channelOrNull != null -> { + val sys = BitchatMessage( + sender = "system", + content = text, + timestamp = Date(), + isRelay = false + ) + messageManager.addChannelMessage(channelOrNull, sys) + } + else -> messageManager.addSystemMessage(text) + } return true } @@ -119,7 +143,7 @@ class MediaSendingManager( return@withContext null } - if (rejectIfOversized(file)) { + if (rejectIfOversized(file, toPeerIDOrNull, channelOrNull)) { return@withContext null } @@ -163,7 +187,7 @@ class MediaSendingManager( return@withContext null } - if (rejectIfOversized(file)) { + if (rejectIfOversized(file, toPeerIDOrNull, channelOrNull)) { return@withContext null } @@ -207,7 +231,7 @@ class MediaSendingManager( return@withContext null } - if (rejectIfOversized(file)) { + if (rejectIfOversized(file, toPeerIDOrNull, channelOrNull)) { return@withContext null } diff --git a/app/src/test/kotlin/com/bitchat/android/ui/MediaSendingManagerMigrationTest.kt b/app/src/test/kotlin/com/bitchat/android/ui/MediaSendingManagerMigrationTest.kt index ebe5f744..60c57198 100644 --- a/app/src/test/kotlin/com/bitchat/android/ui/MediaSendingManagerMigrationTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/ui/MediaSendingManagerMigrationTest.kt @@ -287,6 +287,43 @@ class MediaSendingManagerMigrationTest { assertTrue(messages.none { it.type == com.bitchat.android.model.BitchatMessageType.Image }) } + @Test + fun `oversized file failure is posted to the private conversation and nothing is sent`() { + val bigFile = kotlin.io.path.createTempFile("oversized-private", ".jpg").toFile() + try { + bigFile.writeBytes(ByteArray(11 * 1024 * 1024) { 0x42 }) + + manager.sendImageNote(peerID, null, bigFile.absolutePath) + + val messages = state.privateChats.value[peerID].orEmpty() + assertEquals(1, messages.size) + assertTrue(messages.single().content.contains("too large")) + assertTrue(messages.none { it.type == com.bitchat.android.model.BitchatMessageType.Image }) + assertTrue(state.getMessagesValue().none { it.content.contains("too large") }) + verify(mesh, never()).prepareFilePrivate(any(), any(), any(), any()) + } finally { + bigFile.delete() + } + } + + @Test + fun `oversized file failure is posted to the channel and nothing is sent`() { + val bigFile = kotlin.io.path.createTempFile("oversized-channel", ".jpg").toFile() + try { + bigFile.writeBytes(ByteArray(11 * 1024 * 1024) { 0x42 }) + + manager.sendImageNote(null, "#test", bigFile.absolutePath) + + val channelMessages = state.getChannelMessagesValue()["#test"].orEmpty() + assertEquals(1, channelMessages.size) + assertTrue(channelMessages.single().content.contains("too large")) + assertTrue(state.getMessagesValue().none { it.content.contains("too large") }) + verify(mesh, never()).prepareFilePrivate(any(), any(), any(), any()) + } finally { + bigFile.delete() + } + } + @Test fun `cancelled consent cannot later send or echo`() { whenever(mesh.prepareFilePrivate(eq(peerID), any(), any(), eq(false)))