Skip to content

Commit f797875

Browse files
committed
style(fc): tweaks to typing indicator UX
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 38beb89 commit f797875

1 file changed

Lines changed: 45 additions & 23 deletions

File tree

ui/components/src/main/kotlin/com/getcode/ui/components/chat/TypingIndicator.kt

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import androidx.compose.animation.slideOutVertically
1515
import androidx.compose.animation.togetherWith
1616
import androidx.compose.foundation.Image
1717
import androidx.compose.foundation.background
18-
import androidx.compose.foundation.border
1918
import androidx.compose.foundation.layout.Arrangement
2019
import androidx.compose.foundation.layout.Box
2120
import androidx.compose.foundation.layout.Row
@@ -24,7 +23,9 @@ import androidx.compose.foundation.layout.size
2423
import androidx.compose.foundation.layout.wrapContentSize
2524
import androidx.compose.foundation.lazy.LazyRow
2625
import androidx.compose.foundation.lazy.items
26+
import androidx.compose.foundation.lazy.itemsIndexed
2727
import androidx.compose.foundation.shape.CircleShape
28+
import androidx.compose.foundation.shape.CornerSize
2829
import androidx.compose.material.Text
2930
import androidx.compose.material.icons.Icons
3031
import androidx.compose.material.icons.filled.Person
@@ -61,7 +62,7 @@ fun TypingIndicator(
6162
modifier = modifier
6263
.padding(top = 4.dp)
6364
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen },
64-
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
65+
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2),
6566
verticalAlignment = Alignment.CenterVertically
6667
) {
6768
// Avatar Row
@@ -91,24 +92,22 @@ private fun AvatarRow(
9192
verticalAlignment = Alignment.CenterVertically,
9293
horizontalArrangement = Arrangement.spacedBy(-overlap)
9394
) {
94-
items(
95+
itemsIndexed(
9596
items = userImages.take(maxAvatars),
96-
key = { it.hashCode() }
97-
) { image ->
98-
val index = userImages.take(maxAvatars).indexOf(image)
99-
97+
key = { _, item -> item.hashCode() }
98+
) { index, image ->
99+
// val zIndex = (maxAvatars - index).toFloat()
100100
UserAvatar(
101101
modifier = Modifier
102102
.size(avatarSize)
103-
.zIndex(index.toFloat())
104103
.clip(CircleShape)
105-
.border(CodeTheme.dimens.border, CodeTheme.colors.dividerVariant, CircleShape)
106104
.animateItem(
107105
fadeOutSpec = spring(
108106
stiffness = Spring.StiffnessMediumLow,
109-
visibilityThreshold = 0.8f
110-
),
111-
),
107+
visibilityThreshold = 0.9f
108+
)
109+
)
110+
.zIndex(index.toFloat()),
112111
data = image
113112
) {
114113
Image(
@@ -146,26 +145,36 @@ private fun TypingDots(
146145
) {
147146
Row(
148147
modifier = modifier
149-
.background(color = CodeTheme.colors.brandDark, shape = CodeTheme.shapes.small)
150-
.padding(horizontal = CodeTheme.dimens.grid.x2, vertical = CodeTheme.dimens.grid.x3),
151-
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1, Alignment.CenterHorizontally),
148+
.background(
149+
color = CodeTheme.colors.brandDark,
150+
shape = CodeTheme.shapes.small.copy(
151+
topStart = CornerSize(3.dp)
152+
)
153+
)
154+
.padding(
155+
horizontal = CodeTheme.dimens.grid.x2,
156+
vertical = CodeTheme.dimens.grid.x3
157+
),
158+
horizontalArrangement = Arrangement.spacedBy(
159+
space = CodeTheme.dimens.grid.x1,
160+
alignment = Alignment.CenterHorizontally
161+
),
152162
verticalAlignment = Alignment.CenterVertically
153163
) {
154164
val baseColor = CodeTheme.colors.onSurface
155-
val alphaValues = remember { arrayOf(1f, 0.60f, 0.20f) }
156165
var currentIndex by remember { mutableIntStateOf(0) }
157166

158-
val animatedAlphas = List(3) { index ->
159-
val targetAlpha = if (index == currentIndex) 1f else alphaValues[(index - currentIndex + 3) % 3]
167+
val animatedAlphas = List(DotCount) { index ->
168+
val targetAlpha = if (index == currentIndex) 1f else 0.4f
160169
animateFloatAsState(
161170
targetValue = targetAlpha,
162171
animationSpec = tween(durationMillis = StepDuration),
163172
label = "dotAlpha_$index"
164173
)
165174
}
166175

167-
val scales = List(3) { index ->
168-
val targetScale = if (index == currentIndex) 1f else 0.8f
176+
val animatedScales = List(DotCount) { index ->
177+
val targetScale = if (index == currentIndex) 1f else 0.6f
169178
animateFloatAsState(
170179
targetValue = targetScale,
171180
animationSpec = tween(durationMillis = StepDuration),
@@ -176,22 +185,35 @@ private fun TypingDots(
176185
LaunchedEffect(Unit) {
177186
while (true) {
178187
delay(StepDuration.toLong())
179-
currentIndex = (currentIndex + 1) % 3
188+
currentIndex = when (currentIndex) {
189+
DotCount -> 0
190+
DotCount - 1 -> DotCount
191+
else -> currentIndex + 1
192+
}
193+
194+
// Add extra delay when resetting
195+
if (currentIndex == DotCount) {
196+
delay(StepDuration.toLong())
197+
}
180198
}
181199
}
182200

183-
animatedAlphas.zip(scales).forEach { (alpha, scale) ->
201+
animatedAlphas.zip(animatedScales).forEach { (alpha, scale) ->
184202
Box(
185203
modifier = Modifier
186204
.size(CodeTheme.dimens.grid.x2)
187205
.scale(scale.value)
188-
.background(color = baseColor.copy(alpha = alpha.value), shape = CircleShape)
206+
.background(
207+
color = baseColor.copy(alpha = alpha.value),
208+
shape = CircleShape
209+
)
189210
)
190211
}
191212
}
192213
}
193214

194215
private const val MaxAvatars = 3
216+
private const val DotCount = 3
195217
private const val StepDuration = 500
196218

197219
@Composable

0 commit comments

Comments
 (0)