Skip to content

Commit bef4f9b

Browse files
committed
Added get_timeline_loaded_notify
1 parent 499c370 commit bef4f9b

File tree

6 files changed

+171
-110
lines changed

6 files changed

+171
-110
lines changed

src/app.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ use crate::{
1818
},
1919
login::login_screen::LoginAction,
2020
persistence,
21-
shared::{callout_tooltip::{
21+
shared::callout_tooltip::{
2222
CalloutTooltipOptions,
2323
CalloutTooltipWidgetRefExt,
2424
TooltipAction,
25-
}, message_search_input_bar::MessageSearchAction},
25+
},
2626
sliding_sync::current_user_id,
2727
utils::{
2828
room_name_or_id,
@@ -245,6 +245,7 @@ impl MatchEvent for App {
245245
// A room has been selected, update the app state and navigate to the main content view.
246246
let display_name = room_name_or_id(selected_room.room_name(), selected_room.room_id());
247247
self.app_state.selected_room = Some(selected_room);
248+
248249
// Set the Stack Navigation header to show the name of the newly-selected room.
249250
self.ui
250251
.label(id!(main_content_view.header.content.title_container.title))
@@ -373,13 +374,7 @@ impl MatchEvent for App {
373374
// }
374375
// _ => {}
375376
// }
376-
if let MessageSearchAction::Clicked = action.as_widget_action().cast() {
377-
cx.widget_action(
378-
self.ui.widget_uid(),
379-
&Scope::default().path,
380-
StackNavigationAction::Push(live_id!(search_result_view))
381-
);
382-
}
377+
383378
}
384379
}
385380
}
@@ -424,7 +419,7 @@ impl AppMain for App {
424419

425420
// Forward events to the MatchEvent trait implementation.
426421
self.match_event(cx, event);
427-
let scope = &mut Scope::with_data(&mut self.app_state);
422+
let scope: &mut Scope<'_, '_> = &mut Scope::with_data(&mut self.app_state);
428423
self.ui.handle_event(cx, event, scope);
429424

430425
/*

src/home/home_screen.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use makepad_widgets::*;
22

3-
use crate::{settings::{settings_screen::SettingsScreenWidgetRefExt, SettingsAction}, shared::message_search_input_bar::{MessageSearchInputBarRef, MessageSearchInputBarWidgetExt}};
3+
use crate::{settings::{settings_screen::SettingsScreenWidgetRefExt, SettingsAction}, shared::message_search_input_bar::{MessageSearchAction, MessageSearchInputBarRef, MessageSearchInputBarWidgetExt}};
44

55
live_design! {
66
use link::theme::*;
@@ -271,6 +271,18 @@ impl Widget for HomeScreen {
271271
},
272272
MessageSearchInputAction::Hide => self.view.view(id!(message_search_input_view)).set_visible(cx, false),
273273
}
274+
275+
if let MessageSearchAction::Clicked = action.as_widget_action().cast() {
276+
if !self.view
277+
.stack_navigation(id!(view_stack))
278+
.stack_view_ids().contains(&live_id!(search_result_view)) {
279+
cx.widget_action(
280+
self.widget_uid(),
281+
&Scope::default().path,
282+
StackNavigationAction::Push(live_id!(search_result_view))
283+
);
284+
}
285+
}
274286
}
275287
}
276288

src/home/room_screen.rs

Lines changed: 101 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! A room screen is the UI view that displays a single Room's timeline of events/messages
22
//! along with a message input bar at the bottom.
33
4-
use std::{borrow::Cow, cell::RefCell, collections::BTreeMap, ops::{DerefMut, Range}, sync::Arc};
4+
use std::{borrow::Cow, cell::RefCell, collections::{BTreeMap, HashMap}, ops::{DerefMut, Range}, sync::{Arc, LazyLock, RwLock}};
55

66
use bytesize::ByteSize;
77
use imbl::Vector;
@@ -26,9 +26,10 @@ use matrix_sdk::{
2626
use matrix_sdk_ui::timeline::{
2727
self, EmbeddedEvent, EncryptedMessage, EventTimelineItem, InReplyToDetails, MemberProfileChange, MsgLikeContent, MsgLikeKind, PollState, RoomMembershipChange, TimelineDetails, TimelineEventItemId, TimelineItem, TimelineItemContent, TimelineItemKind, VirtualTimelineItem
2828
};
29+
use tokio::sync::Notify;
2930

3031
use crate::{
31-
app::{AppState, AppStateAction, SelectedRoom}, avatar_cache, event_preview::{plaintext_body_of_timeline_item, text_preview_of_encrypted_message, 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::{edited_indicator::EditedIndicatorWidgetRefExt, editing_pane::EditingPaneState, loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, rooms_list::{RoomsListRef, RoomsListAction}}, location::init_location_subscriber, media_cache::{MediaCache, MediaCacheEntry}, profile::{
32+
app::{AppStateAction, SelectedRoom}, avatar_cache, event_preview::{plaintext_body_of_timeline_item, text_preview_of_encrypted_message, 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::{edited_indicator::EditedIndicatorWidgetRefExt, editing_pane::EditingPaneState, loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, rooms_list::{RoomsListRef, RoomsListAction}}, location::init_location_subscriber, media_cache::{MediaCache, MediaCacheEntry}, profile::{
3233
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
3334
user_profile_cache,
3435
}, shared::{
@@ -48,6 +49,10 @@ const GEO_URI_SCHEME: &str = "geo:";
4849

4950
const MESSAGE_NOTICE_TEXT_COLOR: Vec3 = Vec3 { x: 0.5, y: 0.5, z: 0.5 };
5051

52+
/// Global HashMap tracking timeline loaded notifications for rooms
53+
/// Maps room IDs to their respective notify instances that signal when the room timeline is loaded
54+
static ROOM_TIMELINE_LOADED_MAP: LazyLock<RwLock<HashMap<OwnedRoomId, Arc<Notify>>>> = LazyLock::new(|| RwLock::new(HashMap::new()));
55+
5156
/// The maximum number of timeline items to search through
5257
/// when looking for a particular event.
5358
///
@@ -63,6 +68,14 @@ const SMOOTH_SCROLL_TIME: NonZeroU32 = NonZeroU32::new(500).unwrap();
6368
/// The max size (width or height) of a blurhash image to decode.
6469
const BLURHASH_IMAGE_MAX_SIZE: u32 = 500;
6570

71+
/// Gets the Arc<Notify> for signaling when a room's timeline is loaded and drawn, creating it if it doesn't exist
72+
pub fn get_timeline_loaded_notify(room_id: &OwnedRoomId) -> Option<Arc<Notify>> {
73+
if let Ok(mut map) = ROOM_TIMELINE_LOADED_MAP.write() {
74+
Some(map.entry(room_id.clone()).or_insert_with(|| Arc::new(Notify::new())).clone())
75+
} else {
76+
None
77+
}
78+
}
6679

6780
live_design! {
6881
use link::theme::*;
@@ -1359,7 +1372,7 @@ impl Widget for RoomScreen {
13591372
// If return DrawStep::done() inside self.view.draw_walk, turtle will misalign and panic.
13601373
return DrawStep::done();
13611374
}
1362-
1375+
13631376
let room_screen_widget_uid = self.widget_uid();
13641377
while let Some(subview) = self.view.draw_walk(cx, scope, walk).step() {
13651378
// Here, we only need to handle drawing the portal list.
@@ -1373,6 +1386,12 @@ impl Widget for RoomScreen {
13731386
};
13741387
let room_id = &tl_state.room_id;
13751388
let tl_items = &tl_state.items;
1389+
// Notify when the room timeline is loaded and being drawn
1390+
if let Ok(map) = ROOM_TIMELINE_LOADED_MAP.read() {
1391+
if let Some(notify) = map.get(room_id) {
1392+
notify.notify_one();
1393+
}
1394+
}
13761395

13771396
// Set the portal list's range based on the number of timeline items.
13781397
let last_item_id = tl_items.len();
@@ -1531,6 +1550,7 @@ impl RoomScreen {
15311550
fn process_timeline_updates(&mut self, cx: &mut Cx, portal_list: &PortalListRef) {
15321551
let top_space = self.view(id!(top_space));
15331552
let jump_to_bottom = self.jump_to_bottom_button(id!(jump_to_bottom));
1553+
let loading_pane = self.loading_pane(id!(loading_pane));
15341554
let curr_first_id = portal_list.first_id();
15351555
let ui = self.widget_uid();
15361556
let Some(tl) = self.tl_state.as_mut() else { return };
@@ -1799,6 +1819,61 @@ impl RoomScreen {
17991819
TimelineUpdate::OwnUserReadReceipt(receipt) => {
18001820
tl.latest_own_user_receipt = Some(receipt);
18011821
}
1822+
TimelineUpdate::ScrollToMessage { event_id } => {
1823+
// Search through the timeline to find the message with the given event_id
1824+
let mut num_items_searched = 0;
1825+
let target_msg_tl_index = tl.items
1826+
.focus()
1827+
.into_iter()
1828+
.position(|item| {
1829+
num_items_searched += 1;
1830+
item.as_event()
1831+
.and_then(|e| e.event_id())
1832+
.is_some_and(|ev_id| ev_id == event_id)
1833+
});
1834+
if let Some(index) = target_msg_tl_index {
1835+
let current_first_index = portal_list.first_id();
1836+
let speed = index.saturating_sub(1).abs_diff(current_first_index) as f64 / (SMOOTH_SCROLL_TIME.get() as f64 * 0.001);
1837+
portal_list.smooth_scroll_to(
1838+
cx,
1839+
index.saturating_sub(1),
1840+
//index.saturating_sub(1).abs_diff(current_first_index) as f64 / (SMOOTH_SCROLL_TIME as f64 * 0.001),
1841+
speed,
1842+
None,
1843+
);
1844+
// start highlight animation.
1845+
tl.message_highlight_animation_state = MessageHighlightAnimationState::Pending {
1846+
item_id: index
1847+
};
1848+
} else {
1849+
log!("essage not found - trigger backwards pagination to find it");
1850+
// Message not found - trigger backwards pagination to find it
1851+
loading_pane.set_state(
1852+
cx,
1853+
LoadingPaneState::BackwardsPaginateUntilEvent {
1854+
target_event_id: event_id.clone(),
1855+
events_paginated: 0,
1856+
request_sender: tl.request_sender.clone(),
1857+
},
1858+
);
1859+
loading_pane.show(cx);
1860+
1861+
tl.request_sender.send_if_modified(|requests| {
1862+
if let Some(existing) = requests.iter_mut().find(|r| r.room_id == tl.room_id) {
1863+
// Re-use existing request
1864+
existing.target_event_id = event_id.clone();
1865+
} else {
1866+
requests.push(BackwardsPaginateUntilEventRequest {
1867+
room_id: tl.room_id.clone(),
1868+
target_event_id: event_id.clone(),
1869+
starting_index: 0, // Search from the beginning since we don't know where it is
1870+
current_tl_len: tl.items.len(),
1871+
});
1872+
}
1873+
true
1874+
});
1875+
}
1876+
}
18021877
}
18031878
}
18041879

@@ -2850,7 +2925,10 @@ pub enum TimelineUpdate {
28502925
UserPowerLevels(UserPowerLevels),
28512926
/// An update to the currently logged-in user's own read receipt for this room.
28522927
OwnUserReadReceipt(Receipt),
2853-
2928+
/// Scroll the timeline to the given event.
2929+
ScrollToMessage {
2930+
event_id: OwnedEventId,
2931+
}
28542932
}
28552933

28562934
thread_local! {
@@ -4353,8 +4431,6 @@ pub struct Message {
43534431
/// The jump option required for searched messages.
43544432
/// Contains the room ID, event ID for the message, and whether it's from an all-rooms search.
43554433
#[rust] jump_option: Option<JumpToMessageRequest>,
4356-
/// Add a small delay to ensure new room tab is opened before jumping to the message.
4357-
#[rust] jump_delay: Timer,
43584434
}
43594435

43604436
impl Widget for Message {
@@ -4368,61 +4444,31 @@ impl Widget for Message {
43684444
{
43694445
self.animator_play(cx, id!(highlight.off));
43704446
}
4371-
if let Event::Timer(te) = event {
4372-
if let (Some(_), Some(jump_request)) = (self.jump_delay.is_timer(te), &self.jump_option) {
4373-
cx.widget_action(
4374-
self.widget_uid(),
4375-
&scope.path,
4376-
MessageAction::ScrollToMessage {
4377-
room_id: jump_request.room_id.clone(),
4378-
event_id: jump_request.event_id.clone(),
4379-
}
4380-
);
4381-
}
4382-
}
43834447
if let Some(jump_request) = &self.jump_option {
43844448
if let Event::Actions(actions) = event {
43854449
if self.view.button(id!(jump_to_this_message.jump_button)).clicked(actions) {
4386-
if let Some(selected_room) = {
4387-
let app_state = scope.data.get::<AppState>().unwrap();
4388-
&app_state.selected_room
4389-
} {
4390-
// If room_id is not the selected room, select the room and open its dock tab
4391-
if selected_room.room_id() != &jump_request.room_id {
4392-
let room_name: Option<String> = {
4393-
let rooms_list_ref = cx.get_global::<RoomsListRef>();
4394-
rooms_list_ref.get_room_name(&jump_request.room_id)
4395-
};
4396-
4397-
let target_selected_room = SelectedRoom::JoinedRoom {
4398-
room_id: jump_request.room_id.clone().into(),
4399-
room_name,
4400-
};
4401-
4402-
// Dispatch action to select the room and open its dock tab
4403-
cx.widget_action(
4404-
self.widget_uid(),
4405-
&scope.path,
4406-
RoomsListAction::Selected(target_selected_room)
4407-
);
4408-
}
4409-
}
4410-
// Add a jump delay to ensure new room tab is opened before jumping to the message.
4411-
self.jump_delay = cx.start_timeout(0.5);
4450+
cx.widget_action(
4451+
self.widget_uid(),
4452+
&Scope::default().path,
4453+
StackNavigationAction::PopToRoot
4454+
);
4455+
let room_name: Option<String> = {
4456+
let rooms_list_ref = cx.get_global::<RoomsListRef>();
4457+
rooms_list_ref.get_room_name(&jump_request.room_id)
4458+
};
4459+
let target_selected_room = SelectedRoom::JoinedRoom {
4460+
room_id: jump_request.room_id.clone().into(),
4461+
room_name,
4462+
};
4463+
cx.widget_action(
4464+
self.widget_uid(),
4465+
&scope.path,
4466+
RoomsListAction::Selected(target_selected_room)
4467+
);
4468+
submit_async_request(MatrixRequest::WaitForRoomTimelineLoadedToJump { room_id: jump_request.room_id.clone(), event_id: jump_request.event_id.clone() });
44124469
}
44134470
}
44144471
self.view.handle_event(cx, event, scope);
4415-
let message_view_area = self.view.area();
4416-
match event.hits(cx, message_view_area) {
4417-
Hit::FingerDown(fe) => {
4418-
cx.set_key_focus(message_view_area);
4419-
// A left click to scroll to the message in room screen.
4420-
if fe.device.mouse_button().is_some_and(|b| b.is_primary()) {
4421-
self.jump_delay = cx.start_timeout(0.5);
4422-
}
4423-
}
4424-
_ => {}
4425-
}
44264472
return;
44274473
}
44284474
let Some(details) = self.details.clone() else { return };
@@ -4554,9 +4600,6 @@ impl Message {
45544600
self.jump_option = Some(jump_option);
45554601
self.view.view(id!(jump_to_this_message))
45564602
.set_visible(cx, true);
4557-
self.view.apply_over(cx, live! {
4558-
cursor: Hand
4559-
});
45604603
}
45614604
}
45624605

0 commit comments

Comments
 (0)