fix: update swipe direction for reply functionality in MessageBubble
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
// Нет редактора - возвращаем оригинал
|
||||
|
||||
@@ -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<Int, Int> {
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user