feat: update notification handling and improve media selection with captions
This commit is contained in:
@@ -57,8 +57,8 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = false
|
||||||
isShrinkResources = true
|
isShrinkResources = false
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
|
|||||||
14
app/proguard-rules.pro
vendored
14
app/proguard-rules.pro
vendored
@@ -95,10 +95,10 @@
|
|||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# R8 VerifyError fix: prevent R8 from generating invalid
|
# R8 VerifyError fix: prevent R8 from generating invalid
|
||||||
# bytecode (instance-of on unexpected class Integer) in
|
# bytecode in app UI lambdas (Compose/coroutine) that interact
|
||||||
# app UI lambdas with primitive boxing/unboxing
|
# with third-party libraries involving primitive boxing/unboxing
|
||||||
# ============================================================
|
# ============================================================
|
||||||
-keep,allowobfuscation class com.rosetta.messenger.ui.** { *; }
|
-keep class com.rosetta.messenger.ui.** { *; }
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Data Models
|
# Data Models
|
||||||
@@ -174,3 +174,11 @@
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
-keep class coil.** { *; }
|
-keep class coil.** { *; }
|
||||||
-dontwarn coil.**
|
-dontwarn coil.**
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PhotoEditor (com.burhanrashid52:photoeditor)
|
||||||
|
# Prevents R8 from generating invalid bytecode (VerifyError:
|
||||||
|
# instance-of on unexpected class Integer) in PhotoEditorImpl
|
||||||
|
# ============================================================
|
||||||
|
-keep class ja.burhanrashid52.photoeditor.** { *; }
|
||||||
|
-dontwarn ja.burhanrashid52.photoeditor.**
|
||||||
|
|||||||
@@ -34,10 +34,21 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
private const val TAG = "RosettaFCM"
|
private const val TAG = "RosettaFCM"
|
||||||
private const val CHANNEL_ID = "rosetta_messages"
|
private const val CHANNEL_ID = "rosetta_messages"
|
||||||
private const val CHANNEL_NAME = "Messages"
|
private const val CHANNEL_NAME = "Messages"
|
||||||
private const val NOTIFICATION_ID = 1
|
|
||||||
|
|
||||||
// 🔥 Флаг - приложение в foreground (видимо пользователю)
|
// 🔥 Флаг - приложение в foreground (видимо пользователю)
|
||||||
@Volatile var isAppInForeground = false
|
@Volatile var isAppInForeground = false
|
||||||
|
|
||||||
|
/** Уникальный notification ID для каждого чата (по publicKey) */
|
||||||
|
fun getNotificationIdForChat(senderPublicKey: String): Int {
|
||||||
|
return senderPublicKey.hashCode() and 0x7FFFFFFF // positive int
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Убрать уведомление конкретного чата из шторки */
|
||||||
|
fun cancelNotificationForChat(context: Context, senderPublicKey: String) {
|
||||||
|
val notificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.cancel(getNotificationIdForChat(senderPublicKey))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Вызывается когда получен новый FCM токен Отправляем его на сервер через протокол */
|
/** Вызывается когда получен новый FCM токен Отправляем его на сервер через протокол */
|
||||||
@@ -97,6 +108,8 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
|
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
|
||||||
|
val notifId = getNotificationIdForChat(senderPublicKey ?: "")
|
||||||
|
|
||||||
// Intent для открытия чата
|
// Intent для открытия чата
|
||||||
val intent =
|
val intent =
|
||||||
Intent(this, MainActivity::class.java).apply {
|
Intent(this, MainActivity::class.java).apply {
|
||||||
@@ -107,7 +120,7 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
val pendingIntent =
|
val pendingIntent =
|
||||||
PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
0,
|
notifId,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
@@ -125,7 +138,7 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
|
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
notificationManager.notify(NOTIFICATION_ID, notification)
|
notificationManager.notify(notifId, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Показать простое уведомление */
|
/** Показать простое уведомление */
|
||||||
@@ -162,7 +175,7 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
|
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
notificationManager.notify(NOTIFICATION_ID, notification)
|
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Создать notification channel для Android 8+ */
|
/** Создать notification channel для Android 8+ */
|
||||||
|
|||||||
@@ -459,6 +459,9 @@ fun ChatDetailScreen(
|
|||||||
Lifecycle.Event.ON_RESUME -> {
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
isScreenActive = true
|
isScreenActive = true
|
||||||
viewModel.setDialogActive(true)
|
viewModel.setDialogActive(true)
|
||||||
|
// 🔥 Убираем уведомление этого чата из шторки
|
||||||
|
com.rosetta.messenger.push.RosettaFirebaseMessagingService
|
||||||
|
.cancelNotificationForChat(context, user.publicKey)
|
||||||
}
|
}
|
||||||
Lifecycle.Event.ON_PAUSE -> {
|
Lifecycle.Event.ON_PAUSE -> {
|
||||||
isScreenActive = false
|
isScreenActive = false
|
||||||
@@ -482,6 +485,9 @@ fun ChatDetailScreen(
|
|||||||
LaunchedEffect(user.publicKey, forwardTrigger) {
|
LaunchedEffect(user.publicKey, forwardTrigger) {
|
||||||
viewModel.setUserKeys(currentUserPublicKey, currentUserPrivateKey)
|
viewModel.setUserKeys(currentUserPublicKey, currentUserPrivateKey)
|
||||||
viewModel.openDialog(user.publicKey, user.title, user.username)
|
viewModel.openDialog(user.publicKey, user.title, user.username)
|
||||||
|
// 🔥 Убираем уведомление этого чата из шторки при заходе
|
||||||
|
com.rosetta.messenger.push.RosettaFirebaseMessagingService
|
||||||
|
.cancelNotificationForChat(context, user.publicKey)
|
||||||
// Подписываемся на онлайн статус собеседника
|
// Подписываемся на онлайн статус собеседника
|
||||||
if (!isSavedMessages) {
|
if (!isSavedMessages) {
|
||||||
viewModel.subscribeToOnlineStatus()
|
viewModel.subscribeToOnlineStatus()
|
||||||
@@ -2065,13 +2071,15 @@ fun ChatDetailScreen(
|
|||||||
onDismiss = { showMediaPicker = false },
|
onDismiss = { showMediaPicker = false },
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
currentUserPublicKey = currentUserPublicKey,
|
currentUserPublicKey = currentUserPublicKey,
|
||||||
onMediaSelected = { selectedMedia ->
|
onMediaSelected = { selectedMedia, caption ->
|
||||||
// 📸 Открываем edit screen для выбранных изображений
|
// 📸 Отправляем фото напрямую с caption
|
||||||
val imageUris =
|
val imageUris =
|
||||||
selectedMedia.filter { !it.isVideo }.map { it.uri }
|
selectedMedia.filter { !it.isVideo }.map { it.uri }
|
||||||
|
|
||||||
if (imageUris.isNotEmpty()) {
|
if (imageUris.isNotEmpty()) {
|
||||||
pendingGalleryImages = imageUris
|
showMediaPicker = false
|
||||||
|
inputFocusTrigger++
|
||||||
|
viewModel.sendImageGroupFromUris(imageUris, caption)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMediaSelectedWithCaption = { mediaItem, caption ->
|
onMediaSelectedWithCaption = { mediaItem, caption ->
|
||||||
|
|||||||
@@ -321,13 +321,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Добавляем все сразу
|
// Добавляем все сразу
|
||||||
kotlinx.coroutines.withContext(Dispatchers.Main.immediate) {
|
kotlinx.coroutines.withContext(Dispatchers.Main.immediate) {
|
||||||
val currentList = _messages.value
|
val currentList = _messages.value
|
||||||
val newList = (currentList + newMessages).sortedBy { it.timestamp }
|
val newList = (currentList + newMessages).distinctBy { it.id }.sortedBy { it.timestamp }
|
||||||
|
|
||||||
// 🔍 DEBUG: Проверка на дублирующиеся ID
|
|
||||||
val allIds = newList.map { it.id }
|
|
||||||
val duplicates = allIds.groupBy { it }.filter { it.value.size > 1 }.keys
|
|
||||||
if (duplicates.isNotEmpty()) {}
|
|
||||||
|
|
||||||
_messages.value = newList
|
_messages.value = newList
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,7 +794,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
// 🔥 ДОБАВЛЯЕМ новые к текущим, а не заменяем!
|
// 🔥 ДОБАВЛЯЕМ новые к текущим, а не заменяем!
|
||||||
// Сортируем по timestamp чтобы новые были в конце
|
// Сортируем по timestamp чтобы новые были в конце
|
||||||
val updatedMessages = (currentMessages + newMessages).sortedBy { it.timestamp }
|
val updatedMessages = (currentMessages + newMessages).distinctBy { it.id }.sortedBy { it.timestamp }
|
||||||
|
|
||||||
// 🔥 ИСПРАВЛЕНИЕ: Обновляем кэш сохраняя ВСЕ сообщения, не только отображаемые!
|
// 🔥 ИСПРАВЛЕНИЕ: Обновляем кэш сохраняя ВСЕ сообщения, не только отображаемые!
|
||||||
// Объединяем существующий кэш с новыми сообщениями
|
// Объединяем существующий кэш с новыми сообщениями
|
||||||
@@ -912,7 +906,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
// Добавляем в начало списка (старые сообщения)
|
// Добавляем в начало списка (старые сообщения)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_messages.value = newMessages + _messages.value
|
_messages.value = (newMessages + _messages.value).distinctBy { it.id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ fun ImageCollage(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(spacing)
|
horizontalArrangement = Arrangement.spacedBy(spacing)
|
||||||
) {
|
) {
|
||||||
attachments.forEachIndexed { index, attachment ->
|
attachments.forEachIndexed { index, attachment ->
|
||||||
Box(modifier = Modifier.weight(1f)) {
|
Box(modifier = Modifier.weight(1f).aspectRatio(1f)) {
|
||||||
ImageAttachment(
|
ImageAttachment(
|
||||||
attachment = attachment,
|
attachment = attachment,
|
||||||
chachaKey = chachaKey,
|
chachaKey = chachaKey,
|
||||||
@@ -441,7 +441,7 @@ fun ImageCollage(
|
|||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
messageStatus = messageStatus,
|
messageStatus = messageStatus,
|
||||||
showTimeOverlay = showOverlayOnLast && index == count - 1,
|
showTimeOverlay = showOverlayOnLast && index == count - 1,
|
||||||
aspectRatio = 1f,
|
fillMaxSize = true,
|
||||||
isSelectionMode = isSelectionMode,
|
isSelectionMode = isSelectionMode,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
onImageClick = onImageClick
|
onImageClick = onImageClick
|
||||||
|
|||||||
@@ -1634,7 +1634,14 @@ fun MultiImageEditorScreen(
|
|||||||
setPadding(0, 0, 0, 0)
|
setPadding(0, 0, 0, 0)
|
||||||
setBackgroundColor(android.graphics.Color.BLACK)
|
setBackgroundColor(android.graphics.Color.BLACK)
|
||||||
|
|
||||||
// Загружаем изображение
|
// Инициализация PhotoEditor синхронно в factory (уже на main thread)
|
||||||
|
val editor = PhotoEditor.Builder(ctx, this)
|
||||||
|
.setPinchTextScalable(true)
|
||||||
|
.setClipSourceImage(true)
|
||||||
|
.build()
|
||||||
|
photoEditors[page] = editor
|
||||||
|
|
||||||
|
// Загружаем изображение асинхронно
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val bitmap = loadBitmapRespectExif(ctx, imagesWithCaptions[page].uri)
|
val bitmap = loadBitmapRespectExif(ctx, imagesWithCaptions[page].uri)
|
||||||
@@ -1648,12 +1655,6 @@ fun MultiImageEditorScreen(
|
|||||||
adjustViewBounds = true
|
adjustViewBounds = true
|
||||||
setPadding(0, 0, 0, 0)
|
setPadding(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val editor = PhotoEditor.Builder(ctx, this@apply)
|
|
||||||
.setPinchTextScalable(true)
|
|
||||||
.setClipSourceImage(true)
|
|
||||||
.build()
|
|
||||||
photoEditors[page] = editor
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Handle error
|
// Handle error
|
||||||
@@ -1737,20 +1738,20 @@ fun MultiImageEditorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom section - без imePadding, фото не сжимается
|
// Bottom section
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.background(
|
.background(
|
||||||
Brush.verticalGradient(
|
Brush.verticalGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
Color.Transparent,
|
Color.Transparent,
|
||||||
Color.Black.copy(alpha = 0.7f)
|
Color.Black.copy(alpha = 0.7f)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) {
|
)
|
||||||
|
) {
|
||||||
// Color picker
|
// Color picker
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = showColorPicker && currentTool == EditorTool.DRAW,
|
visible = showColorPicker && currentTool == EditorTool.DRAW,
|
||||||
@@ -1862,7 +1863,6 @@ fun MultiImageEditorScreen(
|
|||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(PrimaryBlue)
|
.background(PrimaryBlue)
|
||||||
.clickable(enabled = !isSaving && !isClosing) {
|
.clickable(enabled = !isSaving && !isClosing) {
|
||||||
// 🚀 Сохраняем копию данных перед анимацией
|
|
||||||
val imagesToSend = imagesWithCaptions.toList()
|
val imagesToSend = imagesWithCaptions.toList()
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -1886,11 +1886,8 @@ fun MultiImageEditorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вызываем callback (он запустит sendImageGroup с optimistic UI)
|
|
||||||
onSendAll(savedImages)
|
onSendAll(savedImages)
|
||||||
|
|
||||||
isSaving = false
|
isSaving = false
|
||||||
// Закрываем после завершения сохранения/отправки
|
|
||||||
animatedDismiss()
|
animatedDismiss()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1900,14 +1897,13 @@ fun MultiImageEditorScreen(
|
|||||||
TablerIcons.ArrowUp,
|
TablerIcons.ArrowUp,
|
||||||
contentDescription = "Send",
|
contentDescription = "Send",
|
||||||
tint = Color.White,
|
tint = Color.White,
|
||||||
modifier = Modifier
|
modifier = Modifier.size(22.dp)
|
||||||
.size(22.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ fun MediaPickerBottomSheet(
|
|||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onMediaSelected: (List<MediaItem>) -> Unit,
|
onMediaSelected: (List<MediaItem>, String) -> Unit,
|
||||||
onMediaSelectedWithCaption: ((MediaItem, String) -> Unit)? = null,
|
onMediaSelectedWithCaption: ((MediaItem, String) -> Unit)? = null,
|
||||||
onOpenCamera: () -> Unit = {},
|
onOpenCamera: () -> Unit = {},
|
||||||
onOpenFilePicker: () -> Unit = {},
|
onOpenFilePicker: () -> Unit = {},
|
||||||
@@ -151,6 +151,10 @@ fun MediaPickerBottomSheet(
|
|||||||
// Caption для фото
|
// Caption для фото
|
||||||
var photoCaption by remember { mutableStateOf("") }
|
var photoCaption by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
// Caption для группы фото (внизу picker'а)
|
||||||
|
var pickerCaption by remember { mutableStateOf("") }
|
||||||
|
var captionEditTextView by remember { mutableStateOf<com.rosetta.messenger.ui.components.AppleEmojiEditTextView?>(null) }
|
||||||
|
|
||||||
// Permission launcher
|
// Permission launcher
|
||||||
val permissionLauncher = rememberLauncherForActivityResult(
|
val permissionLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.RequestMultiplePermissions()
|
contract = ActivityResultContracts.RequestMultiplePermissions()
|
||||||
@@ -426,7 +430,7 @@ fun MediaPickerBottomSheet(
|
|||||||
alignment = Alignment.TopStart, // Начинаем с верха чтобы покрыть весь экран
|
alignment = Alignment.TopStart, // Начинаем с верха чтобы покрыть весь экран
|
||||||
onDismissRequest = animatedClose,
|
onDismissRequest = animatedClose,
|
||||||
properties = PopupProperties(
|
properties = PopupProperties(
|
||||||
focusable = false,
|
focusable = true,
|
||||||
dismissOnBackPress = true,
|
dismissOnBackPress = true,
|
||||||
dismissOnClickOutside = false
|
dismissOnClickOutside = false
|
||||||
)
|
)
|
||||||
@@ -444,6 +448,7 @@ fun MediaPickerBottomSheet(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.imePadding()
|
||||||
.background(Color.Black.copy(alpha = scrimAlpha))
|
.background(Color.Black.copy(alpha = scrimAlpha))
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
@@ -523,7 +528,7 @@ fun MediaPickerBottomSheet(
|
|||||||
onDismiss = animatedClose,
|
onDismiss = animatedClose,
|
||||||
onSend = {
|
onSend = {
|
||||||
val selected = mediaItems.filter { it.id in selectedItems }
|
val selected = mediaItems.filter { it.id in selectedItems }
|
||||||
onMediaSelected(selected)
|
onMediaSelected(selected, pickerCaption.trim())
|
||||||
animatedClose()
|
animatedClose()
|
||||||
},
|
},
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
@@ -648,6 +653,89 @@ fun MediaPickerBottomSheet(
|
|||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Caption bar (видна когда есть выбранные фото)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = selectedItems.isNotEmpty(),
|
||||||
|
enter = fadeIn() + expandVertically(expandFrom = Alignment.Bottom),
|
||||||
|
exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Bottom)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
// Caption input (pill shape)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(20.dp))
|
||||||
|
.background(if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFEFEFF0))
|
||||||
|
.clickable {
|
||||||
|
// Программно открываем клавиатуру при клике на pill
|
||||||
|
captionEditTextView?.let { view ->
|
||||||
|
view.requestFocus()
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
// Emoji button
|
||||||
|
Icon(
|
||||||
|
painter = TelegramIcons.Smile,
|
||||||
|
contentDescription = "Emoji",
|
||||||
|
tint = if (isDarkTheme) Color.White.copy(alpha = 0.5f) else Color.Gray,
|
||||||
|
modifier = Modifier.size(22.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Caption text field
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.heightIn(min = 22.dp, max = 100.dp)
|
||||||
|
) {
|
||||||
|
AppleEmojiTextField(
|
||||||
|
value = pickerCaption,
|
||||||
|
onValueChange = { pickerCaption = it },
|
||||||
|
textColor = if (isDarkTheme) Color.White else Color.Black,
|
||||||
|
textSize = 16f,
|
||||||
|
hint = "Add a caption...",
|
||||||
|
hintColor = if (isDarkTheme) Color.White.copy(alpha = 0.35f) else Color.Gray.copy(alpha = 0.5f),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
requestFocus = false,
|
||||||
|
onViewCreated = { captionEditTextView = it },
|
||||||
|
onFocusChanged = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send button
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(42.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(PrimaryBlue)
|
||||||
|
.clickable {
|
||||||
|
val selected = mediaItems.filter { it.id in selectedItems }
|
||||||
|
onMediaSelected(selected, pickerCaption.trim())
|
||||||
|
animatedClose()
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = TelegramIcons.Send,
|
||||||
|
contentDescription = "Send",
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -730,7 +818,7 @@ fun MediaPickerBottomSheet(
|
|||||||
mimeType = "image/png",
|
mimeType = "image/png",
|
||||||
dateModified = System.currentTimeMillis()
|
dateModified = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
onMediaSelected(listOf(mediaItem))
|
onMediaSelected(listOf(mediaItem), "")
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -776,7 +864,7 @@ fun MediaPickerBottomSheet(
|
|||||||
mimeType = "image/png",
|
mimeType = "image/png",
|
||||||
dateModified = System.currentTimeMillis()
|
dateModified = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
onMediaSelected(listOf(mediaItem))
|
onMediaSelected(listOf(mediaItem), "")
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -811,7 +899,7 @@ fun MediaPickerBottomSheet(
|
|||||||
dateModified = System.currentTimeMillis()
|
dateModified = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
// Отправляем фото (caption можно передать отдельно если нужно)
|
// Отправляем фото (caption можно передать отдельно если нужно)
|
||||||
onMediaSelected(listOf(item))
|
onMediaSelected(listOf(item), "")
|
||||||
previewPhotoUri = null
|
previewPhotoUri = null
|
||||||
photoCaption = ""
|
photoCaption = ""
|
||||||
onDismiss()
|
onDismiss()
|
||||||
|
|||||||
Reference in New Issue
Block a user