Present a blank notification upon receipt of a contact request

Nathan Sobo created

Change summary

crates/client/src/user.rs                   | 30 ++++++++++++-------
crates/contacts_panel/src/contacts_panel.rs | 34 +++++++++++++++++++-
crates/contacts_panel/src/notifications.rs  | 36 +++++++++++++++++++++++
crates/workspace/src/workspace.rs           |  8 +---
crates/zed/src/zed.rs                       |  3 +
5 files changed, 90 insertions(+), 21 deletions(-)

Detailed changes

crates/client/src/user.rs 🔗

@@ -54,7 +54,9 @@ pub struct UserStore {
     _maintain_current_user: Task<()>,
 }
 
-pub enum Event {}
+pub enum Event {
+    NotifyIncomingRequest(Arc<User>),
+}
 
 impl Entity for UserStore {
     type Event = Event;
@@ -182,12 +184,14 @@ impl UserStore {
 
                     let mut incoming_requests = Vec::new();
                     for request in message.incoming_requests {
-                        incoming_requests.push(
-                            this.update(&mut cx, |this, cx| {
-                                this.fetch_user(request.requester_id, cx)
-                            })
-                            .await?,
-                        );
+                        incoming_requests.push({
+                            let user = this
+                                .update(&mut cx, |this, cx| {
+                                    this.fetch_user(request.requester_id, cx)
+                                })
+                                .await?;
+                            (user, request.should_notify)
+                        });
                     }
 
                     let mut outgoing_requests = Vec::new();
@@ -224,14 +228,18 @@ impl UserStore {
                         this.incoming_contact_requests
                             .retain(|user| !removed_incoming_requests.contains(&user.id));
                         // Update existing incoming requests and insert new ones
-                        for request in incoming_requests {
+                        for (user, should_notify) in incoming_requests {
+                            if should_notify {
+                                cx.emit(Event::NotifyIncomingRequest(user.clone()));
+                            }
+
                             match this
                                 .incoming_contact_requests
-                                .binary_search_by_key(&&request.github_login, |contact| {
+                                .binary_search_by_key(&&user.github_login, |contact| {
                                     &contact.github_login
                                 }) {
-                                Ok(ix) => this.incoming_contact_requests[ix] = request,
-                                Err(ix) => this.incoming_contact_requests.insert(ix, request),
+                                Ok(ix) => this.incoming_contact_requests[ix] = user,
+                                Err(ix) => this.incoming_contact_requests.insert(ix, user),
                             }
                         }
 

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -1,4 +1,5 @@
 mod contact_finder;
+mod notifications;
 
 use client::{Contact, User, UserStore};
 use editor::{Cancel, Editor};
@@ -9,13 +10,14 @@ use gpui::{
     impl_actions,
     platform::CursorStyle,
     Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext, RenderContext,
-    Subscription, View, ViewContext, ViewHandle,
+    Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
 };
+use notifications::IncomingRequestNotification;
 use serde::Deserialize;
 use settings::Settings;
 use std::sync::Arc;
 use theme::IconButton;
-use workspace::{AppState, JoinProject};
+use workspace::{AppState, JoinProject, Workspace};
 
 impl_actions!(
     contacts_panel,
@@ -60,7 +62,11 @@ pub fn init(cx: &mut MutableAppContext) {
 }
 
 impl ContactsPanel {
-    pub fn new(app_state: Arc<AppState>, cx: &mut ViewContext<Self>) -> Self {
+    pub fn new(
+        app_state: Arc<AppState>,
+        workspace: WeakViewHandle<Workspace>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
         let user_query_editor = cx.add_view(|cx| {
             let mut editor = Editor::single_line(
                 Some(|theme| theme.contacts_panel.user_query_editor.clone()),
@@ -77,6 +83,28 @@ impl ContactsPanel {
         })
         .detach();
 
+        cx.subscribe(&app_state.user_store, {
+            let user_store = app_state.user_store.clone();
+            move |_, _, event, cx| match event {
+                client::Event::NotifyIncomingRequest(user) => {
+                    if let Some(workspace) = workspace.upgrade(cx) {
+                        workspace.update(cx, |workspace, cx| {
+                            workspace.show_notification(
+                                cx.add_view(|_| {
+                                    IncomingRequestNotification::new(
+                                        user.clone(),
+                                        user_store.clone(),
+                                    )
+                                }),
+                                cx,
+                            )
+                        })
+                    }
+                }
+            }
+        })
+        .detach();
+
         let mut this = Self {
             list_state: ListState::new(0, Orientation::Top, 1000., {
                 let this = cx.weak_handle();

crates/contacts_panel/src/notifications.rs 🔗

@@ -0,0 +1,36 @@
+use client::{User, UserStore};
+use gpui::{color::Color, elements::*, Entity, ModelHandle, View};
+use std::sync::Arc;
+use workspace::Notification;
+
+pub struct IncomingRequestNotification {
+    user: Arc<User>,
+    user_store: ModelHandle<UserStore>,
+}
+
+impl Entity for IncomingRequestNotification {
+    type Event = ();
+}
+
+impl View for IncomingRequestNotification {
+    fn ui_name() -> &'static str {
+        "IncomingRequestNotification"
+    }
+
+    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+        Empty::new()
+            .constrained()
+            .with_height(200.)
+            .contained()
+            .with_background_color(Color::red())
+            .boxed()
+    }
+}
+
+impl Notification for IncomingRequestNotification {}
+
+impl IncomingRequestNotification {
+    pub fn new(user: Arc<User>, user_store: ModelHandle<UserStore>) -> Self {
+        Self { user, user_store }
+    }
+}

crates/workspace/src/workspace.rs 🔗

@@ -1732,11 +1732,7 @@ impl Workspace {
         }
     }
 
-    fn render_notifications(
-        &self,
-        theme: &theme::Workspace,
-        cx: &mut RenderContext<Self>,
-    ) -> Option<ElementBox> {
+    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
         if self.notifications.is_empty() {
             None
         } else {
@@ -2094,7 +2090,7 @@ impl View for Workspace {
                                     .top()
                                     .boxed()
                             }))
-                            .with_children(self.render_notifications(&theme.workspace, cx))
+                            .with_children(self.render_notifications(&theme.workspace))
                             .flex(1.0, true)
                             .boxed(),
                     )

crates/zed/src/zed.rs 🔗

@@ -172,7 +172,8 @@ pub fn build_workspace(
     });
 
     let project_panel = ProjectPanel::new(project, cx);
-    let contact_panel = cx.add_view(|cx| ContactsPanel::new(app_state.clone(), cx));
+    let contact_panel =
+        cx.add_view(|cx| ContactsPanel::new(app_state.clone(), workspace.weak_handle(), cx));
 
     workspace.left_sidebar().update(cx, |sidebar, cx| {
         sidebar.add_item("icons/folder-tree-solid-14.svg", project_panel.into(), cx)