Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display read receipts next to each message as a row of Avatars for users who have seen that message #162

Merged
merged 85 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
5c54512
read_receipt_display
alanpoon Sep 26, 2024
4a78a93
Merge branch 'main' into read_receipt_display
alanpoon Sep 27, 2024
4ad555a
merge main
alanpoon Oct 3, 2024
22638d9
touchup whitespace
alanpoon Oct 4, 2024
0ccb874
Merge branch 'main' into read_receipt_display
alanpoon Oct 4, 2024
f4de445
added avatar img in read_receipts
alanpoon Oct 10, 2024
e4e9b1d
fixed tooltip position
alanpoon Oct 10, 2024
961a00f
code formating
alanpoon Oct 10, 2024
2ff7f7d
Merge branch 'main' into read_receipt_display
alanpoon Oct 10, 2024
d4fd338
Merge branch 'main' into read_receipt_display
alanpoon Oct 10, 2024
c15e8da
fix merge issue
alanpoon Oct 10, 2024
3571b1e
change sender_id to generic avatar_id
alanpoon Oct 10, 2024
0c6dd14
Merge branch 'main' into read_receipt_display
alanpoon Oct 12, 2024
24daf6e
fix conflicts
alanpoon Oct 12, 2024
e5f6943
Merge branch 'main' into read_receipt_display
alanpoon Nov 4, 2024
687bb70
Update src/profile/user_profile.rs
alanpoon Nov 4, 2024
17d1bec
remove unneccessary portal_list
alanpoon Nov 4, 2024
fa5f166
Change tooltip
alanpoon Nov 4, 2024
0e6ae80
Merge branch 'main' into read_receipt_display
alanpoon Nov 5, 2024
a36049f
changed to avatarref
alanpoon Nov 6, 2024
b3d75b9
format fix
alanpoon Nov 6, 2024
7935ffb
Merge branch 'read_receipt_display' of https://github.com/alanpoon/ro…
alanpoon Nov 6, 2024
9a7af7a
moved set_avatar_and_get_username into avatar file
alanpoon Nov 7, 2024
7fa6f39
added plus label at the end of avatarrow
alanpoon Nov 7, 2024
b81e51a
clean up
alanpoon Nov 7, 2024
eeb3384
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Nov 25, 2024
29c010b
added import
alanpoon Dec 3, 2024
49798ca
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Dec 3, 2024
3005aea
code cleanup and resolve conflict
alanpoon Dec 3, 2024
38ae816
shorten tooltip's width
alanpoon Dec 4, 2024
e7ca500
fix formating
alanpoon Dec 9, 2024
b1e6d14
removed generic in set_avatar_row
alanpoon Dec 9, 2024
edbcbf3
Merge branch 'read_receipt_display' of https://github.com/alanpoon/ro…
alanpoon Dec 9, 2024
567d2f7
fixed clippy
alanpoon Dec 9, 2024
d0a3cfb
fix doc
alanpoon Dec 9, 2024
07b5fc7
Create a generic RoomScreenTooltipActions
alanpoon Dec 10, 2024
08d2391
avatarrow cleanup
alanpoon Dec 10, 2024
efa3eed
z
alanpoon Dec 12, 2024
ad8d053
minor cleanup
alanpoon Dec 12, 2024
22aa294
Added human_readable name in read_receipt tooltip
alanpoon Dec 13, 2024
6fe7749
Adding avatarRef and its drawn status
alanpoon Dec 13, 2024
dd5e766
fix discrepancy in read_receipts
alanpoon Dec 19, 2024
b60a4ce
Merge branch 'main' into read_receipt_display
alanpoon Dec 19, 2024
52be02c
remove sync's comment
alanpoon Dec 19, 2024
226ecbe
fix clippy and some fmt
alanpoon Dec 19, 2024
b049f50
Adjust to use latest makepad
alanpoon Dec 19, 2024
ca17887
set TOOLTIP_LENGTH
alanpoon Dec 19, 2024
e29023d
fix human_readable_list and doc improvement
alanpoon Dec 20, 2024
92a1da9
Using HoverIn Struct
alanpoon Jan 1, 2025
65f1217
Hoverin struct
alanpoon Jan 1, 2025
a64da1d
hoverin tooltip struct
alanpoon Jan 1, 2025
28a0b38
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Jan 1, 2025
07613f6
remove sync_once
alanpoon Jan 14, 2025
9a435ac
remove unused
alanpoon Jan 14, 2025
2a747fb
Merge branch 'main' into read_receipt_display
alanpoon Jan 14, 2025
467619a
fixed typo
alanpoon Jan 15, 2025
882a4e6
fix doc
alanpoon Jan 15, 2025
0cfb6f7
Add comment to AvatarRow margin
alanpoon Jan 15, 2025
5f78da0
Merge branch 'read_receipt_display' of https://github.com/alanpoon/ro…
alanpoon Jan 15, 2025
ba4e6c2
Hover Improvement
alanpoon Jan 15, 2025
298148b
clean doc
alanpoon Jan 15, 2025
ae4a357
Added comment for HoverOut
alanpoon Jan 15, 2025
18015eb
doc improvement
alanpoon Jan 15, 2025
d36e2c0
remove strange margin
alanpoon Jan 15, 2025
2ebc526
Merge branch 'main' into read_receipt_display
alanpoon Jan 16, 2025
a034a25
minor fix
alanpoon Jan 16, 2025
0d8f6b8
Move `room_screen_tooltip` within the `room_screen_wrapper`
kevinaboos Jan 16, 2025
a414cc5
fix read_receipts layout
alanpoon Jan 20, 2025
0709cc8
Merge branch 'main' into read_receipt_display
alanpoon Jan 20, 2025
301bfc0
Added callout for read_receipt tooltip
alanpoon Jan 20, 2025
2a57b90
fix clippy
alanpoon Jan 20, 2025
c6f3d7e
Merge branch 'main' into read_receipt_display
alanpoon Jan 21, 2025
0e439d8
tooltip improvement
alanpoon Jan 21, 2025
da8452c
Doc improvement on avatar read_receipt
alanpoon Jan 21, 2025
5ff2a79
fix clippy
alanpoon Jan 21, 2025
eca2d68
revert force inner.children clear
alanpoon Jan 21, 2025
118247c
Set max visible length to 3 to prevent tooltip from being too long
alanpoon Jan 21, 2025
34ed793
sped up avatar_row, right align read receipt
alanpoon Jan 22, 2025
54b7945
minor cleanup, indentation fixes, etc
kevinaboos Jan 23, 2025
6c4b972
parameterise human_readable_list, remove avatar_row's margin
alanpoon Jan 23, 2025
2f50969
Merge branch 'read_receipt_display' of https://github.com/alanpoon/ro…
alanpoon Jan 23, 2025
25fee9b
fix human-readable function
alanpoon Jan 23, 2025
a418b92
Fixed ImageMessage's read receipt
alanpoon Jan 23, 2025
01756fa
Merge branch 'main' into read_receipt_display
alanpoon Jan 23, 2025
29cd900
minor cleanup. Adjust margin for AvatarRow of read receipts
kevinaboos Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/avatar_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ pub fn get_or_fetch_avatar(
},
Entry::Occupied(occupied) => return occupied.get().clone(),
}

