Adjust channel rendering to group related messages

This commit is contained in:
Mikayla 2023-10-04 14:29:08 -07:00
parent 5074bccae4
commit 73e78a2257
No known key found for this signature in database
3 changed files with 126 additions and 68 deletions

View file

@ -2,7 +2,7 @@ use crate::{channel_view::ChannelView, ChatPanelSettings};
use anyhow::Result; use anyhow::Result;
use call::ActiveCall; use call::ActiveCall;
use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
use client::{Client, UserStore}; use client::Client;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use editor::Editor; use editor::Editor;
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
@ -35,7 +35,6 @@ const CHAT_PANEL_KEY: &'static str = "ChatPanel";
pub struct ChatPanel { pub struct ChatPanel {
client: Arc<Client>, client: Arc<Client>,
channel_store: ModelHandle<ChannelStore>, channel_store: ModelHandle<ChannelStore>,
user_store: ModelHandle<UserStore>,
active_chat: Option<(ModelHandle<ChannelChat>, Subscription)>, active_chat: Option<(ModelHandle<ChannelChat>, Subscription)>,
message_list: ListState<ChatPanel>, message_list: ListState<ChatPanel>,
input_editor: ViewHandle<Editor>, input_editor: ViewHandle<Editor>,
@ -79,7 +78,6 @@ impl ChatPanel {
let fs = workspace.app_state().fs.clone(); let fs = workspace.app_state().fs.clone();
let client = workspace.app_state().client.clone(); let client = workspace.app_state().client.clone();
let channel_store = workspace.app_state().channel_store.clone(); let channel_store = workspace.app_state().channel_store.clone();
let user_store = workspace.app_state().user_store.clone();
let input_editor = cx.add_view(|cx| { let input_editor = cx.add_view(|cx| {
let mut editor = Editor::auto_height( let mut editor = Editor::auto_height(
@ -132,7 +130,7 @@ impl ChatPanel {
fs, fs,
client, client,
channel_store, channel_store,
user_store,
active_chat: Default::default(), active_chat: Default::default(),
pending_serialization: Task::ready(None), pending_serialization: Task::ready(None),
message_list, message_list,
@ -331,12 +329,26 @@ impl ChatPanel {
} }
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement<Self> { fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let message = self.active_chat.as_ref().unwrap().0.read(cx).message(ix); let (message, is_continuation, is_last) = {
let active_chat = self.active_chat.as_ref().unwrap().0.read(cx);
let last_message = active_chat.message(ix.saturating_sub(1));
let this_message = active_chat.message(ix);
let is_continuation = last_message.id != this_message.id
&& this_message.sender.id == last_message.sender.id;
(
active_chat.message(ix),
is_continuation,
active_chat.message_count() == ix + 1,
)
};
let now = OffsetDateTime::now_utc(); let now = OffsetDateTime::now_utc();
let theme = theme::current(cx); let theme = theme::current(cx);
let style = if message.is_pending() { let style = if message.is_pending() {
&theme.chat_panel.pending_message &theme.chat_panel.pending_message
} else if is_continuation {
&theme.chat_panel.continuation_message
} else { } else {
&theme.chat_panel.message &theme.chat_panel.message
}; };
@ -352,71 +364,103 @@ impl ChatPanel {
enum DeleteMessage {} enum DeleteMessage {}
let body = message.body.clone(); let body = message.body.clone();
Flex::column() if is_continuation {
.with_child( Flex::row()
Flex::row() .with_child(Text::new(body, style.body.clone()))
.with_child( .with_children(message_id_to_remove.map(|id| {
message MouseEventHandler::new::<DeleteMessage, _>(id as usize, cx, |mouse_state, _| {
.sender let button_style = theme.chat_panel.icon_button.style_for(mouse_state);
.avatar render_icon_button(button_style, "icons/x.svg")
.clone() .aligned()
.map(|avatar| { .into_any()
Image::from_data(avatar) })
.with_style(theme.collab_panel.channel_avatar) .with_padding(Padding::uniform(2.))
.into_any() .with_cursor_style(CursorStyle::PointingHand)
}) .on_click(MouseButton::Left, move |_, this, cx| {
.unwrap_or_else(|| { this.remove_message(id, cx);
Empty::new() })
.constrained() .flex_float()
.with_width( }))
theme.collab_panel.channel_avatar.width.unwrap_or(12.), .contained()
) .with_style(style.container)
.into_any() .with_margin_bottom(if is_last {
}) theme.chat_panel.last_message_bottom_spacing
} else {
0.
})
.into_any()
} else {
Flex::column()
.with_child(
Flex::row()
.with_child(
message
.sender
.avatar
.clone()
.map(|avatar| {
Image::from_data(avatar)
.with_style(theme.collab_panel.channel_avatar)
.into_any()
})
.unwrap_or_else(|| {
Empty::new()
.constrained()
.with_width(
theme.collab_panel.channel_avatar.width.unwrap_or(12.),
)
.into_any()
})
.contained()
.with_margin_right(4.),
)
.with_child(
Label::new(
message.sender.github_login.clone(),
style.sender.text.clone(),
)
.contained() .contained()
.with_margin_right(4.), .with_style(style.sender.container),
)
.with_child(
Label::new(
message.sender.github_login.clone(),
style.sender.text.clone(),
) )
.contained() .with_child(
.with_style(style.sender.container), Label::new(
) format_timestamp(message.timestamp, now, self.local_timezone),
.with_child( style.timestamp.text.clone(),
Label::new( )
format_timestamp(message.timestamp, now, self.local_timezone), .contained()
style.timestamp.text.clone(), .with_style(style.timestamp.container),
) )
.contained() .with_children(message_id_to_remove.map(|id| {
.with_style(style.timestamp.container), MouseEventHandler::new::<DeleteMessage, _>(
) id as usize,
.with_children(message_id_to_remove.map(|id| { cx,
MouseEventHandler::new::<DeleteMessage, _>( |mouse_state, _| {
id as usize, let button_style =
cx, theme.chat_panel.icon_button.style_for(mouse_state);
|mouse_state, _| { render_icon_button(button_style, "icons/x.svg")
let button_style = .aligned()
theme.chat_panel.icon_button.style_for(mouse_state); .into_any()
render_icon_button(button_style, "icons/x.svg") },
.aligned() )
.into_any() .with_padding(Padding::uniform(2.))
}, .with_cursor_style(CursorStyle::PointingHand)
) .on_click(MouseButton::Left, move |_, this, cx| {
.with_padding(Padding::uniform(2.)) this.remove_message(id, cx);
.with_cursor_style(CursorStyle::PointingHand) })
.on_click(MouseButton::Left, move |_, this, cx| { .flex_float()
this.remove_message(id, cx); }))
}) .align_children_center(),
.flex_float() )
})) .with_child(Text::new(body, style.body.clone()))
.align_children_center(), .contained()
) .with_style(style.container)
.with_child(Text::new(body, style.body.clone())) .with_margin_bottom(if is_last {
.contained() theme.chat_panel.last_message_bottom_spacing
.with_style(style.container) } else {
.into_any() 0.
})
.into_any()
}
} }
fn render_input_box(&self, theme: &Arc<Theme>, cx: &AppContext) -> AnyElement<Self> { fn render_input_box(&self, theme: &Arc<Theme>, cx: &AppContext) -> AnyElement<Self> {

View file

@ -635,6 +635,8 @@ pub struct ChatPanel {
pub channel_select: ChannelSelect, pub channel_select: ChannelSelect,
pub input_editor: FieldEditor, pub input_editor: FieldEditor,
pub message: ChatMessage, pub message: ChatMessage,
pub continuation_message: ChatMessage,
pub last_message_bottom_spacing: f32,
pub pending_message: ChatMessage, pub pending_message: ChatMessage,
pub sign_in_prompt: Interactive<TextStyle>, pub sign_in_prompt: Interactive<TextStyle>,
pub icon_button: Interactive<IconButton>, pub icon_button: Interactive<IconButton>,

View file

@ -87,7 +87,19 @@ export default function chat_panel(): any {
...text(layer, "sans", "base", { weight: "bold" }), ...text(layer, "sans", "base", { weight: "bold" }),
}, },
timestamp: text(layer, "sans", "base", "disabled"), timestamp: text(layer, "sans", "base", "disabled"),
margin: { bottom: SPACING } margin: { top: SPACING }
},
last_message_bottom_spacing: SPACING,
continuation_message: {
body: text(layer, "sans", "base"),
sender: {
margin: {
right: 8,
},
...text(layer, "sans", "base", { weight: "bold" }),
},
timestamp: text(layer, "sans", "base", "disabled"),
}, },
pending_message: { pending_message: {
body: text(layer, "sans", "base"), body: text(layer, "sans", "base"),