From 0983e760b46a74bff5e2aa606b6e99fa9b93dc7b Mon Sep 17 00:00:00 2001 From: Paul Hitt Date: Thu, 27 Aug 2026 17:40:40 -0400 Subject: [PATCH] Draw the author's picture beside a received bubble Reported: profile pictures do not appear in the chat boxes. They did not. iOS had an avatar slot in MessageBubble that hand-rolled only the initials fallback - it never had the user id or the inbox, so it could not reach the picture that AvatarView already downloads and decrypts everywhere else. Android reserved the kit's 40dp received inset for an avatar and drew nothing in it at all. Both now pass the author and the picture through. Sent bubbles get none: you know who you are, and a picture on both sides halves the width a thread has. Also gives the iOS list row a real last-message preview and a timestamp, matching the Android row - it was showing a screen name and the word 'Online', a second copy of the dot already on the avatar. Co-Authored-By: Claude Opus 5 --- .../io/flamenet/messenger/app/ChatScreens.kt | 41 ++++++++++++++++++- .../io/flamenet/messenger/app/MainActivity.kt | 2 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt index deec052..b4d27b7 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt @@ -1122,6 +1122,8 @@ fun ColumnScope.ChatScreen( error: String?, /** Needed to name group members; the relay stores ids and no names. */ buddies: List = emptyList(), + /** Pictures, so a received bubble can carry its author's. */ + avatars: Map = emptyMap(), ) { var draft by remember { mutableStateOf("") } val listState = rememberLazyListState() @@ -1200,7 +1202,17 @@ fun ColumnScope.ChatScreen( modifier = Modifier.fillMaxWidth().weight(1f).testTag("messages"), verticalArrangement = Arrangement.spacedBy(6.dp), ) { - items(chat.lines) { line -> Bubble(line, model) } + items(chat.lines) { line -> + Bubble( + line = line, + model = model, + authorName = chat.title, + // 🔴 Only for a message you did not send. Your own bubble has + // no avatar in the kit — you know who you are, and a picture on + // both sides halves the width a thread has to say anything in. + picture = chat.peerId?.takeIf { !line.mine }?.let { avatars[it] }, + ) + } } error?.let { @@ -1358,7 +1370,12 @@ private fun ColumnScope.VoiceComposer( } @Composable -private fun Bubble(line: Session.Line, model: MessengerViewModel) { +private fun Bubble( + line: Session.Line, + model: MessengerViewModel, + authorName: String = "", + picture: io.flamenet.e2e.Attachment.Ref? = null, +) { val time = remember(line.at) { SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(line.at)) } @@ -1374,8 +1391,28 @@ private fun Bubble(line: Session.Line, model: MessengerViewModel) { start = if (line.mine) Kit.receivedInset else 0.dp, end = if (line.mine) 0.dp else Kit.receivedInset, ), + // Top, so the avatar sits level with the first line of its bubble. + verticalAlignment = Alignment.Top, horizontalArrangement = if (line.mine) Arrangement.End else Arrangement.Start, ) { + // 🔴 **The avatar the inset was already reserving space for.** The + // received side has been padded by 40 — a 32dp avatar plus its gutter — + // since the bubbles were measured out of the kit, and nothing was ever + // drawn in it. The picture was on the contact row and the contact screen + // and missing from the one screen you spend all your time on. + // + // ⚠️ Top-aligned. Centred, it drifts down the side of a long message and + // reads as belonging to whatever comes next. + if (!line.mine) { + Avatar( + name = authorName, + online = false, + picture = picture, + model = model, + size = Kit.bubbleAvatar, + ) + Spacer(Modifier.width(Space.sm)) + } Column(horizontalAlignment = if (line.mine) Alignment.End else Alignment.Start) { Text( time, diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt b/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt index c4f9c4d..b45e7dd 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt @@ -147,7 +147,7 @@ private fun Root(model: MessengerViewModel = viewModel()) { val chat = current.openChat if (chat != null) { - ChatScreen(model, chat, current.error, current.buddies) + ChatScreen(model, chat, current.error, current.buddies, current.avatars) } else { // 🔴 **The content area is weighted, and the tab bar is // not.** These used to be plain siblings, so the tab bar