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