From 59adcc17442b22d203dba695760a7c365d831f78 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sun, 22 Oct 2023 17:10:21 +0200 Subject: [PATCH] Load more notifications when scrolling down --- crates/collab/src/rpc.rs | 5 +- crates/collab_ui/src/chat_panel.rs | 2 +- crates/collab_ui/src/notification_panel.rs | 27 ++++--- crates/gpui/src/elements/list.rs | 11 ++- .../notifications/src/notification_store.rs | 81 +++++++++++++++---- crates/rpc/proto/zed.proto | 1 + 6 files changed, 97 insertions(+), 30 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index a6af089a0a..5a29861351 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3184,7 +3184,10 @@ async fn get_notifications( .map(|id| db::NotificationId::from_proto(id)), ) .await?; - response.send(proto::GetNotificationsResponse { notifications })?; + response.send(proto::GetNotificationsResponse { + done: notifications.len() < NOTIFICATION_COUNT_PER_PAGE, + notifications, + })?; Ok(()) } diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 051b44f9d3..30134eb5cd 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -134,7 +134,7 @@ impl ChatPanel { ListState::::new(0, Orientation::Bottom, 1000., move |this, ix, cx| { this.render_message(ix, cx) }); - message_list.set_scroll_handler(|visible_range, this, cx| { + message_list.set_scroll_handler(|visible_range, _, this, cx| { if visible_range.start < MESSAGE_LOADING_THRESHOLD { this.load_more_messages(&LoadMoreMessages, cx); } diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 8efdcf03e1..db0965dbc4 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -1,7 +1,4 @@ -use crate::{ - chat_panel::ChatPanel, format_timestamp, is_channels_feature_enabled, render_avatar, - NotificationPanelSettings, -}; +use crate::{chat_panel::ChatPanel, format_timestamp, render_avatar, NotificationPanelSettings}; use anyhow::Result; use channel::ChannelStore; use client::{Client, Notification, User, UserStore}; @@ -29,6 +26,7 @@ use workspace::{ Workspace, }; +const LOADING_THRESHOLD: usize = 30; const MARK_AS_READ_DELAY: Duration = Duration::from_secs(1); const TOAST_DURATION: Duration = Duration::from_secs(5); const NOTIFICATION_PANEL_KEY: &'static str = "NotificationPanel"; @@ -98,11 +96,21 @@ impl NotificationPanel { }) .detach(); - let notification_list = + let mut notification_list = ListState::::new(0, Orientation::Top, 1000., move |this, ix, cx| { this.render_notification(ix, cx) .unwrap_or_else(|| Empty::new().into_any()) }); + notification_list.set_scroll_handler(|visible_range, count, this, cx| { + if count.saturating_sub(visible_range.end) < LOADING_THRESHOLD { + if let Some(task) = this + .notification_store + .update(cx, |store, cx| store.load_more_notifications(false, cx)) + { + task.detach(); + } + } + }); let mut this = Self { fs, @@ -653,15 +661,14 @@ impl Panel for NotificationPanel { fn set_active(&mut self, active: bool, cx: &mut ViewContext) { self.active = active; - if active { - if !is_channels_feature_enabled(cx) { - cx.emit(Event::Dismissed); - } + if self.notification_store.read(cx).notification_count() == 0 { + cx.emit(Event::Dismissed); } } fn icon_path(&self, cx: &gpui::WindowContext) -> Option<&'static str> { - (settings::get::(cx).button && is_channels_feature_enabled(cx)) + (settings::get::(cx).button + && self.notification_store.read(cx).notification_count() > 0) .then(|| "icons/bell.svg") } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index eaa09a0392..58409aca42 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -30,7 +30,7 @@ struct StateInner { orientation: Orientation, overdraw: f32, #[allow(clippy::type_complexity)] - scroll_handler: Option, &mut V, &mut ViewContext)>>, + scroll_handler: Option, usize, &mut V, &mut ViewContext)>>, } #[derive(Clone, Copy, Debug, Default, PartialEq)] @@ -420,7 +420,7 @@ impl ListState { pub fn set_scroll_handler( &mut self, - handler: impl FnMut(Range, &mut V, &mut ViewContext) + 'static, + handler: impl FnMut(Range, usize, &mut V, &mut ViewContext) + 'static, ) { self.0.borrow_mut().scroll_handler = Some(Box::new(handler)) } @@ -533,7 +533,12 @@ impl StateInner { if self.scroll_handler.is_some() { let visible_range = self.visible_range(height, scroll_top); - self.scroll_handler.as_mut().unwrap()(visible_range, view, cx); + self.scroll_handler.as_mut().unwrap()( + visible_range, + self.items.summary().count, + view, + cx, + ); } cx.notify(); diff --git a/crates/notifications/src/notification_store.rs b/crates/notifications/src/notification_store.rs index 388f2e16d3..06fcebda3c 100644 --- a/crates/notifications/src/notification_store.rs +++ b/crates/notifications/src/notification_store.rs @@ -21,6 +21,7 @@ pub struct NotificationStore { channel_messages: HashMap, channel_store: ModelHandle, notifications: SumTree, + loaded_all_notifications: bool, _watch_connection_status: Task>, _subscriptions: Vec, } @@ -83,9 +84,10 @@ impl NotificationStore { let this = this.upgrade(&cx)?; match status { client::Status::Connected { .. } => { - this.update(&mut cx, |this, cx| this.handle_connect(cx)) - .await - .log_err()?; + if let Some(task) = this.update(&mut cx, |this, cx| this.handle_connect(cx)) + { + task.await.log_err()?; + } } _ => this.update(&mut cx, |this, cx| this.handle_disconnect(cx)), } @@ -96,6 +98,7 @@ impl NotificationStore { Self { channel_store: ChannelStore::global(cx), notifications: Default::default(), + loaded_all_notifications: false, channel_messages: Default::default(), _watch_connection_status: watch_connection_status, _subscriptions: vec![ @@ -142,22 +145,46 @@ impl NotificationStore { None } - pub fn load_more_notifications(&self, cx: &mut ModelContext) -> Task> { - let request = self - .client - .request(proto::GetNotifications { before_id: None }); - cx.spawn(|this, cx| async move { + pub fn load_more_notifications( + &self, + clear_old: bool, + cx: &mut ModelContext, + ) -> Option>> { + if self.loaded_all_notifications && !clear_old { + return None; + } + + let before_id = if clear_old { + None + } else { + self.notifications.first().map(|entry| entry.id) + }; + let request = self.client.request(proto::GetNotifications { before_id }); + Some(cx.spawn(|this, mut cx| async move { let response = request.await?; - Self::add_notifications(this, false, response.notifications, cx).await?; + this.update(&mut cx, |this, _| { + this.loaded_all_notifications = response.done + }); + Self::add_notifications( + this, + response.notifications, + AddNotificationsOptions { + is_new: false, + clear_old, + includes_first: response.done, + }, + cx, + ) + .await?; Ok(()) - }) + })) } - fn handle_connect(&mut self, cx: &mut ModelContext) -> Task> { + fn handle_connect(&mut self, cx: &mut ModelContext) -> Option>> { self.notifications = Default::default(); self.channel_messages = Default::default(); cx.notify(); - self.load_more_notifications(cx) + self.load_more_notifications(true, cx) } fn handle_disconnect(&mut self, cx: &mut ModelContext) { @@ -172,8 +199,12 @@ impl NotificationStore { ) -> Result<()> { Self::add_notifications( this, - true, envelope.payload.notification.into_iter().collect(), + AddNotificationsOptions { + is_new: true, + clear_old: false, + includes_first: false, + }, cx, ) .await @@ -193,8 +224,8 @@ impl NotificationStore { async fn add_notifications( this: ModelHandle, - is_new: bool, notifications: Vec, + options: AddNotificationsOptions, mut cx: AsyncAppContext, ) -> Result<()> { let mut user_ids = Vec::new(); @@ -256,6 +287,20 @@ impl NotificationStore { }) .await?; this.update(&mut cx, |this, cx| { + if options.clear_old { + cx.emit(NotificationEvent::NotificationsUpdated { + old_range: 0..this.notifications.summary().count, + new_count: 0, + }); + this.notifications = SumTree::default(); + this.channel_messages.clear(); + this.loaded_all_notifications = false; + } + + if options.includes_first { + this.loaded_all_notifications = true; + } + this.channel_messages .extend(messages.into_iter().filter_map(|message| { if let ChannelMessageId::Saved(id) = message.id { @@ -269,7 +314,7 @@ impl NotificationStore { notifications .into_iter() .map(|notification| (notification.id, Some(notification))), - is_new, + options.is_new, cx, ); }); @@ -406,3 +451,9 @@ impl<'a> sum_tree::Dimension<'a, NotificationSummary> for UnreadCount { self.0 += summary.unread_count; } } + +struct AddNotificationsOptions { + is_new: bool, + clear_old: bool, + includes_first: bool, +} diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index efdbf0e56c..f69d028483 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1616,6 +1616,7 @@ message AddNotification { message GetNotificationsResponse { repeated Notification notifications = 1; + bool done = 2; } message DeleteNotification {