mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Improve typing indicator
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>): 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
|
||||
) {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user