From 179f65872ddb5fdc684fdbe3f9e20a16bd4951c4 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Fri, 13 Mar 2026 23:11:51 +0700 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=BD=D0=BE=D0=B6=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=BD=D0=B0=D1=8F=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D1=81=D1=8B=D0=BB=D0=BA=D0=B0=20=D0=B8=20=D0=BF=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20media=20picker=20=D0=B2?= =?UTF-8?q?=20gesture-=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../messenger/ui/chats/ChatDetailScreen.kt | 73 +++++++++- .../messenger/ui/chats/ChatViewModel.kt | 44 +++++- .../ui/chats/attach/ChatAttachAlert.kt | 83 +++++++++-- .../components/MediaPickerBottomSheet.kt | 60 ++++++-- .../ui/chats/input/ChatDetailInput.kt | 137 +++++++++++++++++- 5 files changed, 361 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index edf872d..7760ae6 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -2135,6 +2135,47 @@ fun ChatDetailScreen( viewModel .clearReplyMessages() }, + onShowForwardOptions = { panelMessages -> + if (panelMessages.isEmpty()) { + return@MessageInputBar + } + val forwardMessages = + panelMessages.map { msg -> + ForwardManager.ForwardMessage( + messageId = + msg.messageId, + text = msg.text, + timestamp = + msg.timestamp, + isOutgoing = + msg.isOutgoing, + senderPublicKey = + msg.publicKey.ifEmpty { + if (msg.isOutgoing) currentUserPublicKey + else user.publicKey + }, + originalChatPublicKey = + user.publicKey, + senderName = + msg.senderName.ifEmpty { + if (msg.isOutgoing) currentUserName.ifEmpty { "You" } + else user.title.ifEmpty { user.username.ifEmpty { "User" } } + }, + attachments = + msg.attachments + .filter { + it.type != AttachmentType.MESSAGES + } + .map { it.copy(localUri = "") } + ) + } + + ForwardManager.setForwardMessages( + forwardMessages, + showPicker = false + ) + showForwardPicker = true + }, chatTitle = chatTitle, isBlocked = isBlocked, showEmojiPicker = @@ -3313,12 +3354,40 @@ fun ChatDetailScreen( } val forwardMessages = ForwardManager.consumeForwardMessages() - ForwardManager.clear() if (forwardMessages.isEmpty()) { + ForwardManager.clear() return@ForwardChatPickerBottomSheet } - // Реальная отправка forward во все выбранные чаты. + // Desktop parity: если выбран один чат, не отправляем сразу. + // Открываем чат с forward-панелью, чтобы пользователь мог + // добавить подпись и отправить вручную. + if (selectedDialogs.size == 1) { + val targetDialog = selectedDialogs.first() + ForwardManager.setForwardMessages( + forwardMessages, + showPicker = false + ) + ForwardManager.selectChat(targetDialog.opponentKey) + + if (targetDialog.opponentKey != user.publicKey) { + val searchUser = + SearchUser( + title = targetDialog.opponentTitle, + username = + targetDialog.opponentUsername, + publicKey = targetDialog.opponentKey, + verified = targetDialog.verified, + online = targetDialog.isOnline + ) + onNavigateToChat(searchUser) + } + return@ForwardChatPickerBottomSheet + } + + ForwardManager.clear() + + // Мультивыбор оставляем прямой отправкой как раньше. selectedDialogs.forEach { dialog -> viewModel.sendForwardDirectly( dialog.opponentKey, diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index b407b47..d441945 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -2243,6 +2243,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val sender = myPublicKey val privateKey = myPrivateKey val replyMsgs = _replyMessages.value + val replyMsgsToSend = replyMsgs.toList() val isForward = _isForwardMode.value // Разрешаем отправку пустого текста если есть reply/forward @@ -2271,10 +2272,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val timestamp = System.currentTimeMillis() // 🔥 Формируем ReplyData для отображения в UI (только первое сообщение) - // Работает и для reply, и для forward + // Используется для обычного reply (не forward). val replyData: ReplyData? = - if (replyMsgs.isNotEmpty()) { - val firstReply = replyMsgs.first() + if (replyMsgsToSend.isNotEmpty()) { + val firstReply = replyMsgsToSend.first() // 🖼️ Получаем attachments из текущих сообщений для превью // Fallback на firstReply.attachments для forward из другого чата val replyAttachments = @@ -2302,8 +2303,38 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { ) } else null - // Сохраняем reply для отправки ПЕРЕД очисткой - val replyMsgsToSend = replyMsgs.toList() + // 📨 В forward режиме показываем ВСЕ пересылаемые сообщения в optimistic bubble, + // а не только первое. Иначе визуально выглядит как будто отправилось одно сообщение. + val optimisticForwardedMessages: List = + if (isForward && replyMsgsToSend.isNotEmpty()) { + replyMsgsToSend.map { msg -> + val senderDisplayName = + if (msg.isOutgoing) "You" + else msg.senderName.ifEmpty { + opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } } + } + val resolvedAttachments = + _messages.value.find { it.id == msg.messageId }?.attachments + ?: msg.attachments.filter { it.type != AttachmentType.MESSAGES } + ReplyData( + messageId = msg.messageId, + senderName = senderDisplayName, + text = msg.text, + isFromMe = msg.isOutgoing, + isForwarded = true, + forwardedFromName = senderDisplayName, + attachments = resolvedAttachments, + senderPublicKey = msg.publicKey.ifEmpty { + if (msg.isOutgoing) myPublicKey ?: "" else opponentKey ?: "" + }, + recipientPrivateKey = myPrivateKey ?: "" + ) + } + } else { + emptyList() + } + + // Сохраняем режим forward для отправки ПЕРЕД очисткой val isForwardToSend = isForward // 1. 🚀 Optimistic UI - мгновенно показываем сообщение с reply bubble @@ -2314,7 +2345,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { isOutgoing = true, timestamp = Date(timestamp), status = MessageStatus.SENDING, - replyData = replyData // Данные для reply bubble + replyData = if (isForwardToSend) null else replyData, + forwardedMessages = optimisticForwardedMessages ) // � Безопасное добавление с проверкой дубликатов diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/attach/ChatAttachAlert.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/attach/ChatAttachAlert.kt index ffe9a7a..6139562 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/attach/ChatAttachAlert.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/attach/ChatAttachAlert.kt @@ -46,6 +46,7 @@ import app.rosette.android.ui.keyboard.KeyboardTransitionCoordinator import app.rosette.android.ui.keyboard.AnimatedKeyboardTransition import app.rosette.android.ui.keyboard.rememberKeyboardTransitionCoordinator import com.rosetta.messenger.ui.onboarding.PrimaryBlue +import com.rosetta.messenger.ui.utils.NavigationModeUtils import kotlinx.coroutines.Job import kotlinx.coroutines.launch import android.net.Uri @@ -158,6 +159,9 @@ fun ChatAttachAlert( viewModel: AttachAlertViewModel = viewModel() ) { val context = LocalContext.current + val hasNativeNavigationBar = remember(context) { + NavigationModeUtils.hasNativeNavigationBar(context) + } val density = LocalDensity.current val imeInsets = WindowInsets.ime val focusManager = LocalFocusManager.current @@ -204,6 +208,23 @@ fun ChatAttachAlert( // Keyboard helpers // ═══════════════════════════════════════════════════════════ + fun hideUnderlyingChatKeyboard() { + focusManager.clearFocus(force = true) + activity?.currentFocus?.clearFocus() + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + val servedToken = + activity?.currentFocus?.windowToken + ?: activity?.window?.decorView?.findFocus()?.windowToken + ?: activity?.window?.decorView?.windowToken + servedToken?.let { token -> + imm.hideSoftInputFromWindow(token, 0) + imm.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + activity?.window?.insetsController?.hide(android.view.WindowInsets.Type.ime()) + } + } + fun hideKeyboard() { AttachAlertDebugLog.log("AttachAlert", "hideKeyboard() called, captionInputActive=$captionInputActive, shouldShow=$shouldShow, isClosing=$isClosing") pendingCaptionFocus = false @@ -277,10 +298,14 @@ fun ChatAttachAlert( if (coordinator.emojiHeight == 0.dp) { // Use saved keyboard height minus nav bar (same as spacer) val savedPx = KeyboardHeightProvider.getSavedKeyboardHeight(context) - val navBarPx = (context as? Activity)?.window?.decorView?.let { view -> - androidx.core.view.ViewCompat.getRootWindowInsets(view) - ?.getInsets(androidx.core.view.WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0 - } ?: 0 + val navBarPx = if (hasNativeNavigationBar) { + (context as? Activity)?.window?.decorView?.let { view -> + androidx.core.view.ViewCompat.getRootWindowInsets(view) + ?.getInsets(androidx.core.view.WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0 + } ?: 0 + } else { + 0 + } val effectivePx = if (savedPx > 0) (savedPx - navBarPx).coerceAtLeast(0) else 0 coordinator.emojiHeight = if (effectivePx > 0) { with(density) { effectivePx.toDp() } @@ -302,7 +327,11 @@ fun ChatAttachAlert( val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val screenHeightPx = with(density) { screenHeight.toPx() } - val navigationBarInsetPx = WindowInsets.navigationBars.getBottom(density).toFloat() + val navigationBarInsetPx = if (hasNativeNavigationBar) { + WindowInsets.navigationBars.getBottom(density).toFloat() + } else { + 0f + } val statusBarInsetPx = WindowInsets.statusBars.getTop(density).toFloat() val collapsedHeightPx = screenHeightPx * 0.72f @@ -465,6 +494,9 @@ fun ChatAttachAlert( LaunchedEffect(showSheet) { if (showSheet && state.editingItem == null) { + // Close chat keyboard before showing picker so no IME layer remains under it. + hideUnderlyingChatKeyboard() + kotlinx.coroutines.delay(16) // Telegram pattern: set ADJUST_NOTHING on Activity before showing popup // This prevents the system from resizing the layout when focus changes activity?.window?.let { win -> @@ -677,11 +709,20 @@ fun ChatAttachAlert( val origNavBarColor = window?.navigationBarColor ?: 0 val origLightNav = insetsController?.isAppearanceLightNavigationBars ?: true val origLightStatus = insetsController?.isAppearanceLightStatusBars ?: false + val origContrastEnforced = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window?.isNavigationBarContrastEnforced + } else { + null + } onDispose { window?.statusBarColor = origStatusBarColor window?.navigationBarColor = origNavBarColor insetsController?.isAppearanceLightNavigationBars = origLightNav insetsController?.isAppearanceLightStatusBars = origLightStatus + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && origContrastEnforced != null) { + window?.isNavigationBarContrastEnforced = origContrastEnforced + } } } else { onDispose { } @@ -711,16 +752,28 @@ fun ChatAttachAlert( window.statusBarColor = android.graphics.Color.argb(scrimInt, 0, 0, 0) insetsController?.isAppearanceLightStatusBars = false } - // Telegram-like: nav bar follows picker surface, not black scrim. - val navBaseColor = if (dark) 0xFF1C1C1E.toInt() else 0xFFFFFFFF.toInt() - val navAlpha = (state.openProgress * 255f).toInt().coerceIn(0, 255) - window.navigationBarColor = android.graphics.Color.argb( - navAlpha, - android.graphics.Color.red(navBaseColor), - android.graphics.Color.green(navBaseColor), - android.graphics.Color.blue(navBaseColor) - ) - insetsController?.isAppearanceLightNavigationBars = !dark + if (hasNativeNavigationBar) { + // Telegram-like: for 2/3-button navigation keep picker-surface tint. + val navBaseColor = if (dark) 0xFF1C1C1E.toInt() else 0xFFFFFFFF.toInt() + val navAlpha = (state.openProgress * 255f).toInt().coerceIn(0, 255) + window.navigationBarColor = android.graphics.Color.argb( + navAlpha, + android.graphics.Color.red(navBaseColor), + android.graphics.Color.green(navBaseColor), + android.graphics.Color.blue(navBaseColor) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = true + } + insetsController?.isAppearanceLightNavigationBars = !dark + } else { + // Telegram-like on gesture navigation: transparent stable nav area. + window.navigationBarColor = android.graphics.Color.TRANSPARENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } + insetsController?.isAppearanceLightNavigationBars = !dark + } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt index f7ed7e7..77ccdd8 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt @@ -69,6 +69,7 @@ import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.components.AppleEmojiTextField import com.rosetta.messenger.ui.components.KeyboardHeightProvider import com.rosetta.messenger.ui.components.OptimizedEmojiPicker +import com.rosetta.messenger.ui.utils.NavigationModeUtils import app.rosette.android.ui.keyboard.rememberKeyboardTransitionCoordinator import app.rosette.android.ui.keyboard.AnimatedKeyboardTransition import kotlinx.coroutines.Dispatchers @@ -136,6 +137,9 @@ fun MediaPickerBottomSheet( onPhotoPreviewRequested: ((Uri, ThumbnailPosition?) -> Unit)? = null ) { val context = LocalContext.current + val hasNativeNavigationBar = remember(context) { + NavigationModeUtils.hasNativeNavigationBar(context) + } val scope = rememberCoroutineScope() val density = LocalDensity.current val imeInsets = WindowInsets.ime @@ -295,7 +299,11 @@ fun MediaPickerBottomSheet( val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val screenHeightPx = with(density) { screenHeight.toPx() } - val navigationBarInsetPx = WindowInsets.navigationBars.getBottom(density).toFloat() + val navigationBarInsetPx = if (hasNativeNavigationBar) { + WindowInsets.navigationBars.getBottom(density).toFloat() + } else { + 0f + } val statusBarInsetPx = WindowInsets.statusBars.getTop(density).toFloat() // 🔄 Высоты в пикселях для точного контроля @@ -384,6 +392,9 @@ fun MediaPickerBottomSheet( // Запускаем анимацию когда showSheet меняется LaunchedEffect(showSheet) { if (showSheet && editingItem == null) { + // Ensure IME from chat is closed before picker opens. + hideKeyboard() + delay(16) shouldShow = true isClosing = false showAlbumMenu = false @@ -562,12 +573,21 @@ fun MediaPickerBottomSheet( val origNavBarColor = window?.navigationBarColor ?: 0 val origLightNav = insetsController?.isAppearanceLightNavigationBars ?: true val origLightStatus = insetsController?.isAppearanceLightStatusBars ?: false + val origContrastEnforced = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window?.isNavigationBarContrastEnforced + } else { + null + } onDispose { window?.statusBarColor = origStatusBarColor window?.navigationBarColor = origNavBarColor insetsController?.isAppearanceLightNavigationBars = origLightNav insetsController?.isAppearanceLightStatusBars = origLightStatus + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && origContrastEnforced != null) { + window?.isNavigationBarContrastEnforced = origContrastEnforced + } } } else { onDispose { } @@ -601,16 +621,28 @@ fun MediaPickerBottomSheet( ) insetsController?.isAppearanceLightStatusBars = false } - // Telegram-like: nav bar follows picker surface, not scrim. - val navBaseColor = if (dark) 0xFF1C1C1E.toInt() else 0xFFFFFFFF.toInt() - val navAlpha = (state.openProgress * 255f).toInt().coerceIn(0, 255) - window.navigationBarColor = android.graphics.Color.argb( - navAlpha, - android.graphics.Color.red(navBaseColor), - android.graphics.Color.green(navBaseColor), - android.graphics.Color.blue(navBaseColor) - ) - insetsController?.isAppearanceLightNavigationBars = !dark + if (hasNativeNavigationBar) { + // Telegram-like: for 2/3-button navigation keep picker-surface tint. + val navBaseColor = if (dark) 0xFF1C1C1E.toInt() else 0xFFFFFFFF.toInt() + val navAlpha = (state.openProgress * 255f).toInt().coerceIn(0, 255) + window.navigationBarColor = android.graphics.Color.argb( + navAlpha, + android.graphics.Color.red(navBaseColor), + android.graphics.Color.green(navBaseColor), + android.graphics.Color.blue(navBaseColor) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = true + } + insetsController?.isAppearanceLightNavigationBars = !dark + } else { + // Telegram-like on gesture navigation: transparent stable nav area. + window.navigationBarColor = android.graphics.Color.TRANSPARENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } + insetsController?.isAppearanceLightNavigationBars = !dark + } } } @@ -2207,6 +2239,9 @@ fun PhotoPreviewWithCaptionScreen( val backgroundColor = if (isDarkTheme) Color.Black else Color.White val context = LocalContext.current + val hasNativeNavigationBar = remember(context) { + NavigationModeUtils.hasNativeNavigationBar(context) + } val view = LocalView.current val focusManager = LocalFocusManager.current val density = LocalDensity.current @@ -2291,7 +2326,8 @@ fun PhotoPreviewWithCaptionScreen( // 🔥 КЛЮЧЕВОЕ: imePadding применяется ТОЛЬКО когда emoji box НЕ виден val shouldUseImePadding = !coordinator.isEmojiBoxVisible - val shouldAddNavBarPadding = !isKeyboardVisible && !coordinator.isEmojiBoxVisible + val shouldAddNavBarPadding = + hasNativeNavigationBar && !isKeyboardVisible && !coordinator.isEmojiBoxVisible // Логируем состояние при каждой рекомпозиции Surface( diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt index c805e98..b7686aa 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt @@ -36,6 +36,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties import app.rosette.android.ui.keyboard.AnimatedKeyboardTransition import app.rosette.android.ui.keyboard.KeyboardTransitionCoordinator import coil.compose.AsyncImage @@ -88,6 +90,7 @@ fun MessageInputBar( replyMessages: List = emptyList(), isForwardMode: Boolean = false, onCloseReply: () -> Unit = {}, + onShowForwardOptions: (List) -> Unit = {}, chatTitle: String = "", isBlocked: Boolean = false, showEmojiPicker: Boolean = false, @@ -218,6 +221,11 @@ fun MessageInputBar( val canSend = remember(value, hasReply) { value.isNotBlank() || hasReply } var isSending by remember { mutableStateOf(false) } + var showForwardCancelDialog by remember { mutableStateOf(false) } + var forwardCancelDialogCount by remember { mutableIntStateOf(0) } + var forwardCancelDialogMessages by remember { + mutableStateOf>(emptyList()) + } val mentionPattern = remember { Regex("@([\\w\\d_]*)$") } val mentionedPattern = remember { Regex("@([\\w\\d_]{1,})") } val mentionMatch = remember(value, isGroupChat) { if (isGroupChat) mentionPattern.find(value) else null } @@ -661,7 +669,18 @@ fun MessageInputBar( .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null, - onClick = onCloseReply + onClick = { + if (panelIsForwardMode && panelReplyMessages.isNotEmpty()) { + val sourceMessages = + if (replyMessages.isNotEmpty()) replyMessages + else panelReplyMessages + forwardCancelDialogCount = sourceMessages.size + forwardCancelDialogMessages = sourceMessages + showForwardCancelDialog = true + } else { + onCloseReply() + } + } ), contentAlignment = Alignment.Center ) { @@ -765,6 +784,122 @@ fun MessageInputBar( } } + if (showForwardCancelDialog) { + val titleText = + if (forwardCancelDialogCount == 1) "1 message" + else "$forwardCancelDialogCount messages" + val bodyText = + "What would you like to do with $titleText from your chat with ${chatTitle.ifBlank { "this user" }}?" + val popupInteraction = remember { MutableInteractionSource() } + val cardInteraction = remember { MutableInteractionSource() } + val dividerColor = if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFE5E5EA) + val cardColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White + val bodyColor = if (isDarkTheme) Color(0xFFD1D1D6) else Color(0xFF3C3C43) + + Popup( + alignment = Alignment.Center, + onDismissRequest = { + showForwardCancelDialog = false + forwardCancelDialogMessages = emptyList() + }, + properties = + PopupProperties( + focusable = false, + dismissOnBackPress = true, + dismissOnClickOutside = true, + clippingEnabled = false + ) + ) { + Box( + modifier = + Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.36f)) + .clickable( + interactionSource = popupInteraction, + indication = null + ) { showForwardCancelDialog = false }, + contentAlignment = Alignment.Center + ) { + Surface( + modifier = + Modifier + .fillMaxWidth(0.88f) + .widthIn(max = 360.dp) + .clip(RoundedCornerShape(16.dp)) + .clickable( + interactionSource = cardInteraction, + indication = null + ) {}, + color = cardColor, + shadowElevation = 18.dp + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.height(18.dp)) + Text( + text = titleText, + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = if (isDarkTheme) Color.White else Color.Black, + modifier = Modifier.padding(horizontal = 20.dp) + ) + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = bodyText, + fontSize = 16.sp, + lineHeight = 21.sp, + color = bodyColor, + modifier = Modifier.padding(horizontal = 20.dp) + ) + Spacer(modifier = Modifier.height(14.dp)) + Divider(thickness = 0.5.dp, color = dividerColor) + TextButton( + onClick = { + showForwardCancelDialog = false + val panelMessages = + if (replyMessages.isNotEmpty()) { + replyMessages + } else if (forwardCancelDialogMessages.isNotEmpty()) { + forwardCancelDialogMessages + } else if (liveReplyMessages.isNotEmpty()) { + liveReplyMessages + } else { + animatedReplyMessages + } + onShowForwardOptions(panelMessages) + forwardCancelDialogMessages = emptyList() + }, + modifier = Modifier.fillMaxWidth().height(52.dp) + ) { + Text( + text = "Show forwarding options", + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = PrimaryBlue + ) + } + TextButton( + onClick = { + showForwardCancelDialog = false + forwardCancelDialogMessages = emptyList() + onCloseReply() + }, + modifier = Modifier.fillMaxWidth().height(52.dp) + ) { + Text( + text = "Cancel Forwarding", + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFFFF5D73) + ) + } + Spacer(modifier = Modifier.height(6.dp)) + } + } + } + } + } + // EMOJI PICKER if (!isBlocked) { AnimatedKeyboardTransition(