From 44a816dedb9eaf4e80ff8f8c0abe6a4390ffb485 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Tue, 3 Feb 2026 22:56:33 +0500 Subject: [PATCH] fix: update swipe direction for reply functionality in MessageBubble --- .../chats/components/ChatDetailComponents.kt | 14 ++-- .../ui/chats/components/ImageEditorScreen.kt | 31 ++++++- .../com/rosetta/messenger/utils/MediaUtils.kt | 84 +++++++++++++++++-- 3 files changed, 114 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt index 0064459..f01e6c9 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt @@ -321,8 +321,8 @@ fun MessageBubble( detectHorizontalDragGestures( onDragStart = { }, onDragEnd = { - // Если свайпнули достаточно вправо - reply - if (swipeOffset >= swipeThreshold) { + // Если свайпнули достаточно влево - reply + if (swipeOffset <= -swipeThreshold) { onSwipeToReply() } swipeOffset = 0f @@ -331,20 +331,20 @@ fun MessageBubble( swipeOffset = 0f }, onHorizontalDrag = { change, dragAmount -> - // Только свайп вправо (положительное значение) - if (dragAmount > 0 || swipeOffset > 0) { + // Только свайп влево (отрицательное значение) + if (dragAmount < 0 || swipeOffset < 0) { change.consume() val newOffset = swipeOffset + dragAmount - swipeOffset = newOffset.coerceIn(0f, maxSwipe) + swipeOffset = newOffset.coerceIn(-maxSwipe, 0f) } } ) } ) { - // 🔥 Reply icon - слева, появляется при свайпе вправо + // 🔥 Reply icon - справа, появляется при свайпе влево Box( modifier = - Modifier.align(Alignment.CenterStart).padding(start = 16.dp).graphicsLayer { + Modifier.align(Alignment.CenterEnd).padding(end = 16.dp).graphicsLayer { alpha = swipeProgress scaleX = swipeProgress scaleY = swipeProgress diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageEditorScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageEditorScreen.kt index 603a725..b434156 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageEditorScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageEditorScreen.kt @@ -1158,13 +1158,27 @@ private fun TelegramCaptionBar( } } -/** Save edited image and return the URI - returns original if no edits, otherwise crops black bars */ +/** Save edited image and return the URI - returns ORIGINAL without processing */ private suspend fun saveEditedImage( context: Context, photoEditor: PhotoEditor?, photoEditorView: PhotoEditorView?, imageUri: Uri, onResult: (Uri?) -> Unit +) { + // Просто возвращаем оригинальный URI без обработки через PhotoEditor + // Это устраняет проблему с обрезкой изображений + onResult(imageUri) +} + +/** OLD VERSION - Save edited image with crop logic (disabled due to cropping issues) */ +@Suppress("unused") +private suspend fun saveEditedImageOld( + context: Context, + photoEditor: PhotoEditor?, + photoEditorView: PhotoEditorView?, + imageUri: Uri, + onResult: (Uri?) -> Unit ) { if (photoEditor == null || photoEditorView == null) { // Нет редактора - возвращаем оригинал @@ -1282,12 +1296,25 @@ private suspend fun saveEditedImage( } } -/** Save edited image synchronously - returns original if no edits, otherwise crops black bars */ +/** Save edited image synchronously - returns ORIGINAL URI without any processing */ private suspend fun saveEditedImageSync( context: Context, photoEditor: PhotoEditor?, photoEditorView: PhotoEditorView?, imageUri: Uri +): Uri? { + // Просто возвращаем оригинальный URI без обработки + // PhotoEditor вызывает проблемы с обрезкой - пользователь получает оригинал + return imageUri +} + +/** Save edited image synchronously - OLD VERSION with crop logic (disabled) */ +@Suppress("unused") +private suspend fun saveEditedImageSyncOld( + context: Context, + photoEditor: PhotoEditor?, + photoEditorView: PhotoEditorView?, + imageUri: Uri ): Uri? { if (photoEditor == null || photoEditorView == null) { // Нет редактора - возвращаем оригинал diff --git a/app/src/main/java/com/rosetta/messenger/utils/MediaUtils.kt b/app/src/main/java/com/rosetta/messenger/utils/MediaUtils.kt index 8828a36..e193218 100644 --- a/app/src/main/java/com/rosetta/messenger/utils/MediaUtils.kt +++ b/app/src/main/java/com/rosetta/messenger/utils/MediaUtils.kt @@ -3,8 +3,10 @@ package com.rosetta.messenger.utils import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Matrix import android.net.Uri import android.util.Base64 +import androidx.exifinterface.media.ExifInterface import com.vanniktech.blurhash.BlurHash import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -28,28 +30,33 @@ object MediaUtils { /** * Конвертировать изображение из Uri в Base64 PNG - * Автоматически сжимает большие изображения + * Автоматически сжимает большие изображения и учитывает EXIF ориентацию */ suspend fun uriToBase64Image(context: Context, uri: Uri): String? = withContext(Dispatchers.IO) { try { + // Читаем EXIF ориентацию + val orientation = getExifOrientation(context, uri) + // Открываем InputStream val inputStream: InputStream = context.contentResolver.openInputStream(uri) ?: return@withContext null // Декодируем изображение - val originalBitmap = BitmapFactory.decodeStream(inputStream) + var bitmap = BitmapFactory.decodeStream(inputStream) inputStream.close() - if (originalBitmap == null) { + if (bitmap == null) { return@withContext null } + // Применяем EXIF ориентацию (поворот/отражение) + bitmap = applyExifOrientation(bitmap, orientation) // Масштабируем если слишком большое - val scaledBitmap = scaleDownBitmap(originalBitmap, MAX_IMAGE_SIZE) - if (scaledBitmap != originalBitmap) { - originalBitmap.recycle() + val scaledBitmap = scaleDownBitmap(bitmap, MAX_IMAGE_SIZE) + if (scaledBitmap != bitmap) { + bitmap.recycle() } @@ -68,6 +75,59 @@ object MediaUtils { } } + /** + * Читает EXIF ориентацию из изображения + */ + private fun getExifOrientation(context: Context, uri: Uri): Int { + return try { + context.contentResolver.openInputStream(uri)?.use { inputStream -> + val exif = ExifInterface(inputStream) + exif.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL + ) + } ?: ExifInterface.ORIENTATION_NORMAL + } catch (e: Exception) { + ExifInterface.ORIENTATION_NORMAL + } + } + + /** + * Применяет EXIF ориентацию к Bitmap (поворот/отражение) + */ + private fun applyExifOrientation(bitmap: Bitmap, orientation: Int): Bitmap { + val matrix = Matrix() + + when (orientation) { + ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f) + ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f) + ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f) + ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f) + ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f) + ExifInterface.ORIENTATION_TRANSPOSE -> { + matrix.postRotate(90f) + matrix.preScale(-1f, 1f) + } + ExifInterface.ORIENTATION_TRANSVERSE -> { + matrix.postRotate(270f) + matrix.preScale(-1f, 1f) + } + else -> return bitmap // ORIENTATION_NORMAL или неизвестный + } + + return try { + val rotatedBitmap = Bitmap.createBitmap( + bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true + ) + if (rotatedBitmap != bitmap) { + bitmap.recycle() + } + rotatedBitmap + } catch (e: Exception) { + bitmap + } + } + /** * Генерировать Blurhash для изображения */ @@ -203,6 +263,7 @@ object MediaUtils { /** * Получить размеры изображения из Uri без полной загрузки в память + * Учитывает EXIF ориентацию для правильных width/height */ fun getImageDimensions(context: Context, uri: Uri): Pair { return try { @@ -216,6 +277,17 @@ object MediaUtils { var width = options.outWidth var height = options.outHeight + // Учитываем EXIF ориентацию - если поворот 90 или 270, меняем местами width/height + val orientation = getExifOrientation(context, uri) + if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || + orientation == ExifInterface.ORIENTATION_ROTATE_270 || + orientation == ExifInterface.ORIENTATION_TRANSPOSE || + orientation == ExifInterface.ORIENTATION_TRANSVERSE) { + val temp = width + width = height + height = temp + } + // Учитываем масштабирование (как в uriToBase64Image) if (width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE) { val ratio = width.toFloat() / height.toFloat()