From 22a17e5fecd5fd2f30e75fa1739eae3d8d73236f Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Thu, 15 Jan 2026 20:54:04 +0500 Subject: [PATCH] feat: Improve keyboard handling in selection mode for better user experience --- .../messenger/ui/chats/ChatDetailScreen.kt | 490 +++++++++--------- .../messenger/ui/chats/ChatViewModel.kt | 7 +- .../ui/components/KeyboardHeightProvider.kt | 30 +- 3 files changed, 278 insertions(+), 249 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 16dc4eb..60af4ef 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 @@ -323,15 +323,22 @@ fun ChatDetailScreen( var selectedMessages by remember { mutableStateOf>(emptySet()) } val isSelectionMode = selectedMessages.isNotEmpty() - // 🔥 Закрываем клавиатуру и emoji picker когда открывается selection mode (action bar с Reply/Forward) + // Логирование изменений selection mode + LaunchedEffect(isSelectionMode, selectedMessages.size) { + android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════") + android.util.Log.d("ChatDetailScreen", "📝 SELECTION MODE CHANGED") + android.util.Log.d("ChatDetailScreen", " 📊 isSelectionMode: $isSelectionMode") + android.util.Log.d("ChatDetailScreen", " 📊 selectedMessages.size: ${selectedMessages.size}") + android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════") + } + + // 🔥 Backup: если клавиатура ещё открыта когда selection mode активировался + // (клавиатура уже должна быть закрыта в onLongClick, это только backup) LaunchedEffect(isSelectionMode) { if (isSelectionMode) { - // Используем нативный InputMethodManager для НАДЁЖНОГО закрытия клавиатуры - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(view.windowToken, 0) + android.util.Log.d("ChatDetailScreen", "⚠️ Backup keyboard hide triggered") + // Backup закрытие клавиатуры (основное в onLongClick) keyboardController?.hide() - focusManager.clearFocus() - showEmojiPicker = false } } @@ -542,164 +549,140 @@ fun ChatDetailScreen( Scaffold( contentWindowInsets = WindowInsets(0.dp), topBar = { - // 🔥 SELECTION HEADER (появляется при выборе сообщений) - Telegram Style - AnimatedVisibility( - visible = isSelectionMode, - enter = fadeIn(animationSpec = tween(200)) + slideInVertically( - initialOffsetY = { -it }, - animationSpec = tween(250, easing = TelegramEasing) - ), - exit = fadeOut(animationSpec = tween(150)) + slideOutVertically( - targetOffsetY = { -it }, - animationSpec = tween(200, easing = TelegramEasing) - ) - ) { - Box(modifier = Modifier + // 🔥 UNIFIED HEADER - один контейнер, контент меняется внутри + Box( + modifier = Modifier .fillMaxWidth() - .background(if (isDarkTheme) Color(0xFF212121) else Color.White) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .statusBarsPadding() - .height(56.dp) - .padding(horizontal = 4.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - // Left: X (cancel) + Count - Telegram Style - Row(verticalAlignment = Alignment.CenterVertically) { - IconButton(onClick = { selectedMessages = emptySet() }) { - Icon( - Icons.Default.Close, - contentDescription = "Cancel", - tint = if (isDarkTheme) Color.White else Color.Black, - modifier = Modifier.size(24.dp) - ) - } - Spacer(modifier = Modifier.width(8.dp)) - Text( - "${selectedMessages.size}", - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - color = if (isDarkTheme) Color.White else Color.Black - ) - } - - // Right: Action buttons - Telegram Style + .background(if (isSelectionMode) { + if (isDarkTheme) Color(0xFF212121) else Color.White + } else headerBackground) + ) { + // Контент хедера с Crossfade для плавной смены + Crossfade( + targetState = isSelectionMode, + animationSpec = tween(200), + label = "headerContent" + ) { selectionMode -> + if (selectionMode) { + // SELECTION MODE CONTENT Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .height(56.dp) + .padding(horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - // Copy button - IconButton( - onClick = { - val textToCopy = messages - .filter { selectedMessages.contains("${it.id}_${it.timestamp.time}") } - .sortedBy { it.timestamp } - .joinToString("\n\n") { msg -> - val time = SimpleDateFormat("HH:mm", Locale.getDefault()) - .format(msg.timestamp) - "[${if (msg.isOutgoing) "You" else chatTitle}] $time\n${msg.text}" - } - clipboardManager.setText(androidx.compose.ui.text.AnnotatedString(textToCopy)) - selectedMessages = emptySet() + // Left: X (cancel) + Count + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = { selectedMessages = emptySet() }) { + Icon( + Icons.Default.Close, + contentDescription = "Cancel", + tint = if (isDarkTheme) Color.White else Color.Black, + modifier = Modifier.size(24.dp) + ) } - ) { - Icon( - Icons.Default.ContentCopy, - contentDescription = "Copy", - tint = if (isDarkTheme) Color.White else Color.Black, - modifier = Modifier.size(22.dp) + Spacer(modifier = Modifier.width(8.dp)) + Text( + "${selectedMessages.size}", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = if (isDarkTheme) Color.White else Color.Black ) } - // Delete button - IconButton( - onClick = { - // Удаляем выбранные сообщения - messages - .filter { selectedMessages.contains("${it.id}_${it.timestamp.time}") } - .forEach { msg -> viewModel.deleteMessage(msg.id) } - selectedMessages = emptySet() + // Right: Action buttons + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Copy button + IconButton( + onClick = { + val textToCopy = messages + .filter { selectedMessages.contains("${it.id}_${it.timestamp.time}") } + .sortedBy { it.timestamp } + .joinToString("\n\n") { msg -> + val time = SimpleDateFormat("HH:mm", Locale.getDefault()) + .format(msg.timestamp) + "[${if (msg.isOutgoing) "You" else chatTitle}] $time\n${msg.text}" + } + clipboardManager.setText(androidx.compose.ui.text.AnnotatedString(textToCopy)) + selectedMessages = emptySet() + } + ) { + Icon( + Icons.Default.ContentCopy, + contentDescription = "Copy", + tint = if (isDarkTheme) Color.White else Color.Black, + modifier = Modifier.size(22.dp) + ) + } + + // Delete button + IconButton( + onClick = { + messages + .filter { selectedMessages.contains("${it.id}_${it.timestamp.time}") } + .forEach { msg -> viewModel.deleteMessage(msg.id) } + selectedMessages = emptySet() + } + ) { + Icon( + Icons.Default.Delete, + contentDescription = "Delete", + tint = if (isDarkTheme) Color.White else Color.Black, + modifier = Modifier.size(22.dp) + ) } - ) { - Icon( - Icons.Default.Delete, - contentDescription = "Delete", - tint = if (isDarkTheme) Color.White else Color.Black, - modifier = Modifier.size(22.dp) - ) } } - } - - // Bottom line - Box( - modifier = Modifier - .align(Alignment.BottomCenter) - .fillMaxWidth() - .height(0.5.dp) - .background( - if (isDarkTheme) Color.White.copy(alpha = 0.15f) - else Color.Black.copy(alpha = 0.1f) - ) - ) - } - } - - // NORMAL HEADER (скрывается при выборе) - AnimatedVisibility( - visible = !isSelectionMode, - enter = fadeIn(animationSpec = tween(200)), - exit = fadeOut(animationSpec = tween(150)) - ) { - // Telegram-style TopAppBar - solid background без blur - Box(modifier = Modifier.fillMaxWidth().background(headerBackground)) { - Row( - modifier = - Modifier.fillMaxWidth() - .statusBarsPadding() - .height(56.dp) - .padding(horizontal = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - // 🔔 Кнопка назад с badge непрочитанных сообщений - Box { - IconButton( - onClick = hideKeyboardAndBack, - modifier = Modifier.size(40.dp) + } else { + // NORMAL HEADER CONTENT + Row( + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .height(56.dp) + .padding(horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically ) { - Icon( - Icons.Default.KeyboardArrowLeft, - contentDescription = "Back", - tint = headerIconColor, - modifier = Modifier.size(32.dp) - ) - } - // Badge с количеством непрочитанных из других чатов - if (totalUnreadFromOthers > 0) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .offset(x = (-4).dp, y = 6.dp) - .size(if (totalUnreadFromOthers > 9) 20.dp else 18.dp) - .clip(CircleShape) - .background(Color(0xFFFF3B30)), // Красный цвет как в iOS - contentAlignment = Alignment.Center - ) { - Text( - text = if (totalUnreadFromOthers > 99) "99+" - else if (totalUnreadFromOthers > 9) "$totalUnreadFromOthers" - else "$totalUnreadFromOthers", - color = Color.White, - fontSize = if (totalUnreadFromOthers > 9) 9.sp else 10.sp, - fontWeight = FontWeight.Bold, - maxLines = 1 - ) + // Back button with badge + Box { + IconButton( + onClick = hideKeyboardAndBack, + modifier = Modifier.size(40.dp) + ) { + Icon( + Icons.Default.KeyboardArrowLeft, + contentDescription = "Back", + tint = headerIconColor, + modifier = Modifier.size(32.dp) + ) + } + if (totalUnreadFromOthers > 0) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .offset(x = (-4).dp, y = 6.dp) + .size(if (totalUnreadFromOthers > 9) 20.dp else 18.dp) + .clip(CircleShape) + .background(Color(0xFFFF3B30)), + contentAlignment = Alignment.Center + ) { + Text( + text = if (totalUnreadFromOthers > 99) "99+" + else "$totalUnreadFromOthers", + color = Color.White, + fontSize = if (totalUnreadFromOthers > 9) 9.sp else 10.sp, + fontWeight = FontWeight.Bold, + maxLines = 1 + ) + } + } } - } - } Spacer(modifier = Modifier.width(4.dp)) @@ -962,20 +945,21 @@ fun ChatDetailScreen( } } } - // Нижняя линия для разделения + } + } // Закрытие Crossfade + + // Bottom line для unified header Box( - modifier = - Modifier.align(Alignment.BottomCenter) - .fillMaxWidth() - .height(0.5.dp) - .background( - if (isDarkTheme) - Color.White.copy(alpha = 0.1f) - else Color.Black.copy(alpha = 0.08f) - ) + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .height(0.5.dp) + .background( + if (isDarkTheme) Color.White.copy(alpha = 0.15f) + else Color.Black.copy(alpha = 0.1f) + ) ) - } - } // Закрытие AnimatedVisibility для normal header + } // Закрытие Box unified header }, containerColor = backgroundColor, // Фон всего чата // 🔥 Bottom bar - инпут с умным padding: @@ -984,76 +968,38 @@ fun ChatDetailScreen( bottomBar = { // 🔥 Telegram-style: когда Box с эмодзи виден, НЕ используем imePadding // isEmojiBoxVisible учитывает анимацию fade-out (alpha > 0.01) - val bottomModifier = if (coordinator.isEmojiBoxVisible) { - Modifier // Без imePadding - Box с эмодзи заменяет клавиатуру - } else { + // 🔥 В selection mode НЕ используем imePadding (клавиатура закрыта) + val useImePadding = !coordinator.isEmojiBoxVisible && !isSelectionMode + val bottomModifier = if (useImePadding) { Modifier.imePadding() // С imePadding - клавиатура поднимает инпут - } - Column(modifier = bottomModifier) { - // 🔥 FLOATING INPUT BAR - плавает поверх сообщений, поднимается с клавиатурой - // Скрываем когда в режиме выбора - AnimatedVisibility( - visible = !isSelectionMode, - enter = fadeIn(tween(200)) + slideInVertically(initialOffsetY = { it }), - exit = fadeOut(tween(150)) + slideOutVertically(targetOffsetY = { it }) - ) { - Column { - // Input bar с встроенным reply preview (как в React Native) - MessageInputBar( - value = inputText, - onValueChange = { - viewModel.updateInputText(it) - // Отправляем индикатор печатания - if (it.isNotEmpty() && !isSavedMessages) { - viewModel.sendTypingIndicator() - } - }, - onSend = { - // Скрываем кнопку scroll на время отправки - isSendingMessage = true - viewModel.sendMessage() - // Скроллим к новому сообщению - scope.launch { - delay(100) - listState.animateScrollToItem(0) - delay(300) // Ждём завершения анимации - isSendingMessage = false - } - }, - isDarkTheme = isDarkTheme, - backgroundColor = backgroundColor, // Тот же цвет что и фон чата - textColor = textColor, - placeholderColor = secondaryTextColor, - secondaryTextColor = secondaryTextColor, - // Reply state - replyMessages = replyMessages, - isForwardMode = isForwardMode, - onCloseReply = { viewModel.clearReplyMessages() }, - chatTitle = chatTitle, - isBlocked = isBlocked, - // Emoji picker state (поднят для KeyboardAvoidingView) - showEmojiPicker = showEmojiPicker, - onToggleEmojiPicker = { showEmojiPicker = it }, - // Focus requester для автофокуса при reply - focusRequester = inputFocusRequester, - // Coordinator для плавных переходов - coordinator = coordinator, - // 🔥 Для отображения reply preview и скролла - displayReplyMessages = displayReplyMessages, - onReplyClick = scrollToMessage - ) - } + } else { + Modifier // Без imePadding } - // 🔥 SELECTION ACTION BAR - Reply/Forward (появляется при выборе сообщений) - // Плоский стиль как у инпута с border сверху - AnimatedVisibility( - visible = isSelectionMode, - enter = fadeIn(tween(200)) + slideInVertically(initialOffsetY = { it }), - exit = fadeOut(tween(150)) + slideOutVertically(targetOffsetY = { it }) - ) { - Column { - // Плоский контейнер как у инпута + // Логирование состояния + LaunchedEffect(isSelectionMode, useImePadding, coordinator.isEmojiBoxVisible, coordinator.keyboardHeight) { + android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════") + android.util.Log.d("ChatDetailScreen", "🔄 BOTTOM BAR STATE CHANGED") + android.util.Log.d("ChatDetailScreen", " 📊 isSelectionMode: $isSelectionMode") + android.util.Log.d("ChatDetailScreen", " 📊 useImePadding: $useImePadding") + android.util.Log.d("ChatDetailScreen", " 📊 isEmojiBoxVisible: ${coordinator.isEmojiBoxVisible}") + android.util.Log.d("ChatDetailScreen", " 📊 keyboardHeight: ${coordinator.keyboardHeight}") + android.util.Log.d("ChatDetailScreen", " 📊 emojiHeight: ${coordinator.emojiHeight}") + android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════") + } + + Column(modifier = bottomModifier) { + // 🔥 UNIFIED BOTTOM BAR - один контейнер, контент меняется внутри + Crossfade( + targetState = isSelectionMode, + animationSpec = tween(200), + label = "bottomBarContent" + ) { selectionMode -> + android.util.Log.d("ChatDetailScreen", "🎬 Crossfade to selectionMode=$selectionMode") + + if (selectionMode) { + // SELECTION ACTION BAR - Reply/Forward + // 🔥 Высота должна совпадать с MessageInputBar (~56dp content + nav bar) Column( modifier = Modifier .fillMaxWidth() @@ -1067,12 +1013,12 @@ fun ChatDetailScreen( .background(if (isDarkTheme) Color.White.copy(alpha = 0.15f) else Color.Black.copy(alpha = 0.1f)) ) - // Кнопки Reply и Forward + // Кнопки Reply и Forward - такие же отступы как у input row Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 16.dp) - .padding(bottom = 12.dp) + .padding(horizontal = 12.dp, vertical = 8.dp) + .padding(bottom = 16.dp) .navigationBarsPadding(), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically @@ -1081,6 +1027,7 @@ fun ChatDetailScreen( Box( modifier = Modifier .weight(1f) + .height(40.dp) .clip(RoundedCornerShape(12.dp)) .background(PrimaryBlue.copy(alpha = 0.1f)) .clickable { @@ -1089,8 +1036,7 @@ fun ChatDetailScreen( .sortedBy { it.timestamp } viewModel.setReplyMessages(selectedMsgs) selectedMessages = emptySet() - } - .padding(vertical = 16.dp), + }, contentAlignment = Alignment.Center ) { Row( @@ -1117,6 +1063,7 @@ fun ChatDetailScreen( Box( modifier = Modifier .weight(1f) + .height(40.dp) .clip(RoundedCornerShape(12.dp)) .background(PrimaryBlue.copy(alpha = 0.1f)) .clickable { @@ -1125,8 +1072,7 @@ fun ChatDetailScreen( .sortedBy { it.timestamp } viewModel.setForwardMessages(selectedMsgs) selectedMessages = emptySet() - } - .padding(vertical = 16.dp), + }, contentAlignment = Alignment.Center ) { Row( @@ -1150,6 +1096,45 @@ fun ChatDetailScreen( } } } + } else { + // INPUT BAR + Column { + MessageInputBar( + value = inputText, + onValueChange = { + viewModel.updateInputText(it) + if (it.isNotEmpty() && !isSavedMessages) { + viewModel.sendTypingIndicator() + } + }, + onSend = { + isSendingMessage = true + viewModel.sendMessage() + scope.launch { + delay(100) + listState.animateScrollToItem(0) + delay(300) + isSendingMessage = false + } + }, + isDarkTheme = isDarkTheme, + backgroundColor = backgroundColor, + textColor = textColor, + placeholderColor = secondaryTextColor, + secondaryTextColor = secondaryTextColor, + replyMessages = replyMessages, + isForwardMode = isForwardMode, + onCloseReply = { viewModel.clearReplyMessages() }, + chatTitle = chatTitle, + isBlocked = isBlocked, + showEmojiPicker = showEmojiPicker, + onToggleEmojiPicker = { showEmojiPicker = it }, + focusRequester = inputFocusRequester, + coordinator = coordinator, + displayReplyMessages = displayReplyMessages, + onReplyClick = scrollToMessage + ) + } } } } // Закрытие Column с imePadding @@ -1288,12 +1273,29 @@ fun ChatDetailScreen( isSelected = selectedMessages.contains(selectionKey), isHighlighted = highlightedMessageId == message.id, onLongClick = { + android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════") + android.util.Log.d("ChatDetailScreen", "👆 LONG CLICK on message") + android.util.Log.d("ChatDetailScreen", " 📊 isSelectionMode BEFORE: $isSelectionMode") + android.util.Log.d("ChatDetailScreen", " 📊 selectedMessages.size BEFORE: ${selectedMessages.size}") + + // 🔥 СНАЧАЛА закрываем клавиатуру МГНОВЕННО (до изменения state) + if (!isSelectionMode) { + android.util.Log.d("ChatDetailScreen", " ⌨️ Closing keyboard...") + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.windowToken, 0) + focusManager.clearFocus() + showEmojiPicker = false + android.util.Log.d("ChatDetailScreen", " ✅ Keyboard closed") + } // Toggle selection on long press selectedMessages = if (selectedMessages.contains(selectionKey)) { selectedMessages - selectionKey } else { selectedMessages + selectionKey } + + android.util.Log.d("ChatDetailScreen", " 📊 selectedMessages.size AFTER: ${selectedMessages.size}") + android.util.Log.d("ChatDetailScreen", "═══════════════════════════════════════") }, onClick = { // If in selection mode, toggle selection @@ -2332,11 +2334,14 @@ private fun MessageInputBar( .background(if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f)) ) + // BLOCKED CHAT FOOTER - плоский стиль + // 🔥 Высота должна совпадать с MessageInputBar и Selection Action Bar Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 16.dp) - .padding(bottom = 16.dp), + .padding(horizontal = 12.dp, vertical = 8.dp) + .padding(bottom = 16.dp) + .navigationBarsPadding(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { @@ -2453,11 +2458,14 @@ private fun MessageInputBar( } // INPUT ROW - Paperclip → TextField → Emoji → Send/Mic (ПЛОСКИЙ ДИЗАЙН) + // 🔥 Высота должна совпадать с Selection Action Bar (padding + nav bar) Row( modifier = Modifier .fillMaxWidth() .heightIn(min = 48.dp) - .padding(horizontal = 12.dp, vertical = 8.dp), + .padding(horizontal = 12.dp, vertical = 8.dp) + .padding(bottom = 16.dp) // 🔥 Такой же отступ как у Selection Action Bar + .navigationBarsPadding(), // 🔥 Такой же navigationBarsPadding verticalAlignment = Alignment.Bottom ) { // PAPERCLIP BUTTON (слева) 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 1177004..3fbcb3e 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 @@ -768,8 +768,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { * 🔥 Очистить reply/forward */ fun clearReplyMessages() { - _replyMessages.value = emptyList() - _isForwardMode.value = false + viewModelScope.launch { + delay(350) // Задержка после закрытия панели (анимация fadeOut + shrinkVertically) + _replyMessages.value = emptyList() + _isForwardMode.value = false + } } /** diff --git a/app/src/main/java/com/rosetta/messenger/ui/components/KeyboardHeightProvider.kt b/app/src/main/java/com/rosetta/messenger/ui/components/KeyboardHeightProvider.kt index 35f6095..737eae4 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/components/KeyboardHeightProvider.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/components/KeyboardHeightProvider.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp * ``` */ object KeyboardHeightProvider { + private const val TAG = "KeyboardHeight" private const val PREFS_NAME = "emoji_keyboard_prefs" private const val KEY_KEYBOARD_HEIGHT = "kbd_height" private const val DEFAULT_HEIGHT_DP = 280 // Telegram uses 200, we use 280 @@ -35,7 +36,11 @@ object KeyboardHeightProvider { val savedPx = prefs.getInt(KEY_KEYBOARD_HEIGHT, defaultPx) val isDefault = savedPx == defaultPx - android.util.Log.d("KeyboardHeight", "📖 getSavedKeyboardHeight: ${savedPx}px (${pxToDp(context, savedPx)}dp) ${if (isDefault) "[DEFAULT]" else "[SAVED]"}") + android.util.Log.d(TAG, "═══════════════════════════════════════") + android.util.Log.d(TAG, "📖 getSavedKeyboardHeight()") + android.util.Log.d(TAG, " 📏 Height: ${savedPx}px (${pxToDp(context, savedPx)}dp)") + android.util.Log.d(TAG, " 📦 Source: ${if (isDefault) "DEFAULT" else "SAVED"}") + android.util.Log.d(TAG, "═══════════════════════════════════════") return savedPx } @@ -43,6 +48,9 @@ object KeyboardHeightProvider { * Сохранить высоту клавиатуры */ fun saveKeyboardHeight(context: Context, heightPx: Int) { + android.util.Log.d(TAG, "═══════════════════════════════════════") + android.util.Log.d(TAG, "💾 saveKeyboardHeight($heightPx px)") + if (heightPx > 0) { val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) val oldHeight = prefs.getInt(KEY_KEYBOARD_HEIGHT, -1) @@ -50,21 +58,29 @@ object KeyboardHeightProvider { prefs.edit().putInt(KEY_KEYBOARD_HEIGHT, heightPx).apply() + android.util.Log.d(TAG, " 📏 New: ${heightPx}px (${pxToDp(context, heightPx)}dp)") + android.util.Log.d(TAG, " 📏 Old: ${oldHeight}px (${pxToDp(context, oldHeight)}dp)") + android.util.Log.d(TAG, " 🔄 Changed: $changed") + if (changed) { - android.util.Log.d("KeyboardHeight", "💾 SAVED keyboard height: ${heightPx}px (${pxToDp(context, heightPx)}dp) [WAS: ${oldHeight}px]") + android.util.Log.d(TAG, " ✅ SAVED successfully!") } else { - android.util.Log.v("KeyboardHeight", "💾 Same keyboard height: ${heightPx}px (no change)") + android.util.Log.d(TAG, " ⏭️ Same height, no change") } } else { - android.util.Log.w("KeyboardHeight", "⚠️ Attempted to save invalid height: ${heightPx}px") + android.util.Log.w(TAG, " ⚠️ INVALID height: ${heightPx}px - NOT saved!") } + android.util.Log.d(TAG, "═══════════════════════════════════════") } /** * Получить длительность анимации клавиатуры * Telegram использует: AdjustPanLayoutHelper.keyboardDuration (250ms обычно) */ - fun getKeyboardAnimationDuration(): Long = 250L + fun getKeyboardAnimationDuration(): Long { + android.util.Log.d(TAG, "⏱️ getKeyboardAnimationDuration() = 250ms") + return 250L + } // Helper functions private fun dpToPx(context: Context, dp: Int): Int { @@ -91,7 +107,9 @@ fun rememberSavedKeyboardHeight(): Dp { val density = context.resources.displayMetrics.density val heightDp = (heightPx / density).dp - android.util.Log.d("KeyboardHeight", "🎯 rememberSavedKeyboardHeight: ${heightDp} (${heightPx}px, density=${density})") + android.util.Log.d("KeyboardHeight", "🎯 rememberSavedKeyboardHeight()") + android.util.Log.d("KeyboardHeight", " 📏 Result: $heightDp (${heightPx}px)") + android.util.Log.d("KeyboardHeight", " 📱 Density: $density") return heightDp }