Files
Ely-GPUI-Components/src/messaging/receipt.rs
T
ZacharyZhang-NY 99b8a6081b Messaging · Messages: messages in runs, threads, the unread line and receipts
ChatMessage sets a message under its author's picture, name and clock
time. The next by the same author within five minutes, on the same day,
joins the run: only its words, the time in the gutter on hover.
UnreadDivider marks where unread messages start. MessageThread tells who
replied, how many and when the last came; with a handler it opens on a
press, Enter or Space. ThreadPanel holds the first message, a rule
counting the replies, the replies and a composer, its body scrolling.
ReadReceipt says sending, sent, delivered or read, and who saw it in a
group. MessageReactions and TypingIndicator point to collab and motion.

gpui recolors nothing on hover: text takes its color at layout. The
gutter time rests invisible and turns visible through a group hover.
2026-09-26 21:48:17 -04:00

94 lines
2.6 KiB
Rust

use gpui::{App, IntoElement, ParentElement, RenderOnce, Styled, Window, div, prelude::*};
use crate::{
data_display::{Avatar, AvatarGroup},
primitives::{Icon, IconName},
theme::{ActiveTheme, AvatarSize, IconSize, TextSize},
};
/// Where a sent message stands.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Delivery {
Sending,
Sent,
Delivered,
Read,
}
impl Delivery {
pub fn label(self) -> &'static str {
match self {
Self::Sending => "Sending",
Self::Sent => "Sent",
Self::Delivered => "Delivered",
Self::Read => "Read",
}
}
fn icon(self) -> IconName {
match self {
Self::Sending => IconName::Clock,
Self::Sent => IconName::Check,
Self::Delivered | Self::Read => IconName::CheckCheck,
}
}
}
/// A sent message's receipt: a clock while it goes, one tick once sent, two once delivered, two in the info tone once read, each with its word. Read in a group, it shows who saw it.
#[derive(IntoElement)]
pub struct ReadReceipt {
delivery: Delivery,
readers: Vec<Avatar>,
}
impl ReadReceipt {
pub fn new(delivery: Delivery) -> Self {
Self {
delivery,
readers: Vec::new(),
}
}
/// Who read it; only a read message has readers, and the first three show.
pub fn readers(mut self, readers: impl IntoIterator<Item = Avatar>) -> Self {
self.readers = readers.into_iter().collect();
assert!(
self.readers.is_empty() || self.delivery == Delivery::Read,
"only a read message has readers"
);
self
}
}
impl RenderOnce for ReadReceipt {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let colors = &theme.colors;
let tint = if self.delivery == Delivery::Read {
colors.info
} else {
colors.fg_subtle
};
let seen = !self.readers.is_empty();
div()
.flex()
.items_center()
.gap_1()
.text_size(theme.text_size(TextSize::Xs))
.text_color(colors.fg_subtle)
.child(
Icon::new(self.delivery.icon())
.size(IconSize::Xs)
.color(tint),
)
.child(if seen {
"Seen by"
} else {
self.delivery.label()
})
.when(seen, |receipt| {
receipt.child(AvatarGroup::new(self.readers).max(3).size(AvatarSize::Xs))
})
}
}