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 number of unread messages as a badge on the jump-to-bottom button. Send fully-read receipts more efficiently. #206

Merged
merged 81 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
5657931
reduce complexity of send_read_receipt
alanpoon Oct 4, 2024
bb92afb
cleanup
alanpoon Oct 4, 2024
9255365
removed unnecessary import
alanpoon Oct 4, 2024
24be468
added unread_message_badge
alanpoon Oct 10, 2024
c1d1ab6
remove unneccesary changes
alanpoon Oct 11, 2024
35c5977
add jump to top
alanpoon Oct 11, 2024
bc2aa8c
added unread_notification_badge
alanpoon Oct 11, 2024
a9e4945
Merge branch 'main' into jtb_change_in_timeline_item#131
alanpoon Oct 12, 2024
053c8f0
Merge branch 'main' into send_read_receipt_improve
alanpoon Oct 13, 2024
e4c1268
Added scroll past marker
alanpoon Oct 13, 2024
072b9cb
Merge branch 'jtb_change_in_timeline_item#131' into send_read+jtb
alanpoon Oct 13, 2024
3884a66
wip
alanpoon Oct 14, 2024
b7a83a0
send read +jtb
alanpoon Oct 15, 2024
a3b1f10
Merge branch 'main' into send_read+jtb
alanpoon Oct 15, 2024
3824160
added some docs, remove icon_jump_to_top
alanpoon Nov 4, 2024
5633e2b
Merge branch 'main' into send_read+jtb
alanpoon Nov 4, 2024
44f4c25
merge .cargo
alanpoon Nov 4, 2024
9076c53
minor formating
alanpoon Nov 4, 2024
5bf2413
change to start_timeout
alanpoon Nov 4, 2024
1c66730
Merge branch 'main' into send_read+jtb
alanpoon Nov 6, 2024
8bbde58
removed jump_to_top & fix timer issue
alanpoon Nov 6, 2024
b99a57d
set read event into RoomInfo's fully read when sending
alanpoon Nov 6, 2024
99ff1b1
restructure scroll pass read marker logic
alanpoon Nov 6, 2024
0b89757
Merge branch 'main' into send_read+jtb
alanpoon Nov 18, 2024
009077f
Added unread_notification badge
alanpoon Nov 19, 2024
aba7e69
code cleanup
alanpoon Nov 19, 2024
2ce7a20
Merge branch 'main' into send_read+jtb
alanpoon Nov 19, 2024
945d3a1
fix indentation and grammar
alanpoon Nov 19, 2024
170488c
minor comment changes
alanpoon Nov 19, 2024
20982c2
fix not send read receipt when not scrolling
alanpoon Nov 19, 2024
1e5e36c
removed last_display_event
alanpoon Nov 19, 2024
b4b5116
Using fully_read_event's timestamp to determine scrolled_past_fully_read
alanpoon Nov 23, 2024
84faf5e
added doc, use timer.is_empty to check for events
alanpoon Nov 23, 2024
e748196
code cleanup
alanpoon Nov 23, 2024
963a722
Code improvement n log improvement
alanpoon Nov 25, 2024
1566621
changed and_then to map
alanpoon Nov 27, 2024
3627a1f
changed to get_fully_read_event
alanpoon Nov 27, 2024
07df9fd
cap at 99+
alanpoon Nov 27, 2024
b44105a
Merge branch 'send_read+jtb' of https://github.com/alanpoon/robrix in…
alanpoon Nov 27, 2024
c32b7d5
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Nov 27, 2024
1e7d4b2
change to get_fully_read_event
alanpoon Nov 27, 2024
ced53e5
Move client num_of_unread to async
alanpoon Nov 28, 2024
1caacc1
make oval for 99+
alanpoon Nov 28, 2024
22d8e30
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Nov 28, 2024
35b8b02
fix clippy warning
alanpoon Nov 28, 2024
906e53e
fix error handling for set_fully_read_event
alanpoon Nov 28, 2024
fe9973b
added subscribe to update read_receipt
alanpoon Dec 2, 2024
3d090fb
Added comments for subscribe to Updates
alanpoon Dec 2, 2024
b12592a
added new lines and fix clippy
alanpoon Dec 3, 2024
eb857c6
added update_subscriber_initialised check
alanpoon Dec 3, 2024
794cd0a
fix clippy
alanpoon Dec 3, 2024
9893d8e
doc improvement
alanpoon Dec 25, 2024
0c5c5c7
fix doc grammer
alanpoon Dec 25, 2024
927fc8f
fix formatting
alanpoon Dec 25, 2024
ebebb45
remove unnecessary "send"
alanpoon Dec 25, 2024
a4be390
Merge branch 'main' into send_read+jtb
alanpoon Dec 25, 2024
1e0a6a6
minor fix
alanpoon Dec 25, 2024
ff22301
create additional green view
alanpoon Dec 25, 2024
3028b7b
Change Num to Number
alanpoon Dec 26, 2024
9011594
removed serde_json for read_receipts
alanpoon Dec 26, 2024
1643fc9
removed serde_json
alanpoon Dec 26, 2024
8b22003
removed 5 sec timer and use latest_user_read_receipt_timeline_event_id
alanpoon Dec 26, 2024
4c7f168
minor fix
alanpoon Dec 27, 2024
82ed3c5
fix multiline function
alanpoon Dec 30, 2024
30b6af1
fixed doc
alanpoon Dec 30, 2024
1cb9524
Merge branch 'send_read+jtb' of https://github.com/alanpoon/robrix in…
alanpoon Dec 30, 2024
05e0889
Added UnReadMessageCount
alanpoon Dec 30, 2024
d56d237
Removed get_fully_event
alanpoon Dec 31, 2024
50a5cce
Doc fix
alanpoon Jan 1, 2025
5ed414b
Doc fix
alanpoon Jan 1, 2025
ed649a0
Unread fix typo
alanpoon Jan 1, 2025
c156d5a
Doc Fix
alanpoon Jan 1, 2025
2f746d0
Doc Fix
alanpoon Jan 1, 2025
81ed2f1
fix copy-paste error message here
alanpoon Jan 1, 2025
9f55e61
Fix spelling
alanpoon Jan 1, 2025
43c3104
center align the unread badge
alanpoon Jan 1, 2025
7c58b5f
Merge remote-tracking branch 'origin' into send_read+jtb
alanpoon Jan 1, 2025
2b2a9dc
restore whitespace
alanpoon Jan 1, 2025
077c23a
Merge branch 'main' into send_read+jtb
kevinaboos Jan 2, 2025
ab540fd
simplify unread message handling and code structure
kevinaboos Jan 2, 2025
2f9d2a2
clippy fix
kevinaboos Jan 2, 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
156 changes: 87 additions & 69 deletions src/home/room_screen.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//! A room screen is the UI page that displays a single Room's timeline of events/messages
//! along with a message input bar at the bottom.

