From 3844875d3658eb343e6381fb9cdf51bbc5c9bf88 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 10 Mar 2026 21:06:07 +0300 Subject: [PATCH] fix(android): skip reactions for temp messages and fallback gif upload --- .../repository/NetworkMediaRepository.kt | 51 ++++++++++++++++++- .../messenger/ui/chat/ChatViewModel.kt | 2 + 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepository.kt b/android/app/src/main/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepository.kt index ced5890..0849620 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepository.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepository.kt @@ -2,6 +2,8 @@ package ru.daemonlord.messenger.data.media.repository import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Movie import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -96,7 +98,26 @@ class NetworkMediaRepository @Inject constructor( return UploadPayload(fileName = fileName, mimeType = mimeType, bytes = bytes) } if (mimeType.equals("image/gif", ignoreCase = true)) { - return UploadPayload(fileName = fileName, mimeType = mimeType, bytes = bytes) + val still = gifToPngPayload(fileName = fileName, bytes = bytes) + if (still != null) return still + val bitmapFallback = runCatching { BitmapFactory.decodeByteArray(bytes, 0, bytes.size) }.getOrNull() + if (bitmapFallback != null) { + val output = ByteArrayOutputStream() + val compressed = bitmapFallback.compress(Bitmap.CompressFormat.PNG, 100, output) + bitmapFallback.recycle() + if (compressed) { + val pngBytes = output.toByteArray() + if (pngBytes.isNotEmpty()) { + val baseName = fileName.substringBeforeLast('.').ifBlank { "gif" } + return UploadPayload( + fileName = "${baseName}-still.png", + mimeType = "image/png", + bytes = pngBytes, + ) + } + } + } + return UploadPayload(fileName = fileName, mimeType = "application/octet-stream", bytes = bytes) } val source = runCatching { BitmapFactory.decodeByteArray(bytes, 0, bytes.size) }.getOrNull() ?: return UploadPayload(fileName = fileName, mimeType = mimeType, bytes = bytes) @@ -140,4 +161,32 @@ class NetworkMediaRepository @Inject constructor( val mimeType: String, val bytes: ByteArray, ) + + private fun gifToPngPayload( + fileName: String, + bytes: ByteArray, + ): UploadPayload? { + val movie = runCatching { Movie.decodeByteArray(bytes, 0, bytes.size) }.getOrNull() ?: return null + val width = movie.width().coerceAtLeast(1) + val height = movie.height().coerceAtLeast(1) + val bitmap = runCatching { Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) }.getOrNull() ?: return null + return try { + val canvas = Canvas(bitmap) + movie.setTime(0) + movie.draw(canvas, 0f, 0f) + val output = ByteArrayOutputStream() + val compressed = bitmap.compress(Bitmap.CompressFormat.PNG, 100, output) + if (!compressed) return null + val pngBytes = output.toByteArray() + if (pngBytes.isEmpty()) return null + val baseName = fileName.substringBeforeLast('.').ifBlank { "gif" } + UploadPayload( + fileName = "${baseName}-still.png", + mimeType = "image/png", + bytes = pngBytes, + ) + } finally { + bitmap.recycle() + } + } } diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt index 30dc0f8..4fadca0 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt @@ -650,6 +650,7 @@ class ChatViewModel @Inject constructor( } private fun loadReactions(messageId: Long) { + if (messageId <= 0L) return viewModelScope.launch { when (val result = listMessageReactionsUseCase(messageId = messageId)) { is AppResult.Success -> { @@ -666,6 +667,7 @@ class ChatViewModel @Inject constructor( val toRequest = messages .asReversed() .map { it.id } + .filter { it > 0L } .filter { reactionsRequestedMessageIds.add(it) } if (toRequest.isEmpty()) return