Repository navigation
feat(chat): show typists' avatars in a group's typing indicator - #1544
Merged
Merged
Conversation
ChatBottomBar called TypingIndicator without userImages, so its avatar row never drew. The ViewModel now derives one entry per typist, oldest first, from the sender profiles the transcript already resolves, and the bottom bar passes them in. DMs and tip chats get an empty list and keep the dots on their own. An entry is a picture URL from MediaUrlResolver, or the typist's user id when there is no picture or no profile yet, which UserAvatar draws as the Person fallback.
TypingIndicator now takes the typists, a key and an avatar slot rather than a list of image models. The row keeps its size, overlap, clip and animation; what goes in each circle is the caller's. The chat fills the slot with ContactAvatar, the same way the transcript draws a sender, so a member's picture goes through MediaUrlResolver's re-mint and BlurHash fallback, and a member without one shows initials. An unresolved profile draws the unknown-person avatar. Items are keyed on the user id, so an avatar no longer animates out and back in when a profile resolves. MediaUrlResolver comes back out of ChatViewModel. The row shows the last 3 typists, down from 10.
The LazyRow clipped avatars on their way out (its own scroll clip and animateContentSize both clip), dropped a leaving avatar from its width before the fade finished, and put the oldest typist on top. AvatarStack replaces it. Each avatar keeps an animated slot and presence, the stack's width follows the animated count, and the newest typist sits on top at the right. A newcomer grows from its left edge at its final slot, starting at 0.4 scale so it stays inside the stack's width and clear of the bubble. A leaver drops to the bottom while its neighbours slide over it. Each avatar cuts a 2dp transparent ring out of the ones beneath it, rather than painting a ring in a fixed color, so the separation holds over the blurred transcript. Avatars are 24dp with an 8dp overlap and a 4dp gap to the bubble, matching the sender avatar beside a group's messages. The preview now scripts joins and leaves so it can be run and recorded.
The AnimatedContent around the indicator shrinks to nothing on exit and clips to its bounds while it does, so the bubble and avatars were cut off partway through their fade. SizeTransform(clip = false) lets the indicator fade out whole. The preview's transition gets the same change.
A typist whose profile hasn't loaded now goes through the same ContactAvatar call as a resolved one, with no picture and an empty name, instead of the unknown-person glyph.
InitialsText fell back to "?" when the name had no letters. It now draws nothing, matching iOS, which shows only the gradient.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TypingIndicatorhas drawn an overlapping avatar row ahead of its dots for a while, but its one call site inChatBottomBarnever passeduserImages, so the row never appeared. This wires it up for group chats.Component.
TypingIndicatornow takestypists: List<T>, akeyand anavatarslot instead ofuserImages: List<Any>. The slot draws what goes inside each circle, and the stack handles the rest:This replaces the
LazyRow. It clipped avatars on their way out, dropped a leaving avatar from its width before the fade finished, and put the oldest typist on top.The component can't draw member pictures itself: they're
MediaItems rendered byContactAvatar, which lives in:apps:flipcash:shared:common-ui, and:ui:componentscan't depend on that module.PreviewTypingIndicatorscripts joins and leaves with placeholder avatars.Chat.
ChatViewModelderivesState.typingAvatarsfromtypistsand the sender profiles the transcript already uses. The logic is intypingAvatars()(TypingAvatars.kt):sinceascending, sotakeLast(3)keeps whoever started most recently.TypingAvatar(userId, profile), keyed on the user id's hex string. The key stays the same when the profile resolves, so the avatar doesn't animate out and back in.requestSenderProfile, the same way an unknown sender is.CONTACT_DMorTIP_DMgets an empty list and keeps the dots on their own.ChatBottomBarfills the slot withContactAvatar(image, displayName, BlobAccessContext.profile(userId)), which is howMessageRowdraws a sender. A member with a picture gets the same URL re-minting and BlurHash fallback as in the transcript, and a member without one shows initials. A typist whose profile hasn't resolved yet shows the unknown-person avatar.TypingAvatarsTestcovers group ordering bysince, an empty list for a DM and for a tip chat, removal of a typist who stops, and a key that holds when the profile resolves.