From 1454024e7673e0836d7d3570d1b3747e7319f2ce Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Wed, 24 Dec 2025 17:23:19 +0300 Subject: [PATCH] Improve typing indicator --- .../kotlin/ru/fromchat/ui/chat/ChatScreen.kt | 8 +- .../ru/fromchat/ui/chat/PublicChatPanel.kt | 6 +- .../ru/fromchat/ui/chat/TypingIndicator.kt | 99 +++++++++---------- 3 files changed, 49 insertions(+), 64 deletions(-) diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatScreen.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatScreen.kt index 65e921d..cabbe0e 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatScreen.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatScreen.kt @@ -188,13 +188,12 @@ fun ChatScreen( topBar = { TopAppBar( title = { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { + Column(Modifier.fillMaxWidth()) { Text( text = panelState.title, style = MaterialTheme.typography.titleLarge ) + AnimatedContent( targetState = currentTypingUsers.isNotEmpty(), transitionSpec = { @@ -207,9 +206,6 @@ fun ChatScreen( typingUsers = currentTypingUsers.map { it.username }, modifier = Modifier.padding(top = 2.dp) ) - } else { - // Empty space to maintain height - Box(modifier = Modifier.height(0.dp)) } } } diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt index 98d12fb..e79fac6 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt @@ -29,7 +29,7 @@ class PublicChatPanel( scope.launch { typingHandler.typingUsers.collect { users -> Logger.d("PublicChatPanel", "Typing users updated in handler: ${users.map { it.username }}") - updateState { it.copy(typingUsers = users) } + updateState { it.copy(typingUsers = users.filter { it.userId != currentUserId }) } } } } @@ -58,7 +58,7 @@ class PublicChatPanel( } setHasMoreMessages(false) // TODO: Implement has_more from API messagesLoaded = true - } catch (e: Exception) { + } catch (_: Exception) { // Handle error } finally { setLoading(false) @@ -84,7 +84,7 @@ class PublicChatPanel( } } setHasMoreMessages(false) // TODO: Implement has_more from API - } catch (e: Exception) { + } catch (_: Exception) { // Handle error } finally { setLoadingMore(false) diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/TypingIndicator.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/TypingIndicator.kt index c9a5315..9b71dd9 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/TypingIndicator.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/TypingIndicator.kt @@ -4,9 +4,12 @@ import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.keyframes import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme @@ -16,7 +19,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.unit.dp import org.jetbrains.compose.resources.stringResource import ru.fromchat.Res @@ -63,67 +65,54 @@ private fun formatTypingText(typingUsers: List): String { @Composable private fun TypingDots() { val infiniteTransition = rememberInfiniteTransition(label = "typing_dots") - - val dot1Alpha by infiniteTransition.animateFloat( - initialValue = 0.3f, - targetValue = 1f, + val duration = 1000 + val initialScale = 1f + val targetScale = 1.3f + + // Функция-хелпер для создания анимации с задержкой + @Composable + fun animateDotScale(delay: Int) = infiniteTransition.animateFloat( + initialValue = initialScale, + targetValue = initialScale, // Возвращаемся в начало animationSpec = infiniteRepeatable( animation = keyframes { - durationMillis = 1400 - 0.3f at 0 - 1f at 200 - 0.3f at 400 + durationMillis = duration + initialScale at delay // Начало подъема + targetScale at delay + 200 // Пик + initialScale at delay + 400 // Возврат + initialScale at duration // Удержание до конца цикла } ), - label = "dot1" - ) - - val dot2Alpha by infiniteTransition.animateFloat( - initialValue = 0.3f, - targetValue = 1f, - animationSpec = infiniteRepeatable( - animation = keyframes { - durationMillis = 1400 - 0.3f at 200 - 1f at 400 - 0.3f at 600 - } - ), - label = "dot2" - ) - - val dot3Alpha by infiniteTransition.animateFloat( - initialValue = 0.3f, - targetValue = 1f, - animationSpec = infiniteRepeatable( - animation = keyframes { - durationMillis = 1400 - 0.3f at 400 - 1f at 600 - 0.3f at 800 - } - ), - label = "dot3" + label = "dot_scale" ) - Row(verticalAlignment = Alignment.CenterVertically) { - Dot(alpha = dot1Alpha) - Spacer(modifier = Modifier.width(2.dp)) - Dot(alpha = dot2Alpha) - Spacer(modifier = Modifier.width(2.dp)) - Dot(alpha = dot3Alpha) + val dot1Scale by animateDotScale(delay = 0) + val dot2Scale by animateDotScale(delay = 200) + val dot3Scale by animateDotScale(delay = 400) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) // Вместо Spacer + ) { + Dot(scale = dot1Scale, maxScale = targetScale) + Dot(scale = dot2Scale, maxScale = targetScale) + Dot(scale = dot3Scale, maxScale = targetScale) } } @Composable -private fun Dot(alpha: Float) { - Surface( - modifier = Modifier - .width(4.dp) - .height(4.dp) - .alpha(alpha), - shape = CircleShape, - color = MaterialTheme.colorScheme.primary - ) {} -} - +@Suppress("SameParameterValue") +private fun Dot(maxScale: Float, scale: Float) { + Box( + modifier = Modifier.size(4.dp * maxScale), + contentAlignment = Alignment.Center + ) { + Surface( + modifier = Modifier + .width(4.dp * scale) + .height(4.dp * scale), + shape = CircleShape, + color = MaterialTheme.colorScheme.primary + ) {} + } +} \ No newline at end of file