submit_async_request(MatrixRequest::FetchAvatar {
mxc_uri,
on_fetched: enqueue_avatar_update,
Expand Down
30 changes: 7 additions & 23 deletions src/home/event_reaction_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use crate::profile::user_profile_cache::get_user_profile_and_room_member;
use crate::home::room_screen::RoomScreenTooltipActions;
use indexmap::IndexMap;

use super::room_screen::room_screen_tooltip_position_helper;

const TOOLTIP_WIDTH: f64 = 200.0;
const EMOJI_BORDER_COLOR_INCLUDE_SELF: Vec4 = Vec4 { x: 0.0, y: 0.6, z: 0.47, w: 1.0 }; // DarkGreen
const EMOJI_BORDER_COLOR_NOT_INCLUDE_SELF: Vec4 = Vec4 { x: 0.714, y: 0.73, z: 0.75, w: 1.0 }; // Grey
Expand Down Expand Up @@ -120,33 +122,15 @@ impl Widget for ReactionList {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
let uid: WidgetUid = self.widget_uid();
let app_state = scope.data.get::<crate::app::AppState>().unwrap();

let Some(window_geom) = &app_state.window_geom else { return };
for (widget_ref, reaction_data) in self.children.iter() {
match event.hits(cx, widget_ref.area()) {
Hit::FingerHoverIn(_) => {
let widget_rect = widget_ref.area().rect(cx);
let mut too_close_to_right = false;
if let Some(window_geom) = &app_state.window_geom {
if (widget_rect.pos.x + widget_rect.size.x) + TOOLTIP_WIDTH > window_geom.inner_size.x {
too_close_to_right = true;
}
}
let tooltip_pos = if too_close_to_right {
DVec2 {
x: widget_rect.pos.x + (widget_rect.size.x - TOOLTIP_WIDTH),
y: widget_rect.pos.y + widget_rect.size.y
}
} else {
DVec2 {
x: widget_rect.pos.x + widget_rect.size.x,
y: widget_rect.pos.y - 5.0
}
};
let callout_offset = if too_close_to_right {
TOOLTIP_WIDTH - (widget_rect.size.x - 5.0) / 2.0
} else {
10.0
};
let (tooltip_pos,
callout_offset,
too_close_to_right,
) = room_screen_tooltip_position_helper(widget_rect, window_geom, TOOLTIP_WIDTH);
cx.widget_action(uid, &scope.path, RoomScreenTooltipActions::HoverInReactionButton {
tooltip_pos,
tooltip_width: TOOLTIP_WIDTH,
Expand Down
2 changes: 2 additions & 0 deletions src/home/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod main_desktop_ui;
pub mod main_mobile_ui;
pub mod room_preview;
pub mod room_screen;
pub mod room_read_receipt;
pub mod rooms_list;
pub mod rooms_sidebar;
pub mod spaces_dock;
Expand All @@ -20,6 +21,7 @@ pub fn live_design(cx: &mut Cx) {
rooms_list::live_design(cx);
room_preview::live_design(cx);
room_screen::live_design(cx);
room_read_receipt::live_design(cx);
rooms_sidebar::live_design(cx);
main_mobile_ui::live_design(cx);
main_desktop_ui::live_design(cx);
Expand Down
224 changes: 224 additions & 0 deletions src/home/room_read_receipt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
use crate::app::AppState;
use crate::profile::user_profile_cache::get_user_profile_and_room_member;
use crate::shared::avatar::{AvatarRef, AvatarWidgetRefExt};
use crate::home::room_screen::RoomScreenTooltipActions;
use crate::utils::human_readable_list;
use indexmap::IndexMap;
use makepad_widgets::*;
use matrix_sdk::ruma::{events::receipt::Receipt, EventId, OwnedUserId, RoomId};
use matrix_sdk_ui::timeline::EventTimelineItem;
use std::cmp;
use super::room_screen::room_screen_tooltip_position_helper;

/// The default width of the room screen tooltip for read receipts.
const TOOLTIP_WIDTH: f64 = 180.0;

/// The maximum number of items to display in the read receipts AvatarRow
/// and its accompanying tooltip.
pub const MAX_VISIBLE_AVATARS_IN_READ_RECEIPT: usize = 3;


live_design! {
use link::theme::*;
use link::shaders::*;
use link::widgets::*;

use crate::shared::avatar::*;
use crate::shared::styles::*;

pub AvatarRow = {{AvatarRow}} {
avatar_template: <Avatar> {
width: 15.0,
height: 15.0,
text_view = {
text = {
draw_text: {
text_style: { font_size: 6.0 }
}
}
}
}
margin: {top: 5, right: 0},
width: Fit,
height: 15.0,
plus_template: <Label> {
draw_text: {
color: #x0,
text_style: <TITLE_TEXT>{ font_size: 11}
}
text: ""
}
}
}
/// The widget that displays a list of read receipts.
#[derive(Live, Widget, LiveHook)]
pub struct AvatarRow {
#[redraw]
#[live]
draw_text: DrawText,
#[deref]
deref: View,
#[walk]
walk: Walk,
/// The template for the avatars
#[live]
avatar_template: Option<LivePtr>,
#[layout]
layout: Layout,
/// Label template for truncated number of people seen
#[live]
plus_template: Option<LivePtr>,
/// A vector containing its avatarRef, its drawn status and username
///
/// Storing the drawn status helps prevent unnecessary set avatar in the draw_walk function
#[rust]
buttons: Vec<(AvatarRef, bool)>,
#[rust]
label: Option<LabelRef>,
/// The area of the widget
#[redraw]
#[rust]
area: Area,
/// The read receipts for this row
///
/// Contains a map of user id required to render its tooltip
#[rust]
read_receipts: Option<indexmap::IndexMap<matrix_sdk::ruma::OwnedUserId, Receipt>>
}

impl Widget for AvatarRow {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
let Some(read_receipts) = &self.read_receipts else { return };
if read_receipts.is_empty() { return; }
let uid: WidgetUid = self.widget_uid();
let app_state = scope.data.get_mut::<AppState>().unwrap();
let Some(window_geom) = &app_state.window_geom else { return };
let widget_rect = self.area.rect(cx);
match event.hits(cx, self.area) {
Hit::FingerHoverIn(_) => {
let (tooltip_pos,
callout_offset,
too_close_to_right,
) = room_screen_tooltip_position_helper(widget_rect, window_geom, TOOLTIP_WIDTH);
if let Some(read_receipts) = &self.read_receipts {
cx.widget_action(uid, &scope.path, RoomScreenTooltipActions::HoverInReadReceipt{
tooltip_pos,
callout_offset,
read_receipts: read_receipts.clone(),
tooltip_width: TOOLTIP_WIDTH,
pointing_up: too_close_to_right,
});
}
}
Hit::FingerHoverOut(_) => {
cx.widget_action(uid, &scope.path, RoomScreenTooltipActions::HoverOut);
}
_ => {}
}
}

fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
let Some(read_receipts) = &self.read_receipts else { return DrawStep::done() };
if read_receipts.is_empty() { return DrawStep::done() }
cx.begin_turtle(walk, Layout::default());
for (avatar_ref, _) in self.buttons.iter_mut() {
let _ = avatar_ref.draw(cx, scope);
}
if read_receipts.len() > MAX_VISIBLE_AVATARS_IN_READ_RECEIPT {
if let Some(label) = &mut self.label {
label.set_text(&format!(" + {:?}", read_receipts.len() - MAX_VISIBLE_AVATARS_IN_READ_RECEIPT));
let _ = label.draw(cx, scope);
}
}
cx.end_turtle_with_area(&mut self.area);
DrawStep::done()
}
}
impl AvatarRow {
/// Sets the avatar row with the given receipts map.
///
/// If the length of the receipts map changes, the number of avatar buttons is updated.
/// Each avatar button is then updated with the correct username and drawn status by calling
/// `set_avatar_and_get_username` on it.
/// Finally, the `read_receipts` field is updated to contain a clone of the given receipts map.
///
/// This function is called by the `RoomScreen` widget when it needs to update the read receipts list.
pub fn set_avatar_row(
&mut self,
cx: &mut Cx,
room_id: &RoomId,
event_id: Option<&EventId>,
receipts_map: &IndexMap<OwnedUserId, Receipt>,
) {
if receipts_map.len() != self.buttons.len() {
self.buttons.clear();
for _ in 0..cmp::min(MAX_VISIBLE_AVATARS_IN_READ_RECEIPT, receipts_map.len()) {
self.buttons.push((WidgetRef::new_from_ptr(cx, self.avatar_template).as_avatar(), false));
}
self.label = Some(WidgetRef::new_from_ptr(cx, self.plus_template).as_label());
self.read_receipts = Some(receipts_map.clone());
}
for ((avatar_ref, drawn), (user_id, _)) in self.buttons.iter_mut().zip(receipts_map.iter().rev()) {
if !*drawn {
let (_, drawn_status) = avatar_ref.set_avatar_and_get_username(cx, room_id, user_id, None, event_id);
*drawn = drawn_status;
}
}
}
}
impl AvatarRowRef {
/// Handles hover in action
pub fn hover_in(&self, actions: &Actions) -> RoomScreenTooltipActions {
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
item.cast()
} else {
RoomScreenTooltipActions::None
}
}
/// Returns true if the action is a hover out
pub fn hover_out(&self, actions: &Actions) -> bool {
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
matches!(item.cast(), RoomScreenTooltipActions::HoverOut)
} else {
false
}
}
/// See [`AvatarRow::set_avatar_row()`].
pub fn set_avatar_row(&mut self, cx: &mut Cx, room_id: &RoomId, event_id: Option<&EventId>, receipts_map: &IndexMap<OwnedUserId, Receipt>) {
if let Some(ref mut inner) = self.borrow_mut() {
inner.set_avatar_row(cx, room_id, event_id, receipts_map);
}
}
}

/// Populate the read receipts avatar row in a message item
///
/// Given a reference to item widget (typically a MessageEventMarker), a Cx2d, a
/// room ID, and an EventTimelineItem, this will populate the avatar
/// row of the item with the read receipts of the event.
///
pub fn populate_read_receipts(item: &WidgetRef, cx: &mut Cx, room_id: &RoomId, event_tl_item: &EventTimelineItem) {
item.avatar_row(id!(avatar_row)).set_avatar_row(cx, room_id, event_tl_item.event_id(), event_tl_item.read_receipts());
}

/// Populate the tooltip text for a read receipts avatar row.
///
/// Given a Cx2d, an IndexMap of read receipts, and a room ID, this
/// will populate the tooltip text for the read receipts avatar row.
///
/// The tooltip will contain up to the first `MAX_VISIBLE_AVATARS_IN_READ_RECEIPT` displayable names of the users
/// who have seen this event. If there are more than `MAX_VISIBLE_AVATARS_IN_READ_RECEIPT` users, the tooltip
/// will contain the string "and N others".
pub fn populate_tooltip(cx: &mut Cx, read_receipts: IndexMap<OwnedUserId, Receipt>, room_id: &RoomId) -> String {
let mut display_names: Vec<String> = read_receipts.iter().rev().take(MAX_VISIBLE_AVATARS_IN_READ_RECEIPT).map(|(user_id, _)| {
if let (Some(profile), _ ) = get_user_profile_and_room_member(cx, user_id.clone(), room_id, true) {
profile.displayable_name().to_owned()
} else {
user_id.to_string()
}
}).collect();
for _ in display_names.len()..read_receipts.len() {
display_names.push(String::from(""));
}
format!("Seen by {}:\n{}", read_receipts.len(), human_readable_list(&display_names, MAX_VISIBLE_AVATARS_IN_READ_RECEIPT))
}
Loading
Loading