107 channel touch ups (#3087)

Release Notes:

- Add user avatars to channel chat messages
- Group messages by sender
- Fix visual bugs in new chat and note buttons
This commit is contained in:
Mikayla Maki 2023-10-04 15:14:39 -07:00 committed by GitHub
commit b0e56b7c54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 49 deletions

View file

@ -130,6 +130,7 @@ impl ChatPanel {
fs, fs,
client, client,
channel_store, channel_store,
active_chat: Default::default(), active_chat: Default::default(),
pending_serialization: Task::ready(None), pending_serialization: Task::ready(None),
message_list, message_list,
@ -328,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
}; };
@ -349,49 +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| {
Label::new( MouseEventHandler::new::<DeleteMessage, _>(id as usize, cx, |mouse_state, _| {
message.sender.github_login.clone(), let button_style = theme.chat_panel.icon_button.style_for(mouse_state);
style.sender.text.clone(), 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| {
this.remove_message(id, cx);
})
.flex_float()
}))
.contained()
.with_style(style.container)
.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.),
) )
.contained() .with_child(
.with_style(style.sender.container), Label::new(
) message.sender.github_login.clone(),
.with_child( style.sender.text.clone(),
Label::new( )
format_timestamp(message.timestamp, now, self.local_timezone), .contained()
style.timestamp.text.clone(), .with_style(style.sender.container),
) )
.contained() .with_child(
.with_style(style.timestamp.container), Label::new(
) format_timestamp(message.timestamp, now, self.local_timezone),
.with_children(message_id_to_remove.map(|id| { style.timestamp.text.clone(),
MouseEventHandler::new::<DeleteMessage, _>( )
id as usize, .contained()
cx, .with_style(style.timestamp.container),
|mouse_state, _| {
let button_style =
theme.chat_panel.icon_button.style_for(mouse_state);
render_icon_button(button_style, "icons/x.svg")
.aligned()
.into_any()
},
) )
.with_padding(Padding::uniform(2.)) .with_children(message_id_to_remove.map(|id| {
.with_cursor_style(CursorStyle::PointingHand) MouseEventHandler::new::<DeleteMessage, _>(
.on_click(MouseButton::Left, move |_, this, cx| { id as usize,
this.remove_message(id, cx); cx,
}) |mouse_state, _| {
.flex_float() let button_style =
})), theme.chat_panel.icon_button.style_for(mouse_state);
) render_icon_button(button_style, "icons/x.svg")
.with_child(Text::new(body, style.body.clone())) .aligned()
.contained() .into_any()
.with_style(style.container) },
.into_any() )
.with_padding(Padding::uniform(2.))
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, this, cx| {
this.remove_message(id, cx);
})
.flex_float()
}))
.align_children_center(),
)
.with_child(Text::new(body, style.body.clone()))
.contained()
.with_style(style.container)
.with_margin_bottom(if is_last {
theme.chat_panel.last_message_bottom_spacing
} else {
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

@ -1937,6 +1937,8 @@ impl CollabPanel {
is_dragged_over = true; is_dragged_over = true;
} }
let has_messages_notification = channel.unseen_message_id.is_some();
MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| { MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
let row_hovered = state.hovered(); let row_hovered = state.hovered();
@ -2022,24 +2024,33 @@ impl CollabPanel {
.flex(1., true) .flex(1., true)
}) })
.with_child( .with_child(
MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |_, _| { MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
let container_style = collab_theme
.disclosure
.button
.style_for(mouse_state)
.container;
if channel.unseen_message_id.is_some() { if channel.unseen_message_id.is_some() {
Svg::new("icons/conversations.svg") Svg::new("icons/conversations.svg")
.with_color(collab_theme.channel_note_active_color) .with_color(collab_theme.channel_note_active_color)
.constrained() .constrained()
.with_width(collab_theme.channel_hash.width) .with_width(collab_theme.channel_hash.width)
.contained()
.with_style(container_style)
.with_uniform_padding(4.)
.into_any() .into_any()
} else if row_hovered { } else if row_hovered {
Svg::new("icons/conversations.svg") Svg::new("icons/conversations.svg")
.with_color(collab_theme.channel_hash.color) .with_color(collab_theme.channel_hash.color)
.constrained() .constrained()
.with_width(collab_theme.channel_hash.width) .with_width(collab_theme.channel_hash.width)
.contained()
.with_style(container_style)
.with_uniform_padding(4.)
.into_any() .into_any()
} else { } else {
Empty::new() Empty::new().into_any()
.constrained()
.with_width(collab_theme.channel_hash.width)
.into_any()
} }
}) })
.on_click(MouseButton::Left, move |_, this, cx| { .on_click(MouseButton::Left, move |_, this, cx| {
@ -2056,7 +2067,12 @@ impl CollabPanel {
.with_margin_right(4.), .with_margin_right(4.),
) )
.with_child( .with_child(
MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |_, cx| { MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
let container_style = collab_theme
.disclosure
.button
.style_for(mouse_state)
.container;
if row_hovered || channel.unseen_note_version.is_some() { if row_hovered || channel.unseen_note_version.is_some() {
Svg::new("icons/file.svg") Svg::new("icons/file.svg")
.with_color(if channel.unseen_note_version.is_some() { .with_color(if channel.unseen_note_version.is_some() {
@ -2067,6 +2083,8 @@ impl CollabPanel {
.constrained() .constrained()
.with_width(collab_theme.channel_hash.width) .with_width(collab_theme.channel_hash.width)
.contained() .contained()
.with_style(container_style)
.with_uniform_padding(4.)
.with_margin_right(collab_theme.channel_hash.container.margin.left) .with_margin_right(collab_theme.channel_hash.container.margin.left)
.with_tooltip::<NotesTooltip>( .with_tooltip::<NotesTooltip>(
ix as usize, ix as usize,
@ -2076,13 +2094,16 @@ impl CollabPanel {
cx, cx,
) )
.into_any() .into_any()
} else { } else if has_messages_notification {
Empty::new() Empty::new()
.constrained() .constrained()
.with_width(collab_theme.channel_hash.width) .with_width(collab_theme.channel_hash.width)
.contained() .contained()
.with_uniform_padding(4.)
.with_margin_right(collab_theme.channel_hash.container.margin.left) .with_margin_right(collab_theme.channel_hash.container.margin.left)
.into_any() .into_any()
} else {
Empty::new().into_any()
} }
}) })
.on_click(MouseButton::Left, move |_, this, cx| { .on_click(MouseButton::Left, move |_, this, cx| {

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"),

View file

@ -21,6 +21,7 @@ export default function contacts_panel(): any {
...text(theme.lowest, "sans", "base"), ...text(theme.lowest, "sans", "base"),
button: icon_button({ variant: "ghost" }), button: icon_button({ variant: "ghost" }),
spacing: 4, spacing: 4,
padding: 4,
}, },
} }
} }