@@ -15,7 +15,6 @@ import androidx.compose.animation.slideOutVertically
1515import androidx.compose.animation.togetherWith
1616import androidx.compose.foundation.Image
1717import androidx.compose.foundation.background
18- import androidx.compose.foundation.border
1918import androidx.compose.foundation.layout.Arrangement
2019import androidx.compose.foundation.layout.Box
2120import androidx.compose.foundation.layout.Row
@@ -24,7 +23,9 @@ import androidx.compose.foundation.layout.size
2423import androidx.compose.foundation.layout.wrapContentSize
2524import androidx.compose.foundation.lazy.LazyRow
2625import androidx.compose.foundation.lazy.items
26+ import androidx.compose.foundation.lazy.itemsIndexed
2727import androidx.compose.foundation.shape.CircleShape
28+ import androidx.compose.foundation.shape.CornerSize
2829import androidx.compose.material.Text
2930import androidx.compose.material.icons.Icons
3031import 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
194215private const val MaxAvatars = 3
216+ private const val DotCount = 3
195217private const val StepDuration = 500
196218
197219@Composable
0 commit comments