fix: close keyboard before dismissing ImageEditorScreen
This commit is contained in:
@@ -2030,6 +2030,18 @@ fun ChatDetailScreen(
|
||||
pendingGalleryImages = imageUris
|
||||
}
|
||||
},
|
||||
onMediaSelectedWithCaption = { mediaItem, caption ->
|
||||
// 📸 Отправляем фото с caption напрямую
|
||||
showMediaPicker = false
|
||||
scope.launch {
|
||||
val base64 = MediaUtils.uriToBase64Image(context, mediaItem.uri)
|
||||
val blurhash = MediaUtils.generateBlurhash(context, mediaItem.uri)
|
||||
val (width, height) = MediaUtils.getImageDimensions(context, mediaItem.uri)
|
||||
if (base64 != null) {
|
||||
viewModel.sendImageMessage(base64, blurhash, caption, width, height)
|
||||
}
|
||||
}
|
||||
},
|
||||
onOpenCamera = {
|
||||
// 📷 Создаём временный файл для фото
|
||||
try {
|
||||
@@ -2054,7 +2066,8 @@ fun ChatDetailScreen(
|
||||
onAvatarClick = {
|
||||
// 👤 Отправляем свой аватар (как в desktop)
|
||||
viewModel.sendAvatarMessage()
|
||||
}
|
||||
},
|
||||
recipientName = user.title
|
||||
)
|
||||
|
||||
// 📷 Image Editor для фото с камеры (с caption как в Telegram)
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
@@ -38,6 +39,8 @@ import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -107,6 +110,8 @@ fun ImageEditorScreen(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val view = LocalView.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
// Editor state
|
||||
var currentTool by remember { mutableStateOf(EditorTool.NONE) }
|
||||
@@ -219,9 +224,22 @@ fun ImageEditorScreen(
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||
) {
|
||||
// Close button (X)
|
||||
// Close button (X) - сначала закрывает клавиатуру, потом экран
|
||||
IconButton(
|
||||
onClick = onDismiss,
|
||||
onClick = {
|
||||
// Проверяем, открыта ли клавиатура
|
||||
val imm = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as android.view.inputmethod.InputMethodManager
|
||||
val isKeyboardOpen = imm.isAcceptingText
|
||||
|
||||
if (isKeyboardOpen) {
|
||||
// Закрываем клавиатуру
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
focusManager.clearFocus()
|
||||
} else {
|
||||
// Закрываем экран
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.align(Alignment.CenterStart)
|
||||
) {
|
||||
Icon(
|
||||
@@ -340,6 +358,7 @@ fun ImageEditorScreen(
|
||||
caption = caption,
|
||||
onCaptionChange = { caption = it },
|
||||
isSaving = isSaving,
|
||||
isKeyboardVisible = isKeyboardVisibleForCaption,
|
||||
onSend = {
|
||||
scope.launch {
|
||||
isSaving = true
|
||||
@@ -673,32 +692,61 @@ private fun TelegramRotateBar(
|
||||
|
||||
/**
|
||||
* Telegram-style caption input bar
|
||||
* Меняет внешний вид в зависимости от состояния клавиатуры:
|
||||
* - Клавиатура закрыта: минимальный стиль (камера + текст + синяя стрелка)
|
||||
* - Клавиатура открыта: полный стиль (emoji + текст + галочка)
|
||||
*/
|
||||
@Composable
|
||||
private fun TelegramCaptionBar(
|
||||
caption: String,
|
||||
onCaptionChange: (String) -> Unit,
|
||||
isSaving: Boolean,
|
||||
isKeyboardVisible: Boolean,
|
||||
onSend: () -> Unit
|
||||
) {
|
||||
// Telegram-style: прямоугольный темный бар с emoji слева и галочкой справа
|
||||
// Анимированный переход между стилями
|
||||
val backgroundAlpha by animateFloatAsState(
|
||||
targetValue = if (isKeyboardVisible) 0.75f else 0f,
|
||||
animationSpec = tween(200, easing = TelegramEasing),
|
||||
label = "background"
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.Black.copy(alpha = 0.75f))
|
||||
.padding(horizontal = 8.dp, vertical = 10.dp),
|
||||
.background(Color.Black.copy(alpha = backgroundAlpha))
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
// Emoji icon (как в Telegram)
|
||||
// Левая иконка: камера когда клавиатура закрыта, emoji когда открыта
|
||||
AnimatedContent(
|
||||
targetState = isKeyboardVisible,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(150)) togetherWith fadeOut(tween(150))
|
||||
},
|
||||
label = "left_icon"
|
||||
) { keyboardOpen ->
|
||||
if (keyboardOpen) {
|
||||
// Клавиатура открыта - emoji иконка
|
||||
Icon(
|
||||
TablerIcons.MoodSmile,
|
||||
contentDescription = "Emoji",
|
||||
tint = Color.White.copy(alpha = 0.7f),
|
||||
modifier = Modifier.size(26.dp)
|
||||
)
|
||||
} else {
|
||||
// Клавиатура закрыта - камера иконка
|
||||
Icon(
|
||||
TablerIcons.Camera,
|
||||
contentDescription = "Camera",
|
||||
tint = Color.White.copy(alpha = 0.7f),
|
||||
modifier = Modifier.size(26.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Caption text field - простой без фона
|
||||
// Caption text field
|
||||
BasicTextField(
|
||||
value = caption,
|
||||
onValueChange = onCaptionChange,
|
||||
@@ -707,7 +755,8 @@ private fun TelegramCaptionBar(
|
||||
color = Color.White,
|
||||
fontSize = 16.sp
|
||||
),
|
||||
maxLines = 4,
|
||||
maxLines = if (isKeyboardVisible) 4 else 1,
|
||||
singleLine = !isKeyboardVisible,
|
||||
decorationBox = { innerTextField ->
|
||||
Box {
|
||||
if (caption.isEmpty()) {
|
||||
@@ -722,10 +771,17 @@ private fun TelegramCaptionBar(
|
||||
}
|
||||
)
|
||||
|
||||
// Send button - голубой кружок с галочкой (как в Telegram)
|
||||
// Кнопка отправки
|
||||
AnimatedContent(
|
||||
targetState = isKeyboardVisible,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(150)) togetherWith fadeOut(tween(150))
|
||||
},
|
||||
label = "send_button"
|
||||
) { keyboardOpen ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.size(if (keyboardOpen) 32.dp else 36.dp)
|
||||
.clip(CircleShape)
|
||||
.background(PrimaryBlue)
|
||||
.clickable(enabled = !isSaving) { onSend() },
|
||||
@@ -733,21 +789,25 @@ private fun TelegramCaptionBar(
|
||||
) {
|
||||
if (isSaving) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(if (keyboardOpen) 18.dp else 20.dp),
|
||||
color = Color.White,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
// Клавиатура открыта - галочка, закрыта - стрелка отправки
|
||||
Icon(
|
||||
TablerIcons.Check,
|
||||
if (keyboardOpen) TablerIcons.Check else TablerIcons.Send,
|
||||
contentDescription = "Send",
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(20.dp)
|
||||
modifier = Modifier
|
||||
.size(if (keyboardOpen) 20.dp else 22.dp)
|
||||
.then(if (!keyboardOpen) Modifier.offset(x = 1.dp) else Modifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Save edited image and return the URI - crops black bars from FIT_CENTER */
|
||||
private suspend fun saveEditedImage(
|
||||
|
||||
@@ -76,11 +76,13 @@ fun MediaPickerBottomSheet(
|
||||
onDismiss: () -> Unit,
|
||||
isDarkTheme: Boolean,
|
||||
onMediaSelected: (List<MediaItem>) -> Unit,
|
||||
onMediaSelectedWithCaption: ((MediaItem, String) -> Unit)? = null, // Для отправки с caption
|
||||
onOpenCamera: () -> Unit = {},
|
||||
onOpenFilePicker: () -> Unit = {},
|
||||
onAvatarClick: () -> Unit = {},
|
||||
currentUserPublicKey: String = "",
|
||||
maxSelection: Int = 10
|
||||
maxSelection: Int = 10,
|
||||
recipientName: String? = null // Имя получателя для отображения в редакторе
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -290,20 +292,14 @@ fun MediaPickerBottomSheet(
|
||||
onOpenCamera()
|
||||
},
|
||||
onItemClick = { item ->
|
||||
// Telegram-style:
|
||||
// - Первый клик по невыбранной фото → выбрать
|
||||
// - Клик по уже выбранной фото → открыть редактор
|
||||
if (item.id in selectedItems) {
|
||||
// Уже выбрана - открываем редактор (только для изображений)
|
||||
// Telegram-style: клик на фото сразу открывает редактор с caption
|
||||
if (!item.isVideo) {
|
||||
editingItem = item
|
||||
} else {
|
||||
// Для видео - снимаем выделение
|
||||
// Для видео - добавляем/убираем из selection
|
||||
if (item.id in selectedItems) {
|
||||
selectedItems = selectedItems - item.id
|
||||
}
|
||||
} else {
|
||||
// Не выбрана - добавляем в selection
|
||||
if (selectedItems.size < maxSelection) {
|
||||
} else if (selectedItems.size < maxSelection) {
|
||||
selectedItems = selectedItems + item.id
|
||||
}
|
||||
}
|
||||
@@ -334,10 +330,35 @@ fun MediaPickerBottomSheet(
|
||||
onDismiss = { editingItem = null },
|
||||
onSave = { editedUri ->
|
||||
editingItem = null
|
||||
// После редактирования открываем предпросмотр с caption
|
||||
// Если нет onMediaSelectedWithCaption - открываем preview
|
||||
if (onMediaSelectedWithCaption == null) {
|
||||
previewPhotoUri = editedUri
|
||||
} else {
|
||||
// Отправляем без caption (если нажали Done вместо Send)
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelected(listOf(mediaItem))
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
isDarkTheme = isDarkTheme
|
||||
onSaveWithCaption = if (onMediaSelectedWithCaption != null) { editedUri, caption ->
|
||||
editingItem = null
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelectedWithCaption(mediaItem, caption)
|
||||
onDismiss()
|
||||
} else null,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showCaptionInput = onMediaSelectedWithCaption != null,
|
||||
recipientName = recipientName
|
||||
)
|
||||
}
|
||||
|
||||
@@ -350,10 +371,35 @@ fun MediaPickerBottomSheet(
|
||||
},
|
||||
onSave = { editedUri ->
|
||||
pendingPhotoUri = null
|
||||
// После редактирования открываем предпросмотр с caption
|
||||
// Если нет onMediaSelectedWithCaption - открываем preview
|
||||
if (onMediaSelectedWithCaption == null) {
|
||||
previewPhotoUri = editedUri
|
||||
} else {
|
||||
// Отправляем без caption
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelected(listOf(mediaItem))
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
isDarkTheme = isDarkTheme
|
||||
onSaveWithCaption = if (onMediaSelectedWithCaption != null) { editedUri, caption ->
|
||||
pendingPhotoUri = null
|
||||
val mediaItem = MediaItem(
|
||||
id = System.currentTimeMillis(),
|
||||
uri = editedUri,
|
||||
mimeType = "image/png",
|
||||
dateModified = System.currentTimeMillis()
|
||||
)
|
||||
onMediaSelectedWithCaption(mediaItem, caption)
|
||||
onDismiss()
|
||||
} else null,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showCaptionInput = onMediaSelectedWithCaption != null,
|
||||
recipientName = recipientName
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user