use std::{borrow::Cow, collections::{BTreeMap, HashMap}, ops::{DerefMut, Range}, sync::{Arc, Mutex}, time::{Instant, SystemTime}};
use std::{borrow::Cow, collections::BTreeMap, ops::{DerefMut, Range}, sync::{Arc, Mutex}, time::SystemTime};

use bytesize::ByteSize;
use imbl::Vector;
use makepad_widgets::*;
use matrix_sdk::{
ruma::{
events::{room::{
events::{receipt::Receipt, room::{
message::{
AudioMessageEventContent, CustomEventContent, EmoteMessageEventContent, FileMessageEventContent, FormattedBody, ImageMessageEventContent, KeyVerificationRequestEventContent, LocationMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, RoomMessageEventContent, ServerNoticeMessageEventContent, TextMessageEventContent, VideoMessageEventContent
}, ImageInfo, MediaSource
Expand All @@ -26,7 +26,7 @@ use crate::{
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
user_profile_cache,
}, shared::{
avatar::{AvatarRef, AvatarWidgetRefExt}, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt}, jump_to_bottom_button::JumpToBottomButtonWidgetExt, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, typing_animation::TypingAnimationWidgetExt
avatar::{AvatarRef, AvatarWidgetRefExt}, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, typing_animation::TypingAnimationWidgetExt
}, sliding_sync::{self, get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineRequestSender}, utils::{self, unix_time_millis_to_datetime, ImageFormat, MediaFormatConst}
};
use rangemap::RangeSet;
Expand All @@ -50,6 +50,7 @@ live_design! {
use crate::shared::avatar::Avatar;
use crate::shared::text_or_image::TextOrImage;
use crate::shared::html_or_plaintext::*;
use crate::shared::icon_button::*;
use crate::profile::user_profile::UserProfileSlidingPane;
use crate::shared::typing_animation::TypingAnimation;
use crate::shared::icon_button::*;
Expand Down Expand Up @@ -992,8 +993,6 @@ pub struct RoomScreen {
#[rust] room_name: String,
/// The persistent UI-relevant states for the room that this widget is currently displaying.
#[rust] tl_state: Option<TimelineUiState>,
/// 5 secs timer when scroll ends
#[rust] fully_read_timer: Timer,
}
impl Drop for RoomScreen {
fn drop(&mut self) {
Expand Down Expand Up @@ -1254,19 +1253,6 @@ impl Widget for RoomScreen {
}
}

// Mark events as fully read after they have been displayed on screen for 5 seconds.
if self.fully_read_timer.is_event(event).is_some() {
if let (Some(ref mut tl_state), Some(ref _room_id)) = (&mut self.tl_state, &self.room_id) {
for (k, (room, event, start, ref mut moved_to_queue)) in &mut tl_state.read_event_hashmap {
if start.elapsed() > std::time::Duration::new(5, 0) && !*moved_to_queue{
tl_state.marked_fully_read_queue.insert(k.clone(), (room.clone(), event.clone()));
*moved_to_queue = true;
}
}
}
cx.stop_timer(self.fully_read_timer);
}

if self.animator_handle_event(cx, event).must_redraw() {
self.redraw(cx);
}
Expand Down Expand Up @@ -1528,7 +1514,8 @@ impl RoomScreen {
log!("Timeline::handle_event(): jumping view from event index {curr_item_idx} to new index {new_item_idx}, scroll {new_item_scroll}, event ID {_event_id}");
portal_list.set_first_id_and_scroll(new_item_idx, new_item_scroll);
tl.prev_first_index = Some(new_item_idx);
cx.stop_timer(self.fully_read_timer);
// Set scrolled_past_read_marker false when we jump to a new event
tl.scrolled_past_read_marker = false;
}
}
//
Expand All @@ -1540,10 +1527,13 @@ impl RoomScreen {

// If new items were appended to the end of the timeline, show an unread messages badge on the jump to bottom button.
if is_append && !portal_list.is_at_end() {
// log!("is_append was true, showing unread message badge on the jump to bottom button visible");
jump_to_bottom.show_unread_message_badge(1);
if let Some(room_id) = &self.room_id {
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
// Immediately show the unread badge with no count while we fetch the actual count in the background.
jump_to_bottom.show_unread_message_badge(cx, UnreadMessageCount::Unknown);
submit_async_request(MatrixRequest::GetNumberUnreadMessages{ room_id: room_id.clone() });
}
}

if clear_cache {
tl.content_drawn_since_last_update.clear();
tl.profile_drawn_since_last_update.clear();
Expand Down Expand Up @@ -1577,6 +1567,9 @@ impl RoomScreen {
tl.items = new_items;
done_loading = true;
}
TimelineUpdate::NewUnreadMessagesCount(unread_messages_count) => {
jump_to_bottom.show_unread_message_badge(cx, unread_messages_count);
}
TimelineUpdate::TargetEventFound { target_event_id, index } => {
// log!("Target event found in room {}: {target_event_id}, index: {index}", tl.room_id);
tl.request_sender.send_if_modified(|requests| {
Expand Down Expand Up @@ -1688,6 +1681,9 @@ impl RoomScreen {
input_bar.set_visible(can_user_send_message);
can_not_send_message_notice.set_visible(!can_user_send_message);
}
TimelineUpdate::OwnUserReadReceipt(receipt) => {
tl.latest_own_user_receipt = Some(receipt);
}
}
}

Expand Down Expand Up @@ -1934,8 +1930,8 @@ impl RoomScreen {
message_highlight_animation_state: MessageHighlightAnimationState::default(),
last_scrolled_index: usize::MAX,
prev_first_index: None,
read_event_hashmap: HashMap::new(),
marked_fully_read_queue: HashMap::new(),
scrolled_past_read_marker: false,
latest_own_user_receipt: None,
};
(new_tl_state, true)
};
Expand All @@ -1949,6 +1945,7 @@ impl RoomScreen {
}
);

submit_async_request(MatrixRequest::SubscribeToOwnUserReadReceiptsChanged { room_id: room_id.clone(), subscribe: true });
// Kick off a back pagination request for this room. This is "urgent",
// because we want to show the user some messages as soon as possible
// when they first open the room, and there might not be any messages yet.
Expand Down Expand Up @@ -2082,7 +2079,7 @@ impl RoomScreen {
/// Sends read receipts based on the current scroll position of the timeline.
fn send_user_read_receipts_based_on_scroll_pos(
&mut self,
cx: &mut Cx,
_cx: &mut Cx,
actions: &ActionsBuf,
portal_list: &PortalListRef,
) {
Expand All @@ -2091,52 +2088,55 @@ impl RoomScreen {
return;
}
let first_index = portal_list.first_id();

let Some(tl_state) = self.tl_state.as_mut() else { return };
let Some(room_id) = self.room_id.as_ref() else { return };

if let Some(ref mut index) = tl_state.prev_first_index {
// to detect change of scroll when scroll ends
if *index != first_index {
// scroll changed
self.fully_read_timer = cx.start_interval(5.0);
let time_now = std::time::Instant::now();
if first_index > *index {
// Store visible event messages with current time into a hashmap
let mut read_receipt_event = None;
for r in first_index .. (first_index + portal_list.visible_items() + 1) {
if let Some(v) = tl_state.items.get(r) {
if let Some(e) = v.as_event().and_then(|f| f.event_id()) {
read_receipt_event = Some(e.to_owned());
tl_state.read_event_hashmap
.entry(e.to_string())
.or_insert_with(|| (room_id.clone(), e.to_owned(), time_now, false));
}
}
}
if let Some(event_id) = read_receipt_event {
submit_async_request(MatrixRequest::ReadReceipt { room_id: room_id.clone(), event_id });
}
let mut fully_read_receipt_event = None;
// Implements sending fully read receipts when message is scrolled out of first row
for r in *index..first_index {
if let Some(v) = tl_state.items.get(r) {
if let Some(e) = v.as_event().and_then(|f| f.event_id()) {
let mut to_remove = vec![];
for (event_id_string, (_, event_id)) in &tl_state.marked_fully_read_queue {
if e == event_id {
fully_read_receipt_event = Some(event_id.clone());
to_remove.push(event_id_string.clone());
}
}
for r in to_remove {
tl_state.marked_fully_read_queue.remove(&r);
}
if first_index >= *index {
// Get event_id and timestamp for the last visible event
let Some((last_event_id, last_timestamp)) = tl_state
.items
.get(first_index + portal_list.visible_items())
.and_then(|f| f.as_event())
.and_then(|f| f.event_id().map(|e| (e, f.timestamp())))
else {
*index = first_index;
return;
};
submit_async_request(MatrixRequest::ReadReceipt {
room_id: tl_state.room_id.clone(),
event_id: last_event_id.to_owned(),
});
if tl_state.scrolled_past_read_marker {
submit_async_request(MatrixRequest::FullyReadReceipt {
room_id: tl_state.room_id.clone(),
event_id: last_event_id.to_owned(),
});
} else {
if let Some(own_user_receipt_timestamp) = &tl_state.latest_own_user_receipt.clone()
.and_then(|receipt| receipt.ts) {
let Some((_first_event_id, first_timestamp)) = tl_state
.items
.get(first_index)
.and_then(|f| f.as_event())
.and_then(|f| f.event_id().map(|e| (e, f.timestamp())))
else {
*index = first_index;
return;
};
if own_user_receipt_timestamp >= &first_timestamp
&& own_user_receipt_timestamp <= &last_timestamp
{
tl_state.scrolled_past_read_marker = true;
submit_async_request(MatrixRequest::FullyReadReceipt {
room_id: tl_state.room_id.clone(),
event_id: last_event_id.to_owned(),
});
}

}
}
if let Some(event_id) = fully_read_receipt_event {
submit_async_request(MatrixRequest::FullyReadReceipt { room_id: room_id.clone(), event_id: event_id.clone()});
}
}
*index = first_index;
}
Expand Down Expand Up @@ -2208,6 +2208,8 @@ pub enum TimelineUpdate {
/// This supersedes `index_of_first_change` and is used when the entire timeline is being redrawn.
clear_cache: bool,
},
/// The updated number of unread messages in the room.
NewUnreadMessagesCount(UnreadMessageCount),
/// The target event ID was found at the given `index` in the timeline items vector.
///
/// This means that the RoomScreen widget can scroll the timeline up to this event,
Expand Down Expand Up @@ -2250,9 +2252,10 @@ pub enum TimelineUpdate {
/// The list of users (their displayable name) who are currently typing in this room.
users: Vec<String>,
},
/// A notice that the permission of user's ability to send messages in this room,
/// this condition is simple so that we only use `bool`
CanUserSendMessage (bool)
/// An update containing whether the user is permitted to send messages in this room.
CanUserSendMessage(bool),
/// An update to the currently logged-in user's own read receipt for this room.
OwnUserReadReceipt(Receipt),
}

/// The global set of all timeline states, one entry per room.
Expand Down Expand Up @@ -2334,9 +2337,24 @@ struct TimelineUiState {
/// at which point we submit a backwards pagination request to fetch more events.
last_scrolled_index: usize,

/// The index of the first item shown in the timeline's PortalList from *before* the last "jump".
///
/// This index is saved before the timeline undergoes any jumps, e.g.,
/// receiving new items, major scroll changes, or other timeline view jumps.
prev_first_index: Option<usize>,
read_event_hashmap: HashMap<String, (OwnedRoomId, OwnedEventId, Instant, bool)>,
marked_fully_read_queue: HashMap<String, (OwnedRoomId, OwnedEventId)>,

/// Whether the user has scrolled past their latest read marker.
///
/// This is used to determine whether we should send a fully-read receipt
/// after the user scrolls past their "read marker", i.e., their latest fully-read receipt.
/// Its value is determined by comparing the fully-read event's timestamp with the
/// first and last timestamp of displayed events in the timeline.
/// When scrolling down, if the value is true, we send a fully-read receipt
/// for the last visible event in the timeline.
///
/// When new message come in, this value is reset to `false`.
scrolled_past_read_marker: bool,
latest_own_user_receipt: Option<Receipt>,
}

#[derive(Default, Debug)]
Expand Down
Loading
Loading