Detailed changes
@@ -213,6 +213,12 @@ impl ChannelStore {
self.channel_index.by_id().values().nth(ix)
}
+ pub fn has_channel_invitation(&self, channel_id: ChannelId) -> bool {
+ self.channel_invitations
+ .iter()
+ .any(|channel| channel.id == channel_id)
+ }
+
pub fn channel_invitations(&self) -> &[Arc<Channel>] {
&self.channel_invitations
}
@@ -187,6 +187,7 @@ impl Database {
rpc::Notification::ChannelInvitation {
channel_id: channel_id.to_proto(),
channel_name: channel.name,
+ inviter_id: inviter_id.to_proto(),
},
true,
&*tx,
@@ -276,6 +277,7 @@ impl Database {
&rpc::Notification::ChannelInvitation {
channel_id: channel_id.to_proto(),
channel_name: Default::default(),
+ inviter_id: Default::default(),
},
accept,
&*tx,
@@ -292,7 +294,7 @@ impl Database {
channel_id: ChannelId,
member_id: UserId,
remover_id: UserId,
- ) -> Result<()> {
+ ) -> Result<Option<NotificationId>> {
self.transaction(|tx| async move {
self.check_user_is_channel_admin(channel_id, remover_id, &*tx)
.await?;
@@ -310,7 +312,17 @@ impl Database {
Err(anyhow!("no such member"))?;
}
- Ok(())
+ Ok(self
+ .remove_notification(
+ member_id,
+ rpc::Notification::ChannelInvitation {
+ channel_id: channel_id.to_proto(),
+ channel_name: Default::default(),
+ inviter_id: Default::default(),
+ },
+ &*tx,
+ )
+ .await?)
})
.await
}
@@ -2331,7 +2331,8 @@ async fn remove_channel_member(
let channel_id = ChannelId::from_proto(request.channel_id);
let member_id = UserId::from_proto(request.user_id);
- db.remove_channel_member(channel_id, member_id, session.user_id)
+ let removed_notification_id = db
+ .remove_channel_member(channel_id, member_id, session.user_id)
.await?;
let mut update = proto::UpdateChannels::default();
@@ -2342,7 +2343,18 @@ async fn remove_channel_member(
.await
.user_connection_ids(member_id)
{
- session.peer.send(connection_id, update.clone())?;
+ session.peer.send(connection_id, update.clone()).trace_err();
+ if let Some(notification_id) = removed_notification_id {
+ session
+ .peer
+ .send(
+ connection_id,
+ proto::DeleteNotification {
+ notification_id: notification_id.to_proto(),
+ },
+ )
+ .trace_err();
+ }
}
response.send(proto::Ack {})?;
@@ -1,5 +1,7 @@
use crate::tests::TestServer;
use gpui::{executor::Deterministic, TestAppContext};
+use notifications::NotificationEvent;
+use parking_lot::Mutex;
use rpc::Notification;
use std::sync::Arc;
@@ -14,6 +16,23 @@ async fn test_notifications(
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
+ let notification_events_a = Arc::new(Mutex::new(Vec::new()));
+ let notification_events_b = Arc::new(Mutex::new(Vec::new()));
+ client_a.notification_store().update(cx_a, |_, cx| {
+ let events = notification_events_a.clone();
+ cx.subscribe(&cx.handle(), move |_, _, event, _| {
+ events.lock().push(event.clone());
+ })
+ .detach()
+ });
+ client_b.notification_store().update(cx_b, |_, cx| {
+ let events = notification_events_b.clone();
+ cx.subscribe(&cx.handle(), move |_, _, event, _| {
+ events.lock().push(event.clone());
+ })
+ .detach()
+ });
+
// Client A sends a contact request to client B.
client_a
.user_store()
@@ -36,6 +55,18 @@ async fn test_notifications(
}
);
assert!(!entry.is_read);
+ assert_eq!(
+ ¬ification_events_b.lock()[0..],
+ &[
+ NotificationEvent::NewNotification {
+ entry: entry.clone(),
+ },
+ NotificationEvent::NotificationsUpdated {
+ old_range: 0..0,
+ new_count: 1
+ }
+ ]
+ );
store.respond_to_notification(entry.notification.clone(), true, cx);
});
@@ -49,6 +80,18 @@ async fn test_notifications(
let entry = store.notification_at(0).unwrap();
assert!(entry.is_read);
assert_eq!(entry.response, Some(true));
+ assert_eq!(
+ ¬ification_events_b.lock()[2..],
+ &[
+ NotificationEvent::NotificationRead {
+ entry: entry.clone(),
+ },
+ NotificationEvent::NotificationsUpdated {
+ old_range: 0..1,
+ new_count: 1
+ }
+ ]
+ );
});
// Client A receives a notification that client B accepted their request.
@@ -89,12 +132,13 @@ async fn test_notifications(
assert_eq!(store.notification_count(), 2);
assert_eq!(store.unread_notification_count(), 1);
- let entry = store.notification_at(1).unwrap();
+ let entry = store.notification_at(0).unwrap();
assert_eq!(
entry.notification,
Notification::ChannelInvitation {
channel_id,
- channel_name: "the-channel".to_string()
+ channel_name: "the-channel".to_string(),
+ inviter_id: client_a.id()
}
);
assert!(!entry.is_read);
@@ -108,7 +152,7 @@ async fn test_notifications(
assert_eq!(store.notification_count(), 2);
assert_eq!(store.unread_notification_count(), 0);
- let entry = store.notification_at(1).unwrap();
+ let entry = store.notification_at(0).unwrap();
assert!(entry.is_read);
assert_eq!(entry.response, Some(true));
});
@@ -1,11 +1,9 @@
use crate::{
- format_timestamp, is_channels_feature_enabled,
- notifications::contact_notification::ContactNotification, render_avatar,
- NotificationPanelSettings,
+ format_timestamp, is_channels_feature_enabled, render_avatar, NotificationPanelSettings,
};
use anyhow::Result;
use channel::ChannelStore;
-use client::{Client, Notification, UserStore};
+use client::{Client, Notification, User, UserStore};
use db::kvp::KEY_VALUE_STORE;
use futures::StreamExt;
use gpui::{
@@ -19,7 +17,7 @@ use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
use project::Fs;
use serde::{Deserialize, Serialize};
use settings::SettingsStore;
-use std::sync::Arc;
+use std::{sync::Arc, time::Duration};
use theme::{IconButton, Theme};
use time::{OffsetDateTime, UtcOffset};
use util::{ResultExt, TryFutureExt};
@@ -28,6 +26,7 @@ use workspace::{
Workspace,
};
+const TOAST_DURATION: Duration = Duration::from_secs(5);
const NOTIFICATION_PANEL_KEY: &'static str = "NotificationPanel";
pub struct NotificationPanel {
@@ -42,6 +41,7 @@ pub struct NotificationPanel {
pending_serialization: Task<Option<()>>,
subscriptions: Vec<gpui::Subscription>,
workspace: WeakViewHandle<Workspace>,
+ current_notification_toast: Option<(u64, Task<()>)>,
local_timezone: UtcOffset,
has_focus: bool,
}
@@ -58,7 +58,7 @@ pub enum Event {
Dismissed,
}
-actions!(chat_panel, [ToggleFocus]);
+actions!(notification_panel, [ToggleFocus]);
pub fn init(_cx: &mut AppContext) {}
@@ -69,14 +69,8 @@ impl NotificationPanel {
let user_store = workspace.app_state().user_store.clone();
let workspace_handle = workspace.weak_handle();
- let notification_list =
- ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
- this.render_notification(ix, cx)
- });
-
cx.add_view(|cx| {
let mut status = client.status();
-
cx.spawn(|this, mut cx| async move {
while let Some(_) = status.next().await {
if this
@@ -91,6 +85,12 @@ impl NotificationPanel {
})
.detach();
+ let notification_list =
+ ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
+ this.render_notification(ix, cx)
+ .unwrap_or_else(|| Empty::new().into_any())
+ });
+
let mut this = Self {
fs,
client,
@@ -102,6 +102,7 @@ impl NotificationPanel {
pending_serialization: Task::ready(None),
workspace: workspace_handle,
has_focus: false,
+ current_notification_toast: None,
subscriptions: Vec::new(),
active: false,
width: None,
@@ -169,73 +170,20 @@ impl NotificationPanel {
);
}
- fn render_notification(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- self.try_render_notification(ix, cx)
- .unwrap_or_else(|| Empty::new().into_any())
- }
-
- fn try_render_notification(
+ fn render_notification(
&mut self,
ix: usize,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement<Self>> {
- let notification_store = self.notification_store.read(cx);
- let user_store = self.user_store.read(cx);
- let channel_store = self.channel_store.read(cx);
- let entry = notification_store.notification_at(ix)?;
- let notification = entry.notification.clone();
+ let entry = self.notification_store.read(cx).notification_at(ix)?;
let now = OffsetDateTime::now_utc();
let timestamp = entry.timestamp;
-
- let icon;
- let text;
- let actor;
- let needs_acceptance;
- match notification {
- Notification::ContactRequest { sender_id } => {
- let requester = user_store.get_cached_user(sender_id)?;
- icon = "icons/plus.svg";
- text = format!("{} wants to add you as a contact", requester.github_login);
- needs_acceptance = true;
- actor = Some(requester);
- }
- Notification::ContactRequestAccepted { responder_id } => {
- let responder = user_store.get_cached_user(responder_id)?;
- icon = "icons/plus.svg";
- text = format!("{} accepted your contact invite", responder.github_login);
- needs_acceptance = false;
- actor = Some(responder);
- }
- Notification::ChannelInvitation {
- ref channel_name, ..
- } => {
- actor = None;
- icon = "icons/hash.svg";
- text = format!("you were invited to join the #{channel_name} channel");
- needs_acceptance = true;
- }
- Notification::ChannelMessageMention {
- sender_id,
- channel_id,
- message_id,
- } => {
- let sender = user_store.get_cached_user(sender_id)?;
- let channel = channel_store.channel_for_id(channel_id)?;
- let message = notification_store.channel_message_for_id(message_id)?;
-
- icon = "icons/conversations.svg";
- text = format!(
- "{} mentioned you in the #{} channel:\n{}",
- sender.github_login, channel.name, message.body,
- );
- needs_acceptance = false;
- actor = Some(sender);
- }
- }
+ let (actor, text, icon, needs_response) = self.present_notification(entry, cx)?;
let theme = theme::current(cx);
let style = &theme.notification_panel;
let response = entry.response;
+ let notification = entry.notification.clone();
let message_style = if entry.is_read {
style.read_text.clone()
@@ -276,7 +224,7 @@ impl NotificationPanel {
)
.into_any(),
)
- } else if needs_acceptance {
+ } else if needs_response {
Some(
Flex::row()
.with_children([
@@ -336,6 +284,69 @@ impl NotificationPanel {
)
}
+ fn present_notification(
+ &self,
+ entry: &NotificationEntry,
+ cx: &AppContext,
+ ) -> Option<(Option<Arc<client::User>>, String, &'static str, bool)> {
+ let user_store = self.user_store.read(cx);
+ let channel_store = self.channel_store.read(cx);
+ let icon;
+ let text;
+ let actor;
+ let needs_response;
+ match entry.notification {
+ Notification::ContactRequest { sender_id } => {
+ let requester = user_store.get_cached_user(sender_id)?;
+ icon = "icons/plus.svg";
+ text = format!("{} wants to add you as a contact", requester.github_login);
+ needs_response = user_store.is_contact_request_pending(&requester);
+ actor = Some(requester);
+ }
+ Notification::ContactRequestAccepted { responder_id } => {
+ let responder = user_store.get_cached_user(responder_id)?;
+ icon = "icons/plus.svg";
+ text = format!("{} accepted your contact invite", responder.github_login);
+ needs_response = false;
+ actor = Some(responder);
+ }
+ Notification::ChannelInvitation {
+ ref channel_name,
+ channel_id,
+ inviter_id,
+ } => {
+ let inviter = user_store.get_cached_user(inviter_id)?;
+ icon = "icons/hash.svg";
+ text = format!(
+ "{} invited you to join the #{channel_name} channel",
+ inviter.github_login
+ );
+ needs_response = channel_store.has_channel_invitation(channel_id);
+ actor = Some(inviter);
+ }
+ Notification::ChannelMessageMention {
+ sender_id,
+ channel_id,
+ message_id,
+ } => {
+ let sender = user_store.get_cached_user(sender_id)?;
+ let channel = channel_store.channel_for_id(channel_id)?;
+ let message = self
+ .notification_store
+ .read(cx)
+ .channel_message_for_id(message_id)?;
+ icon = "icons/conversations.svg";
+ text = format!(
+ "{} mentioned you in the #{} channel:\n{}",
+ sender.github_login, channel.name, message.body,
+ );
+ needs_response = false;
+ actor = Some(sender);
+ }
+ }
+ Some((actor, text, icon, needs_response))
+ }
+
fn render_sign_in_prompt(
&self,
theme: &Arc<Theme>,
@@ -387,7 +398,7 @@ impl NotificationPanel {
match event {
NotificationEvent::NewNotification { entry } => self.add_toast(entry, cx),
NotificationEvent::NotificationRemoved { entry }
- | NotificationEvent::NotificationRead { entry } => self.remove_toast(entry, cx),
+ | NotificationEvent::NotificationRead { entry } => self.remove_toast(entry.id, cx),
NotificationEvent::NotificationsUpdated {
old_range,
new_count,
@@ -399,49 +410,44 @@ impl NotificationPanel {
}
fn add_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
- let id = entry.id as usize;
- match entry.notification {
- Notification::ContactRequest {
- sender_id: actor_id,
- }
- | Notification::ContactRequestAccepted {
- responder_id: actor_id,
- } => {
- let user_store = self.user_store.clone();
- let Some(user) = user_store.read(cx).get_cached_user(actor_id) else {
- return;
- };
- self.workspace
- .update(cx, |workspace, cx| {
- workspace.show_notification(id, cx, |cx| {
- cx.add_view(|_| {
- ContactNotification::new(
- user,
- entry.notification.clone(),
- user_store,
- )
- })
- })
- })
+ let Some((actor, text, _, _)) = self.present_notification(entry, cx) else {
+ return;
+ };
+
+ let id = entry.id;
+ self.current_notification_toast = Some((
+ id,
+ cx.spawn(|this, mut cx| async move {
+ cx.background().timer(TOAST_DURATION).await;
+ this.update(&mut cx, |this, cx| this.remove_toast(id, cx))
.ok();
- }
- Notification::ChannelInvitation { .. } => {}
- Notification::ChannelMessageMention { .. } => {}
- }
+ }),
+ ));
+
+ self.workspace
+ .update(cx, |workspace, cx| {
+ workspace.show_notification(0, cx, |cx| {
+ let workspace = cx.weak_handle();
+ cx.add_view(|_| NotificationToast {
+ actor,
+ text,
+ workspace,
+ })
+ })
+ })
+ .ok();
}
- fn remove_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
- let id = entry.id as usize;
- match entry.notification {
- Notification::ContactRequest { .. } | Notification::ContactRequestAccepted { .. } => {
+ fn remove_toast(&mut self, notification_id: u64, cx: &mut ViewContext<Self>) {
+ if let Some((current_id, _)) = &self.current_notification_toast {
+ if *current_id == notification_id {
+ self.current_notification_toast.take();
self.workspace
.update(cx, |workspace, cx| {
- workspace.dismiss_notification::<ContactNotification>(id, cx)
+ workspace.dismiss_notification::<NotificationToast>(0, cx)
})
.ok();
}
- Notification::ChannelInvitation { .. } => {}
- Notification::ChannelMessageMention { .. } => {}
}
}
@@ -582,3 +588,111 @@ fn render_icon_button<V: View>(style: &IconButton, svg_path: &'static str) -> im
.contained()
.with_style(style.container)
}
+
+pub struct NotificationToast {
+ actor: Option<Arc<User>>,
+ text: String,
+ workspace: WeakViewHandle<Workspace>,
+}
+
+pub enum ToastEvent {
+ Dismiss,
+}
+
+impl NotificationToast {
+ fn focus_notification_panel(&self, cx: &mut AppContext) {
+ let workspace = self.workspace.clone();
+ cx.defer(move |cx| {
+ workspace
+ .update(cx, |workspace, cx| {
+ workspace.focus_panel::<NotificationPanel>(cx);
+ })
+ .ok();
+ })
+ }
+}
+
+impl Entity for NotificationToast {
+ type Event = ToastEvent;
+}
+
+impl View for NotificationToast {
+ fn ui_name() -> &'static str {
+ "ContactNotification"
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ let user = self.actor.clone();
+ let theme = theme::current(cx).clone();
+ let theme = &theme.contact_notification;
+
+ MouseEventHandler::new::<Self, _>(0, cx, |_, cx| {
+ Flex::row()
+ .with_children(user.and_then(|user| {
+ Some(
+ Image::from_data(user.avatar.clone()?)
+ .with_style(theme.header_avatar)
+ .aligned()
+ .constrained()
+ .with_height(
+ cx.font_cache()
+ .line_height(theme.header_message.text.font_size),
+ )
+ .aligned()
+ .top(),
+ )
+ }))
+ .with_child(
+ Text::new(self.text.clone(), theme.header_message.text.clone())
+ .contained()
+ .with_style(theme.header_message.container)
+ .aligned()
+ .top()
+ .left()
+ .flex(1., true),
+ )
+ .with_child(
+ MouseEventHandler::new::<ToastEvent, _>(0, cx, |state, _| {
+ let style = theme.dismiss_button.style_for(state);
+ Svg::new("icons/x.svg")
+ .with_color(style.color)
+ .constrained()
+ .with_width(style.icon_width)
+ .aligned()
+ .contained()
+ .with_style(style.container)
+ .constrained()
+ .with_width(style.button_width)
+ .with_height(style.button_width)
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .with_padding(Padding::uniform(5.))
+ .on_click(MouseButton::Left, move |_, _, cx| {
+ cx.emit(ToastEvent::Dismiss)
+ })
+ .aligned()
+ .constrained()
+ .with_height(
+ cx.font_cache()
+ .line_height(theme.header_message.text.font_size),
+ )
+ .aligned()
+ .top()
+ .flex_float(),
+ )
+ .contained()
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, this, cx| {
+ this.focus_notification_panel(cx);
+ cx.emit(ToastEvent::Dismiss);
+ })
+ .into_any()
+ }
+}
+
+impl workspace::notifications::Notification for NotificationToast {
+ fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
+ matches!(event, ToastEvent::Dismiss)
+ }
+}
@@ -1,120 +1,11 @@
-use client::User;
-use gpui::{
- elements::*,
- platform::{CursorStyle, MouseButton},
- AnyElement, AppContext, Element, ViewContext,
-};
+use gpui::AppContext;
use std::sync::Arc;
use workspace::AppState;
-pub mod contact_notification;
pub mod incoming_call_notification;
pub mod project_shared_notification;
-enum Dismiss {}
-enum Button {}
-
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
incoming_call_notification::init(app_state, cx);
project_shared_notification::init(app_state, cx);
}
-
-pub fn render_user_notification<F, V: 'static>(
- user: Arc<User>,
- title: &'static str,
- body: Option<&'static str>,
- on_dismiss: F,
- buttons: Vec<(&'static str, Box<dyn Fn(&mut V, &mut ViewContext<V>)>)>,
- cx: &mut ViewContext<V>,
-) -> AnyElement<V>
-where
- F: 'static + Fn(&mut V, &mut ViewContext<V>),
-{
- let theme = theme::current(cx).clone();
- let theme = &theme.contact_notification;
-
- Flex::column()
- .with_child(
- Flex::row()
- .with_children(user.avatar.clone().map(|avatar| {
- Image::from_data(avatar)
- .with_style(theme.header_avatar)
- .aligned()
- .constrained()
- .with_height(
- cx.font_cache()
- .line_height(theme.header_message.text.font_size),
- )
- .aligned()
- .top()
- }))
- .with_child(
- Text::new(
- format!("{} {}", user.github_login, title),
- theme.header_message.text.clone(),
- )
- .contained()
- .with_style(theme.header_message.container)
- .aligned()
- .top()
- .left()
- .flex(1., true),
- )
- .with_child(
- MouseEventHandler::new::<Dismiss, _>(user.id as usize, cx, |state, _| {
- let style = theme.dismiss_button.style_for(state);
- Svg::new("icons/x.svg")
- .with_color(style.color)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .contained()
- .with_style(style.container)
- .constrained()
- .with_width(style.button_width)
- .with_height(style.button_width)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .with_padding(Padding::uniform(5.))
- .on_click(MouseButton::Left, move |_, view, cx| on_dismiss(view, cx))
- .aligned()
- .constrained()
- .with_height(
- cx.font_cache()
- .line_height(theme.header_message.text.font_size),
- )
- .aligned()
- .top()
- .flex_float(),
- )
- .into_any_named("contact notification header"),
- )
- .with_children(body.map(|body| {
- Label::new(body, theme.body_message.text.clone())
- .contained()
- .with_style(theme.body_message.container)
- }))
- .with_children(if buttons.is_empty() {
- None
- } else {
- Some(
- Flex::row()
- .with_children(buttons.into_iter().enumerate().map(
- |(ix, (message, handler))| {
- MouseEventHandler::new::<Button, _>(ix, cx, |state, _| {
- let button = theme.button.style_for(state);
- Label::new(message, button.text.clone())
- .contained()
- .with_style(button.container)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, view, cx| handler(view, cx))
- },
- ))
- .aligned()
- .right(),
- )
- })
- .contained()
- .into_any()
-}
@@ -1,106 +0,0 @@
-use crate::notifications::render_user_notification;
-use client::{User, UserStore};
-use gpui::{elements::*, Entity, ModelHandle, View, ViewContext};
-use std::sync::Arc;
-use workspace::notifications::Notification;
-
-pub struct ContactNotification {
- user_store: ModelHandle<UserStore>,
- user: Arc<User>,
- notification: rpc::Notification,
-}
-
-#[derive(Clone, PartialEq)]
-struct Dismiss(u64);
-
-#[derive(Clone, PartialEq)]
-pub struct RespondToContactRequest {
- pub user_id: u64,
- pub accept: bool,
-}
-
-pub enum Event {
- Dismiss,
-}
-
-impl Entity for ContactNotification {
- type Event = Event;
-}
-
-impl View for ContactNotification {
- fn ui_name() -> &'static str {
- "ContactNotification"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- match self.notification {
- rpc::Notification::ContactRequest { .. } => render_user_notification(
- self.user.clone(),
- "wants to add you as a contact",
- Some("They won't be alerted if you decline."),
- |notification, cx| notification.dismiss(cx),
- vec![
- (
- "Decline",
- Box::new(|notification, cx| {
- notification.respond_to_contact_request(false, cx)
- }),
- ),
- (
- "Accept",
- Box::new(|notification, cx| {
- notification.respond_to_contact_request(true, cx)
- }),
- ),
- ],
- cx,
- ),
- rpc::Notification::ContactRequestAccepted { .. } => render_user_notification(
- self.user.clone(),
- "accepted your contact request",
- None,
- |notification, cx| notification.dismiss(cx),
- vec![],
- cx,
- ),
- _ => unreachable!(),
- }
- }
-}
-
-impl Notification for ContactNotification {
- fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
- matches!(event, Event::Dismiss)
- }
-}
-
-impl ContactNotification {
- pub fn new(
- user: Arc<User>,
- notification: rpc::Notification,
- user_store: ModelHandle<UserStore>,
- ) -> Self {
- Self {
- user,
- notification,
- user_store,
- }
- }
-
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- self.user_store.update(cx, |store, cx| {
- store
- .dismiss_contact_request(self.user.id, cx)
- .detach_and_log_err(cx);
- });
- cx.emit(Event::Dismiss);
- }
-
- fn respond_to_contact_request(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
- self.user_store
- .update(cx, |store, cx| {
- store.respond_to_contact_request(self.user.id, accept, cx)
- })
- .detach();
- }
-}
@@ -25,6 +25,7 @@ pub struct NotificationStore {
_subscriptions: Vec<client::Subscription>,
}
+#[derive(Clone, PartialEq, Eq, Debug)]
pub enum NotificationEvent {
NotificationsUpdated {
old_range: Range<usize>,
@@ -118,7 +119,13 @@ impl NotificationStore {
self.channel_messages.get(&id)
}
+ // Get the nth newest notification.
pub fn notification_at(&self, ix: usize) -> Option<&NotificationEntry> {
+ let count = self.notifications.summary().count;
+ if ix >= count {
+ return None;
+ }
+ let ix = count - 1 - ix;
let mut cursor = self.notifications.cursor::<Count>();
cursor.seek(&Count(ix), Bias::Right, &());
cursor.item()
@@ -200,7 +207,9 @@ impl NotificationStore {
for entry in ¬ifications {
match entry.notification {
- Notification::ChannelInvitation { .. } => {}
+ Notification::ChannelInvitation { inviter_id, .. } => {
+ user_ids.push(inviter_id);
+ }
Notification::ContactRequest {
sender_id: requester_id,
} => {
@@ -273,8 +282,11 @@ impl NotificationStore {
old_range.start = cursor.start().1 .0;
}
- if let Some(existing_notification) = cursor.item() {
- if existing_notification.id == id {
+ let old_notification = cursor.item();
+ if let Some(old_notification) = old_notification {
+ if old_notification.id == id {
+ cursor.next(&());
+
if let Some(new_notification) = &new_notification {
if new_notification.is_read {
cx.emit(NotificationEvent::NotificationRead {
@@ -283,20 +295,19 @@ impl NotificationStore {
}
} else {
cx.emit(NotificationEvent::NotificationRemoved {
- entry: existing_notification.clone(),
+ entry: old_notification.clone(),
});
}
- cursor.next(&());
}
- }
-
- if let Some(notification) = new_notification {
+ } else if let Some(new_notification) = &new_notification {
if is_new {
cx.emit(NotificationEvent::NewNotification {
- entry: notification.clone(),
+ entry: new_notification.clone(),
});
}
+ }
+ if let Some(notification) = new_notification {
new_notifications.push(notification, &());
}
}
@@ -30,12 +30,13 @@ pub enum Notification {
#[serde(rename = "entity_id")]
channel_id: u64,
channel_name: String,
+ inviter_id: u64,
},
ChannelMessageMention {
- sender_id: u64,
- channel_id: u64,
#[serde(rename = "entity_id")]
message_id: u64,
+ sender_id: u64,
+ channel_id: u64,
},
}
@@ -84,6 +85,7 @@ fn test_notification() {
Notification::ChannelInvitation {
channel_id: 100,
channel_name: "the-channel".into(),
+ inviter_id: 50,
},
Notification::ChannelMessageMention {
sender_id: 200,