From 286d9b21c7270207c4cdc152db9e93209bc17125 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Sun, 11 Jan 2026 05:12:38 +0500 Subject: [PATCH] feat: Improve keyboard handling and fade-out animation in ChatDetailScreen --- .../messenger/ui/chats/ChatDetailScreen.kt | 143 +++++++++++------- 1 file changed, 90 insertions(+), 53 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 92fd38b..b153ab1 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 @@ -53,6 +53,7 @@ import com.rosetta.messenger.ui.components.AppleEmojiPickerPanel import com.rosetta.messenger.ui.components.AppleEmojiTextField import com.rosetta.messenger.ui.components.AppleEmojiText import androidx.compose.ui.text.font.FontFamily +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.* @@ -130,7 +131,7 @@ fun ChatDetailScreen( var isVisible by remember { mutableStateOf(false) } val screenAlpha by animateFloatAsState( targetValue = if (isVisible) 1f else 0f, - animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing), + animationSpec = tween(durationMillis = 180, easing = FastOutSlowInEasing), label = "screenFade" ) @@ -138,15 +139,22 @@ fun ChatDetailScreen( isVisible = true } - // πŸ”₯ БыстроС Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ ΠΊΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Ρ‹ ΠΈ Π²Ρ‹Ρ…ΠΎΠ΄ - val hideKeyboardAndBack = remember { - { - // МгновСнно ΡƒΠ±ΠΈΡ€Π°Π΅ΠΌ фокус ΠΈ ΠΊΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Ρƒ - focusManager.clearFocus(force = true) - keyboardController?.hide() - // Π‘Ρ€Π°Π·Ρƒ Π²Ρ‹Ρ…ΠΎΠ΄ΠΈΠΌ Π±Π΅Π· Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ + val listState = rememberLazyListState() + val scope = rememberCoroutineScope() + + // πŸ”₯ БыстроС Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ с fade-out Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠ΅ΠΉ + val hideKeyboardAndBack: () -> Unit = { + // МгновСнно ΡƒΠ±ΠΈΡ€Π°Π΅ΠΌ фокус ΠΈ ΠΊΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Ρƒ + focusManager.clearFocus(force = true) + keyboardController?.hide() + // ЗапускаСм fade-out + isVisible = false + // Π’Ρ‹Ρ…ΠΎΠ΄ΠΈΠΌ послС ΠΊΠΎΡ€ΠΎΡ‚ΠΊΠΎΠΉ Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠΈ + scope.launch { + delay(150) onBack() } + Unit } // ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ это Saved Messages ΠΈΠ»ΠΈ ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΉ Ρ‡Π°Ρ‚ @@ -189,9 +197,6 @@ fun ChatDetailScreen( else -> "offline" } - val listState = rememberLazyListState() - val scope = rememberCoroutineScope() - // πŸ”₯ ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° систСмной ΠΊΠ½ΠΎΠΏΠΊΠΈ Π½Π°Π·Π°Π΄ BackHandler { hideKeyboardAndBack() @@ -237,12 +242,24 @@ fun ChatDetailScreen( .fillMaxSize() .graphicsLayer { alpha = screenAlpha } ) { + // Π¦Π²Π΅Ρ‚Π° для ΠΌΠ°Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ стСкла + val glassHeaderBackground = if (isDarkTheme) + Color(0xFF1A1A1A).copy(alpha = 0.85f) + else + Color(0xFFF5F5F5).copy(alpha = 0.85f) + + val glassInputPanelBackground = if (isDarkTheme) + Color(0xFF1A1A1A).copy(alpha = 0.88f) + else + Color(0xFFF5F5F5).copy(alpha = 0.88f) + Scaffold( topBar = { - // ΠšΠ°ΡΡ‚ΠΎΠΌΠ½Ρ‹ΠΉ TopAppBar для Ρ‡Π°Ρ‚Π° - Surface( - color = backgroundColor, - shadowElevation = 0.dp + // ΠšΠ°ΡΡ‚ΠΎΠΌΠ½Ρ‹ΠΉ TopAppBar для Ρ‡Π°Ρ‚Π° с эффСктом ΠΌΠ°Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ стСкла + Box( + modifier = Modifier + .fillMaxWidth() + .background(glassHeaderBackground) ) { Row( modifier = Modifier @@ -267,7 +284,11 @@ fun ChatDetailScreen( .size(40.dp) .clip(CircleShape) .background(if (isSavedMessages) PrimaryBlue else avatarColors.backgroundColor) - .clickable { onUserProfileClick() }, + .clickable { + keyboardController?.hide() + focusManager.clearFocus() + onUserProfileClick() + }, contentAlignment = Alignment.Center ) { if (isSavedMessages) { @@ -293,7 +314,11 @@ fun ChatDetailScreen( Column( modifier = Modifier .weight(1f) - .clickable { onUserProfileClick() } + .clickable { + keyboardController?.hide() + focusManager.clearFocus() + onUserProfileClick() + } ) { Row(verticalAlignment = Alignment.CenterVertically) { Text( @@ -334,7 +359,11 @@ fun ChatDetailScreen( } // Кнопка Π»ΠΎΠ³ΠΎΠ² (для ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ) - IconButton(onClick = { showLogs = true }) { + IconButton(onClick = { + keyboardController?.hide() + focusManager.clearFocus() + showLogs = true + }) { Icon( Icons.Default.BugReport, contentDescription = "Logs", @@ -342,7 +371,11 @@ fun ChatDetailScreen( ) } - IconButton(onClick = { /* TODO: More options */ }) { + IconButton(onClick = { + keyboardController?.hide() + focusManager.clearFocus() + /* TODO: More options */ + }) { Icon( Icons.Default.MoreVert, contentDescription = "More", @@ -350,9 +383,20 @@ fun ChatDetailScreen( ) } } - } - }, - containerColor = backgroundColor + // НиТняя линия для раздСлСния + 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) + ) + ) + } + }, + containerColor = Color.Transparent ) { paddingValues -> Column( modifier = Modifier @@ -404,7 +448,13 @@ fun ChatDetailScreen( LazyColumn( state = listState, modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp), + // ДобавляСм padding свСрху ΠΈ снизу для скролла ΠΏΠΎΠ΄ glass header/input + contentPadding = PaddingValues( + start = 8.dp, + end = 8.dp, + top = 8.dp, + bottom = 8.dp + ), reverseLayout = true ) { // Для inverted FlatList: ΠΈΠ΄Ρ‘ΠΌ ΠΎΡ‚ Π½ΠΎΠ²Ρ‹Ρ… ΠΊ старым @@ -504,7 +554,7 @@ fun ChatDetailScreen( } /** - * πŸš€ ΠŸΡƒΠ·Ρ‹Ρ€Π΅ΠΊ сообщСния с fade-in Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠ΅ΠΉ + * πŸš€ ΠŸΡƒΠ·Ρ‹Ρ€Π΅ΠΊ сообщСния с fade-in Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠ΅ΠΉ (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π²ΠΎΠΌ появлСнии) */ @Composable private fun MessageBubble( @@ -512,30 +562,9 @@ private fun MessageBubble( isDarkTheme: Boolean, index: Int = 0 // Для staggered Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠΈ ) { - // πŸ”₯ Fade-in + slide анимация - var isVisible by remember { mutableStateOf(false) } - val alpha by animateFloatAsState( - targetValue = if (isVisible) 1f else 0f, - animationSpec = tween( - durationMillis = 150, - delayMillis = minOf(index * 20, 200), // Staggered, max 200ms delay - easing = FastOutSlowInEasing - ), - label = "bubbleAlpha" - ) - val offsetY by animateFloatAsState( - targetValue = if (isVisible) 0f else 20f, - animationSpec = tween( - durationMillis = 150, - delayMillis = minOf(index * 20, 200), - easing = FastOutSlowInEasing - ), - label = "bubbleOffset" - ) + // πŸ”₯ Fade-in + slide анимация - ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ key для прСдотвращСния ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΠΎΠΉ Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠΈ + var isVisible by remember(message.id) { mutableStateOf(true) } // Π‘Ρ€Π°Π·Ρƒ true - Π±Π΅Π· ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΠΎΠΉ Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠΈ - LaunchedEffect(message.id) { - isVisible = true - } val bubbleColor = if (message.isOutgoing) { PrimaryBlue } else { @@ -553,11 +582,7 @@ private fun MessageBubble( Row( modifier = Modifier .fillMaxWidth() - .padding(vertical = 2.dp) - .graphicsLayer { - this.alpha = alpha - translationY = offsetY - }, + .padding(vertical = 2.dp), horizontalArrangement = if (message.isOutgoing) Arrangement.End else Arrangement.Start ) { Box( @@ -682,7 +707,7 @@ private fun MessageInputBar( val glassBackground = if (isDarkTheme) Color(0xFF3C3C3C).copy(alpha = 0.9f) else Color(0xFFF0F0F0).copy(alpha = 0.92f) val glassBorder = if (isDarkTheme) Color.White.copy(alpha = 0.25f) else Color.Black.copy(alpha = 0.1f) val emojiIconColor = if (isDarkTheme) Color.White.copy(alpha = 0.62f) else Color.Black.copy(alpha = 0.5f) - val panelBackground = if (isDarkTheme) Color(0xFF1A1A1A).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f) + val panelBackground = if (isDarkTheme) Color(0xFF1A1A1A).copy(alpha = 0.88f) else Color(0xFFF5F5F5).copy(alpha = 0.88f) // БостояниС ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ val canSend = remember(value) { value.isNotBlank() } @@ -747,6 +772,18 @@ private fun MessageInputBar( .fillMaxWidth() .background(panelBackground) ) { + // ВСрхняя линия для раздСлСния (эффСкт стСкла) + Box( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + .height(0.5.dp) + .background( + if (isDarkTheme) Color.White.copy(alpha = 0.1f) + else Color.Black.copy(alpha = 0.08f) + ) + ) + Row( modifier = Modifier .fillMaxWidth()