From ac26f0e0132e8e6294e0bb80e167c55b5885a581 Mon Sep 17 00:00:00 2001 From: alanpoon Date: Thu, 19 Sep 2024 20:55:40 +0800 Subject: [PATCH 1/6] simplified send read receipt --- src/home/room_screen.rs | 99 ++++++++++++++++++++++++++++++++++++----- src/sliding_sync.rs | 48 +++++++++++++++++++- 2 files changed, 134 insertions(+), 13 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index ad80beb9..ebda8d59 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -1,7 +1,7 @@ //! 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, ops::{DerefMut, Range}, sync::{Arc, Mutex}}; +use std::{borrow::Cow, collections::{BTreeMap, HashMap}, ops::{DerefMut, Range}, sync::{Arc, Mutex}}; use imbl::Vector; use makepad_widgets::*; @@ -17,20 +17,14 @@ use matrix_sdk_ui::timeline::{ }; use crate::{ - avatar_cache::{self, AvatarCacheEntry}, - event_preview::{text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, - media_cache::{MediaCache, MediaCacheEntry}, - profile::{ + avatar_cache::{self, AvatarCacheEntry}, event_preview::{text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::main_content::MainContentWidgetRefExt, media_cache::{MediaCache, MediaCacheEntry}, profile::{ user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt}, user_profile_cache, - }, - shared::{ + }, shared::{ avatar::{AvatarRef, AvatarWidgetRefExt}, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt}, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, - }, - sliding_sync::{get_client, submit_async_request, take_timeline_update_receiver, MatrixRequest}, - utils::{self, unix_time_millis_to_datetime, MediaFormatConst}, + }, sliding_sync::{get_client, submit_async_request, take_timeline_update_receiver, MatrixRequest}, utils::{self, unix_time_millis_to_datetime, MediaFormatConst} }; use rangemap::RangeSet; @@ -852,8 +846,68 @@ struct RoomScreen { #[rust] room_name: String, /// The UI-relevant states for the room that this widget is currently displaying. #[rust] tl_state: Option, + /// 5 secs timer when scroll ends + #[rust] fullyread_timer: Timer, } +impl RoomScreen{ + fn send_user_read_receipts_based_on_scroll_pos(&mut self,cx:&mut Cx,actions: &Vec>){ + //stopped scrolling + if !self.portal_list(id!(list)).scrolled(actions){ + let portal_list = self.portal_list(id!(list)); + let first_index = portal_list.first_id(); + if let (Some(ref mut tl_state),Some(ref room_id))= (&mut self.tl_state,&self.room_id){ + 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.fullyread_timer = cx.start_interval(5.0); + let time_now = std::time::Instant::now(); + if first_index> *index{ + // Implements sending read receipt when scrolling up to see bottom messages + for r in tl_state.content_drawn_since_last_update.clone(){ + for r in r{ + if r>first_index{ + if let Some(v) = tl_state.items.get(r){ + if let Some((Some(e),Some(m))) = v.as_event().and_then(|f| Some((f.event_id(),f.content().as_message()))){ + submit_async_request(MatrixRequest::ReadReceipt { room_id: room_id.clone(),event_id:e.to_owned(),message:m.body().to_string().clone() }); + if !tl_state.read_event_hashmap.contains_key(&e.to_string()){ + tl_state.read_event_hashmap.insert(e.to_string(), (room_id.clone(),e.to_owned(),time_now,false)); + } + } + } + } + } + } + // 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).clone(){ + if let Some((Some(e),Some(msg))) = v.as_event().and_then(|f| Some((f.event_id(),f.content().as_message()))){ + let mut to_remove = vec![]; + for (event_id_string,(room_id,event_id )) in &tl_state.marked_fully_read_queue{ + if &e.to_owned() == event_id{ + submit_async_request(MatrixRequest::FullyReadReceipt { room_id: room_id.clone(), event_id: event_id.clone(),message:msg.body().to_string().clone() }); + to_remove.push(event_id_string.clone()); + } + } + for r in to_remove{ + tl_state.marked_fully_read_queue.remove(&r); + } + } + } + } + } + + *index = first_index; + } + }else{ + tl_state.prev_first_index = Some(first_index); + } + } + } + + } +} impl Widget for RoomScreen { // Handle events and actions for the RoomScreen widget and its inner Timeline view. fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { @@ -981,6 +1035,7 @@ impl Widget for RoomScreen { } if let Event::Actions(actions) = event { + self.send_user_read_receipts_based_on_scroll_pos(cx,actions); for action in actions { // Handle actions on a message, e.g., clicking the reply button or clicking the reply preview. match action.as_widget_action().cast() { @@ -1201,7 +1256,19 @@ impl Widget for RoomScreen { }); } } - + // mark events as fully read after displayed for 5 seconds + if self.fullyread_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.fullyread_timer); + } // Only forward visibility-related events (touch/tap/scroll) to the inner timeline view // if the user profile sliding pane is not visible. if event.requires_visibility() && pane.is_currently_shown(cx) { @@ -1437,6 +1504,10 @@ impl RoomScreen { media_cache: MediaCache::new(MediaFormatConst::File, Some(update_sender)), replying_to: None, saved_state: SavedState::default(), + prev_first_index: None, + read_event_hashmap:HashMap::new(), + marked_fully_read_queue:HashMap::new(), + prev_fullyread_last_timestamp:None }; (new_tl_state, true) }; @@ -1653,6 +1724,12 @@ struct TimelineUiState { /// The states relevant to the UI display of this timeline that are saved upon /// a `Hide` action and restored upon a `Show` action. saved_state: SavedState, + /// To detect if scroll changes when it is not scrolled + prev_first_index:Option, + // Intermediate hashmap to track display time, last bool value: true indicates moved to queue + read_event_hashmap:HashMap, + // Queue to send fully read receipt + marked_fully_read_queue:HashMap, } /// The item index, scroll position, and optional unique IDs of the first `N` events diff --git a/src/sliding_sync.rs b/src/sliding_sync.rs index e888e7d8..e0f0b7d0 100644 --- a/src/sliding_sync.rs +++ b/src/sliding_sync.rs @@ -5,8 +5,8 @@ use futures_util::{StreamExt, pin_mut}; use imbl::Vector; use makepad_widgets::{error, log, SignalToUI}; use matrix_sdk::{ - config::RequestConfig, media::MediaRequest, room::RoomMember, ruma::{ - api::client::session::get_login_types::v3::LoginType, assign, events::{room::{message::{ForwardThread, RoomMessageEventContent}, MediaSource}, FullStateEventContent, StateEventType}, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UInt, UserId + config::RequestConfig, media::MediaRequest, room::{Receipts,RoomMember}, ruma::{ + api::client::{receipt::create_receipt::v3::ReceiptType,session::get_login_types::v3::LoginType}, assign, events::{receipt::{ReceiptThread}, room::{message::{ForwardThread, RoomMessageEventContent}, MediaSource}, FullStateEventContent, StateEventType}, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UInt, UserId }, sliding_sync::http::request::{AccountData, ListFilters, ToDevice, E2EE}, Client, Room, SlidingSyncList, SlidingSyncMode }; use matrix_sdk_ui::{timeline::{AnyOtherFullStateEventContent, LiveBackPaginationStatus, RepliedToInfo, TimelineDetails, TimelineItem, TimelineItemContent}, Timeline}; @@ -194,6 +194,16 @@ pub enum MatrixRequest { SendTypingNotice { room_id: OwnedRoomId, typing: bool, + }, + ReadReceipt{ + room_id:OwnedRoomId, + event_id:OwnedEventId, + message:String, + }, + FullyReadReceipt{ + room_id:OwnedRoomId, + event_id:OwnedEventId, + message:String, } } @@ -516,7 +526,41 @@ async fn async_worker(mut receiver: UnboundedReceiver) -> Result< SignalToUI::set_ui_signal(); }); } + MatrixRequest::ReadReceipt { room_id,event_id, message }=>{ + let timeline = { + let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); + let Some(room_info) = all_room_info.get_mut(&room_id) else { + log!("BUG: room info not found for send message request {room_id}"); + continue; + }; + room_info.timeline.clone() + }; + let _send_message_task = Handle::current().spawn(async move { + match timeline.send_single_receipt(ReceiptType::Read, ReceiptThread::Unthreaded, event_id.clone()).await { + Ok(_send_handle) => log!("Sent message to room {room_id}.read_marker {event_id} message {message}"), + Err(_e) => error!("Failed to send message to room {room_id}: {_e:?}"), + } + }); + }, + MatrixRequest::FullyReadReceipt { room_id,event_id,message }=>{ + let timeline = { + let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); + let Some(room_info) = all_room_info.get_mut(&room_id) else { + log!("BUG: room info not found for send message request {room_id}"); + continue; + }; + + room_info.timeline.clone() + }; + let _send_message_task = Handle::current().spawn(async move { + let receipt = Receipts::new().fully_read_marker(event_id.clone()); + match timeline.send_multiple_receipts(receipt).await { + Ok(_send_handle) => log!("Sent message to room {room_id}.fully_read_marker {event_id} message {message}"), + Err(_e) => error!("Failed to send message to room {room_id}: {_e:?}"), + } + }); + } } } From 8215a1b55ce1a9a03485ba046ee135c0f9b04acd Mon Sep 17 00:00:00 2001 From: alanpoon Date: Fri, 20 Sep 2024 09:43:52 +0800 Subject: [PATCH 2/6] remove extra field --- src/home/room_screen.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index ebda8d59..4d4ae36a 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -1507,7 +1507,6 @@ impl RoomScreen { prev_first_index: None, read_event_hashmap:HashMap::new(), marked_fully_read_queue:HashMap::new(), - prev_fullyread_last_timestamp:None }; (new_tl_state, true) }; From b5d3017b452a9b5df98b6063cae5fc5dc9d73291 Mon Sep 17 00:00:00 2001 From: alanpoon Date: Mon, 23 Sep 2024 10:43:48 +0800 Subject: [PATCH 3/6] revision of pr --- src/home/room_screen.rs | 106 +++++++++++++++++++++++----------------- src/sliding_sync.rs | 19 +++---- 2 files changed, 68 insertions(+), 57 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 4d4ae36a..3471ada4 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -847,64 +847,75 @@ struct RoomScreen { /// The UI-relevant states for the room that this widget is currently displaying. #[rust] tl_state: Option, /// 5 secs timer when scroll ends - #[rust] fullyread_timer: Timer, + #[rust] fully_read_timer: Timer, } impl RoomScreen{ - fn send_user_read_receipts_based_on_scroll_pos(&mut self,cx:&mut Cx,actions: &Vec>){ + fn send_user_read_receipts_based_on_scroll_pos(&mut self, cx:&mut Cx, actions:&Vec>){ //stopped scrolling - if !self.portal_list(id!(list)).scrolled(actions){ - let portal_list = self.portal_list(id!(list)); - let first_index = portal_list.first_id(); - if let (Some(ref mut tl_state),Some(ref room_id))= (&mut self.tl_state,&self.room_id){ - 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.fullyread_timer = cx.start_interval(5.0); - let time_now = std::time::Instant::now(); - if first_index> *index{ - // Implements sending read receipt when scrolling up to see bottom messages - for r in tl_state.content_drawn_since_last_update.clone(){ - for r in r{ - if r>first_index{ - if let Some(v) = tl_state.items.get(r){ - if let Some((Some(e),Some(m))) = v.as_event().and_then(|f| Some((f.event_id(),f.content().as_message()))){ - submit_async_request(MatrixRequest::ReadReceipt { room_id: room_id.clone(),event_id:e.to_owned(),message:m.body().to_string().clone() }); - if !tl_state.read_event_hashmap.contains_key(&e.to_string()){ - tl_state.read_event_hashmap.insert(e.to_string(), (room_id.clone(),e.to_owned(),time_now,false)); - } - } - } - } + if self.portal_list(id!(list)).scrolled(actions) { + return; + } + let portal_list = self.portal_list(id!(list)); + 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(Some(e)) = v.as_event().and_then(|f| Some(f.event_id())) { + read_receipt_event = Some(e.to_owned()); + if !tl_state.read_event_hashmap.contains_key(&e.to_string()) { + tl_state.read_event_hashmap.insert(e.to_string(), (room_id.clone(),e.to_owned(),time_now,false)); } } - // 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).clone(){ - if let Some((Some(e),Some(msg))) = v.as_event().and_then(|f| Some((f.event_id(),f.content().as_message()))){ - let mut to_remove = vec![]; - for (event_id_string,(room_id,event_id )) in &tl_state.marked_fully_read_queue{ - if &e.to_owned() == event_id{ - submit_async_request(MatrixRequest::FullyReadReceipt { room_id: room_id.clone(), event_id: event_id.clone(),message:msg.body().to_string().clone() }); - to_remove.push(event_id_string.clone()); - } - } - for r in to_remove{ - tl_state.marked_fully_read_queue.remove(&r); - } + } + } + 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).clone() { + if let Some(Some(e)) = v.as_event().and_then(|f| Some(f.event_id())) { + let mut to_remove = vec![]; + for (event_id_string,(_,event_id )) in &tl_state.marked_fully_read_queue { + if &e.to_owned() == 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); + } } } - - *index = first_index; } - }else{ - tl_state.prev_first_index = Some(first_index); + 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; } + }else{ + tl_state.prev_first_index = Some(first_index); } + + } } @@ -968,6 +979,8 @@ impl Widget for RoomScreen { if curr_item_idx != new_item_idx { 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); } } // TODO: after an (un)ignore user event, all timelines are cleared. @@ -996,6 +1009,7 @@ impl Widget for RoomScreen { // log!("Timeline::handle_event(): changed_indices: {changed_indices:?}, items len: {}\ncontent drawn: {:#?}\nprofile drawn: {:#?}", items.len(), tl.content_drawn_since_last_update, tl.profile_drawn_since_last_update); } tl.items = items; + } TimelineUpdate::TimelineStartReached => { log!("Timeline::handle_event(): timeline start reached for room {}", tl.room_id); @@ -1257,7 +1271,7 @@ impl Widget for RoomScreen { } } // mark events as fully read after displayed for 5 seconds - if self.fullyread_timer.is_event(event).is_some() { + 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{ @@ -1267,7 +1281,7 @@ impl Widget for RoomScreen { } } } - cx.stop_timer(self.fullyread_timer); + cx.stop_timer(self.fully_read_timer); } // Only forward visibility-related events (touch/tap/scroll) to the inner timeline view // if the user profile sliding pane is not visible. diff --git a/src/sliding_sync.rs b/src/sliding_sync.rs index e0f0b7d0..cf54ce2b 100644 --- a/src/sliding_sync.rs +++ b/src/sliding_sync.rs @@ -198,12 +198,10 @@ pub enum MatrixRequest { ReadReceipt{ room_id:OwnedRoomId, event_id:OwnedEventId, - message:String, }, FullyReadReceipt{ room_id:OwnedRoomId, event_id:OwnedEventId, - message:String, } } @@ -526,28 +524,27 @@ async fn async_worker(mut receiver: UnboundedReceiver) -> Result< SignalToUI::set_ui_signal(); }); } - MatrixRequest::ReadReceipt { room_id,event_id, message }=>{ + MatrixRequest::ReadReceipt { room_id,event_id }=>{ let timeline = { let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); let Some(room_info) = all_room_info.get_mut(&room_id) else { - log!("BUG: room info not found for send message request {room_id}"); + log!("BUG: room info not found for send read receipt request {room_id}"); continue; }; - room_info.timeline.clone() }; let _send_message_task = Handle::current().spawn(async move { match timeline.send_single_receipt(ReceiptType::Read, ReceiptThread::Unthreaded, event_id.clone()).await { - Ok(_send_handle) => log!("Sent message to room {room_id}.read_marker {event_id} message {message}"), - Err(_e) => error!("Failed to send message to room {room_id}: {_e:?}"), + Ok(_send_handle) => log!("Sent read receipt request to room {room_id} {event_id}"), + Err(_e) => error!("Failed to send read receipt request to room {room_id}: {_e:?}"), } }); }, - MatrixRequest::FullyReadReceipt { room_id,event_id,message }=>{ + MatrixRequest::FullyReadReceipt { room_id,event_id }=>{ let timeline = { let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); let Some(room_info) = all_room_info.get_mut(&room_id) else { - log!("BUG: room info not found for send message request {room_id}"); + log!("BUG: room info not found for send read receipt request {room_id}"); continue; }; @@ -556,8 +553,8 @@ async fn async_worker(mut receiver: UnboundedReceiver) -> Result< let _send_message_task = Handle::current().spawn(async move { let receipt = Receipts::new().fully_read_marker(event_id.clone()); match timeline.send_multiple_receipts(receipt).await { - Ok(_send_handle) => log!("Sent message to room {room_id}.fully_read_marker {event_id} message {message}"), - Err(_e) => error!("Failed to send message to room {room_id}: {_e:?}"), + Ok(_send_handle) => log!("Sent fully read receipt / fully_read_marker to room {room_id} {event_id}"), + Err(_e) => error!("Failed to fully read receipt to room {room_id}: {_e:?}"), } }); } From 8f7078eab72377288047623beb00d5e5deb9ce3f Mon Sep 17 00:00:00 2001 From: alanpoon Date: Mon, 23 Sep 2024 11:08:33 +0800 Subject: [PATCH 4/6] resolve mr conflict --- src/home/room_screen.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 94f68653..c54c9bba 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -1,7 +1,7 @@ //! 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}}; +use std::{borrow::Cow, collections::{BTreeMap, HashMap}, ops::{DerefMut, Range}, sync::{Arc, Mutex}, time::Instant}; use imbl::Vector; use makepad_widgets::*; @@ -1894,6 +1894,10 @@ struct TimelineUiState { /// The states relevant to the UI display of this timeline that are saved upon /// a `Hide` action and restored upon a `Show` action. saved_state: SavedState, + message_highlight_animation_state: MessageHighlightAnimationState, + prev_first_index: Option, + read_event_hashmap: HashMap, + marked_fully_read_queue: HashMap, } /// The item index, scroll position, and optional unique IDs of the first `N` events From a74a84d8e6aa791a0a5359281372ff606ba96438 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Mon, 30 Sep 2024 21:11:08 -0700 Subject: [PATCH 5/6] restore deleted comments, clean up log messages --- src/home/room_screen.rs | 9 +++++++++ src/sliding_sync.rs | 40 +++++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index e1a53578..27bd7200 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -1894,7 +1894,16 @@ struct TimelineUiState { /// The states relevant to the UI display of this timeline that are saved upon /// a `Hide` action and restored upon a `Show` action. saved_state: SavedState, + + /// The state of the message highlight animation. + /// + /// We need to run the animation once the scrolling, triggered by the click of of a + /// a reply preview, ends. so we keep a small state for it. + /// By default, it starts in Off. + /// Once the scrolling is started, the state becomes Pending. + /// If the animation was trigged, the state goes back to Off. message_highlight_animation_state: MessageHighlightAnimationState, + prev_first_index: Option, read_event_hashmap: HashMap, marked_fully_read_queue: HashMap, diff --git a/src/sliding_sync.rs b/src/sliding_sync.rs index a936714b..36f34969 100644 --- a/src/sliding_sync.rs +++ b/src/sliding_sync.rs @@ -227,18 +227,23 @@ pub enum MatrixRequest { room_id: OwnedRoomId, typing: bool, }, + /// Subscribe to typing notices for the given room. + /// + /// This request does not return a response or notify the UI thread. SubscribeToTypingNotices { room_id: OwnedRoomId, /// Whether to subscribe or unsubscribe from typing notices for this room. subscribe: bool, }, + /// Sends a read receipt for the given event in the given room. ReadReceipt{ - room_id:OwnedRoomId, - event_id:OwnedEventId, + room_id: OwnedRoomId, + event_id: OwnedEventId, }, + /// Sends a fully-read receipt for the given event in the given room. FullyReadReceipt{ - room_id:OwnedRoomId, - event_id:OwnedEventId, + room_id: OwnedRoomId, + event_id: OwnedEventId, } } @@ -612,37 +617,38 @@ async fn async_worker(mut receiver: UnboundedReceiver) -> Result< SignalToUI::set_ui_signal(); }); } - MatrixRequest::ReadReceipt { room_id,event_id }=>{ + + MatrixRequest::ReadReceipt { room_id, event_id }=>{ let timeline = { let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); - let Some(room_info) = all_room_info.get_mut(&room_id) else { - log!("BUG: room info not found for send read receipt request {room_id}"); + let Some(room_info) = all_room_info.get(&room_id) else { + log!("BUG: room info not found when sending read receipt, room {room_id}, {event_id}"); continue; }; room_info.timeline.clone() }; - let _send_message_task = Handle::current().spawn(async move { + let _send_rr_task = Handle::current().spawn(async move { match timeline.send_single_receipt(ReceiptType::Read, ReceiptThread::Unthreaded, event_id.clone()).await { - Ok(_send_handle) => log!("Sent read receipt request to room {room_id} {event_id}"), - Err(_e) => error!("Failed to send read receipt request to room {room_id}: {_e:?}"), + Ok(()) => log!("Sent read receipt to room {room_id}, {event_id}"), + Err(_e) => error!("Failed to send read receipt to room {room_id}, {event_id}; error: {_e:?}"), } }); }, - MatrixRequest::FullyReadReceipt { room_id,event_id }=>{ + + MatrixRequest::FullyReadReceipt { room_id, event_id }=>{ let timeline = { let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); - let Some(room_info) = all_room_info.get_mut(&room_id) else { - log!("BUG: room info not found for send read receipt request {room_id}"); + let Some(room_info) = all_room_info.get(&room_id) else { + log!("BUG: room info not found when sending fully read receipt, room {room_id}, {event_id}"); continue; }; - room_info.timeline.clone() }; - let _send_message_task = Handle::current().spawn(async move { + let _send_frr_task = Handle::current().spawn(async move { let receipt = Receipts::new().fully_read_marker(event_id.clone()); match timeline.send_multiple_receipts(receipt).await { - Ok(_send_handle) => log!("Sent fully read receipt / fully_read_marker to room {room_id} {event_id}"), - Err(_e) => error!("Failed to fully read receipt to room {room_id}: {_e:?}"), + Ok(()) => log!("Sent fully read receipt to room {room_id}, {event_id}"), + Err(_e) => error!("Failed to send fully read receipt to room {room_id}, {event_id}; error: {_e:?}"), } }); } From 65c41a7c2fef6c828b0871c8d9e93a7c96d1c3c2 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Mon, 30 Sep 2024 21:49:01 -0700 Subject: [PATCH 6/6] more minor cleanup --- src/home/room_screen.rs | 62 +++++++++++++++++++++-------------------- src/sliding_sync.rs | 12 ++++---- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 27bd7200..4fb95c0c 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -930,20 +930,20 @@ struct RoomScreen { } impl RoomScreen{ - fn send_user_read_receipts_based_on_scroll_pos(&mut self, cx:&mut Cx, actions:&Vec>){ + fn send_user_read_receipts_based_on_scroll_pos( + &mut self, + cx: &mut Cx, + actions: &ActionsBuf, + ) { + let portal_list = self.portal_list(id!(list)); //stopped scrolling - if self.portal_list(id!(list)).scrolled(actions) { + if portal_list.scrolled(actions) { return; } - let portal_list = self.portal_list(id!(list)); 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; - }; + 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 { @@ -953,12 +953,15 @@ impl RoomScreen{ 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 { + for r in first_index .. (first_index + portal_list.visible_items() + 1) { if let Some(v) = tl_state.items.get(r) { - if let Some(Some(e)) = v.as_event().and_then(|f| Some(f.event_id())) { + if let Some(e) = v.as_event().and_then(|f| f.event_id()) { read_receipt_event = Some(e.to_owned()); if !tl_state.read_event_hashmap.contains_key(&e.to_string()) { - tl_state.read_event_hashmap.insert(e.to_string(), (room_id.clone(),e.to_owned(),time_now,false)); + tl_state.read_event_hashmap.insert( + e.to_string(), + (room_id.clone(), e.to_owned(), time_now, false), + ); } } } @@ -970,10 +973,10 @@ impl RoomScreen{ // 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).clone() { - if let Some(Some(e)) = v.as_event().and_then(|f| Some(f.event_id())) { + 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.to_owned() == event_id{ + 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()); } @@ -990,14 +993,12 @@ impl RoomScreen{ } *index = first_index; } - }else{ + } else { tl_state.prev_first_index = Some(first_index); } - - - } } + impl Widget for RoomScreen { // Handle events and actions for the RoomScreen widget and its inner Timeline view. fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { @@ -1145,7 +1146,7 @@ impl Widget for RoomScreen { } if let Event::Actions(actions) = event { - self.send_user_read_receipts_based_on_scroll_pos(cx,actions); + self.send_user_read_receipts_based_on_scroll_pos(cx, actions); for action in actions { // Handle actions on a message, e.g., clicking the reply button or clicking the reply preview. match action.as_widget_action().cast() { @@ -1400,19 +1401,20 @@ impl Widget for RoomScreen { }); } } - // mark events as fully read after displayed for 5 seconds + + // 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())); + 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); } + // Only forward visibility-related events (touch/tap/scroll) to the inner timeline view // if the user profile sliding pane is not visible. if event.requires_visibility() && pane.is_currently_shown(cx) { @@ -1649,8 +1651,8 @@ impl RoomScreen { saved_state: SavedState::default(), message_highlight_animation_state: MessageHighlightAnimationState::default(), prev_first_index: None, - read_event_hashmap:HashMap::new(), - marked_fully_read_queue:HashMap::new(), + read_event_hashmap: HashMap::new(), + marked_fully_read_queue: HashMap::new(), }; (new_tl_state, true) }; @@ -1905,8 +1907,8 @@ struct TimelineUiState { message_highlight_animation_state: MessageHighlightAnimationState, prev_first_index: Option, - read_event_hashmap: HashMap, - marked_fully_read_queue: HashMap, + read_event_hashmap: HashMap, + marked_fully_read_queue: HashMap, } /// The item index, scroll position, and optional unique IDs of the first `N` events diff --git a/src/sliding_sync.rs b/src/sliding_sync.rs index 36f34969..99f2483f 100644 --- a/src/sliding_sync.rs +++ b/src/sliding_sync.rs @@ -620,7 +620,7 @@ async fn async_worker(mut receiver: UnboundedReceiver) -> Result< MatrixRequest::ReadReceipt { room_id, event_id }=>{ let timeline = { - let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); + let all_room_info = ALL_ROOM_INFO.lock().unwrap(); let Some(room_info) = all_room_info.get(&room_id) else { log!("BUG: room info not found when sending read receipt, room {room_id}, {event_id}"); continue; @@ -629,15 +629,15 @@ async fn async_worker(mut receiver: UnboundedReceiver) -> Result< }; let _send_rr_task = Handle::current().spawn(async move { match timeline.send_single_receipt(ReceiptType::Read, ReceiptThread::Unthreaded, event_id.clone()).await { - Ok(()) => log!("Sent read receipt to room {room_id}, {event_id}"), - Err(_e) => error!("Failed to send read receipt to room {room_id}, {event_id}; error: {_e:?}"), + Ok(sent) => log!("{} read receipt to room {room_id} for event {event_id}", if sent { "Sent" } else { "Already sent" }), + Err(_e) => error!("Failed to send read receipt to room {room_id} for event {event_id}; error: {_e:?}"), } }); }, MatrixRequest::FullyReadReceipt { room_id, event_id }=>{ let timeline = { - let mut all_room_info = ALL_ROOM_INFO.lock().unwrap(); + let all_room_info = ALL_ROOM_INFO.lock().unwrap(); let Some(room_info) = all_room_info.get(&room_id) else { log!("BUG: room info not found when sending fully read receipt, room {room_id}, {event_id}"); continue; @@ -647,8 +647,8 @@ async fn async_worker(mut receiver: UnboundedReceiver) -> Result< let _send_frr_task = Handle::current().spawn(async move { let receipt = Receipts::new().fully_read_marker(event_id.clone()); match timeline.send_multiple_receipts(receipt).await { - Ok(()) => log!("Sent fully read receipt to room {room_id}, {event_id}"), - Err(_e) => error!("Failed to send fully read receipt to room {room_id}, {event_id}; error: {_e:?}"), + Ok(()) => log!("Sent fully read receipt to room {room_id}, event {event_id}"), + Err(_e) => error!("Failed to send fully read receipt to room {room_id}, event {event_id}; error: {_e:?}"), } }); }