fix: update swipe direction for reply functionality in MessageBubble

This commit is contained in:
k1ngsterr1
2026-02-03 22:56:33 +05:00
parent a7268bb986
commit 44a816dedb
3 changed files with 114 additions and 15 deletions

View File

@@ -321,8 +321,8 @@ fun MessageBubble(
detectHorizontalDragGestures( detectHorizontalDragGestures(
onDragStart = { }, onDragStart = { },
onDragEnd = { onDragEnd = {
// Если свайпнули достаточно вправо - reply // Если свайпнули достаточно влево - reply
if (swipeOffset >= swipeThreshold) { if (swipeOffset <= -swipeThreshold) {
onSwipeToReply() onSwipeToReply()
} }
swipeOffset = 0f swipeOffset = 0f
@@ -331,20 +331,20 @@ fun MessageBubble(
swipeOffset = 0f swipeOffset = 0f
}, },
onHorizontalDrag = { change, dragAmount -> onHorizontalDrag = { change, dragAmount ->
// Только свайп вправо (положительное значение) // Только свайп влево (отрицательное значение)
if (dragAmount > 0 || swipeOffset > 0) { if (dragAmount < 0 || swipeOffset < 0) {
change.consume() change.consume()
val newOffset = swipeOffset + dragAmount val newOffset = swipeOffset + dragAmount
swipeOffset = newOffset.coerceIn(0f, maxSwipe) swipeOffset = newOffset.coerceIn(-maxSwipe, 0f)
} }
} }
) )
} }
) { ) {
// 🔥 Reply icon - слева, появляется при свайпе вправо // 🔥 Reply icon - справа, появляется при свайпе влево
Box( Box(
modifier = modifier =
Modifier.align(Alignment.CenterStart).padding(start = 16.dp).graphicsLayer { Modifier.align(Alignment.CenterEnd).padding(end = 16.dp).graphicsLayer {
alpha = swipeProgress alpha = swipeProgress
scaleX = swipeProgress scaleX = swipeProgress
scaleY = swipeProgress scaleY = swipeProgress

View File

@@ -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( private suspend fun saveEditedImage(
context: Context, context: Context,
photoEditor: PhotoEditor?, photoEditor: PhotoEditor?,
photoEditorView: PhotoEditorView?, photoEditorView: PhotoEditorView?,
imageUri: Uri, imageUri: Uri,
onResult: (Uri?) -> Unit 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) { 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( private suspend fun saveEditedImageSync(
context: Context, context: Context,
photoEditor: PhotoEditor?, photoEditor: PhotoEditor?,
photoEditorView: PhotoEditorView?, photoEditorView: PhotoEditorView?,
imageUri: Uri 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? { ): Uri? {
if (photoEditor == null || photoEditorView == null) { if (photoEditor == null || photoEditorView == null) {
// Нет редактора - возвращаем оригинал // Нет редактора - возвращаем оригинал

View File

@@ -3,8 +3,10 @@ package com.rosetta.messenger.utils
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import androidx.exifinterface.media.ExifInterface
import com.vanniktech.blurhash.BlurHash import com.vanniktech.blurhash.BlurHash
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -28,28 +30,33 @@ object MediaUtils {
/** /**
* Конвертировать изображение из Uri в Base64 PNG * Конвертировать изображение из Uri в Base64 PNG
* Автоматически сжимает большие изображения * Автоматически сжимает большие изображения и учитывает EXIF ориентацию
*/ */
suspend fun uriToBase64Image(context: Context, uri: Uri): String? = withContext(Dispatchers.IO) { suspend fun uriToBase64Image(context: Context, uri: Uri): String? = withContext(Dispatchers.IO) {
try { try {
// Читаем EXIF ориентацию
val orientation = getExifOrientation(context, uri)
// Открываем InputStream // Открываем InputStream
val inputStream: InputStream = context.contentResolver.openInputStream(uri) val inputStream: InputStream = context.contentResolver.openInputStream(uri)
?: return@withContext null ?: return@withContext null
// Декодируем изображение // Декодируем изображение
val originalBitmap = BitmapFactory.decodeStream(inputStream) var bitmap = BitmapFactory.decodeStream(inputStream)
inputStream.close() inputStream.close()
if (originalBitmap == null) { if (bitmap == null) {
return@withContext null return@withContext null
} }
// Применяем EXIF ориентацию (поворот/отражение)
bitmap = applyExifOrientation(bitmap, orientation)
// Масштабируем если слишком большое // Масштабируем если слишком большое
val scaledBitmap = scaleDownBitmap(originalBitmap, MAX_IMAGE_SIZE) val scaledBitmap = scaleDownBitmap(bitmap, MAX_IMAGE_SIZE)
if (scaledBitmap != originalBitmap) { if (scaledBitmap != bitmap) {
originalBitmap.recycle() 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 для изображения * Генерировать Blurhash для изображения
*/ */
@@ -203,6 +263,7 @@ object MediaUtils {
/** /**
* Получить размеры изображения из Uri без полной загрузки в память * Получить размеры изображения из Uri без полной загрузки в память
* Учитывает EXIF ориентацию для правильных width/height
*/ */
fun getImageDimensions(context: Context, uri: Uri): Pair<Int, Int> { fun getImageDimensions(context: Context, uri: Uri): Pair<Int, Int> {
return try { return try {
@@ -216,6 +277,17 @@ object MediaUtils {
var width = options.outWidth var width = options.outWidth
var height = options.outHeight 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) // Учитываем масштабирование (как в uriToBase64Image)
if (width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE) { if (width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE) {
val ratio = width.toFloat() / height.toFloat() val ratio = width.toFloat() / height.toFloat()