Ensure notifications are dismissed

Conrad Irwin created

Before this change if you joined a project without clicking on the
notification it would never disappear.

Fix a related bug where if you have more than one monitor, the
notification was only dismissed from one of them.

Change summary

crates/call/src/room.rs                             |  7 ++
crates/collab/src/tests/following_tests.rs          | 39 +++++++++++++-
crates/collab_ui/src/collab_titlebar_item.rs        |  2 
crates/collab_ui/src/collab_ui.rs                   |  2 
crates/collab_ui/src/project_shared_notification.rs | 21 ++++++-
crates/workspace/src/workspace.rs                   | 11 ----
6 files changed, 61 insertions(+), 21 deletions(-)

Detailed changes

crates/call/src/room.rs 🔗

@@ -44,6 +44,12 @@ pub enum Event {
     RemoteProjectUnshared {
         project_id: u64,
     },
+    RemoteProjectJoined {
+        project_id: u64,
+    },
+    RemoteProjectInvitationDiscarded {
+        project_id: u64,
+    },
     Left,
 }
 
@@ -1015,6 +1021,7 @@ impl Room {
     ) -> Task<Result<ModelHandle<Project>>> {
         let client = self.client.clone();
         let user_store = self.user_store.clone();
+        cx.emit(Event::RemoteProjectJoined { project_id: id });
         cx.spawn(|this, mut cx| async move {
             let project =
                 Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;

crates/collab/src/tests/following_tests.rs 🔗

@@ -1,7 +1,10 @@
 use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
 use call::ActiveCall;
+use collab_ui::project_shared_notification::ProjectSharedNotification;
 use editor::{Editor, ExcerptRange, MultiBuffer};
-use gpui::{executor::Deterministic, geometry::vector::vec2f, TestAppContext, ViewHandle};
+use gpui::{
+    executor::Deterministic, geometry::vector::vec2f, AppContext, TestAppContext, ViewHandle,
+};
 use live_kit_client::MacOSDisplay;
 use serde_json::json;
 use std::sync::Arc;
@@ -1073,6 +1076,24 @@ async fn test_peers_simultaneously_following_each_other(
     });
 }
 
+fn visible_push_notifications(
+    cx: &mut TestAppContext,
+) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {
+    let mut ret = Vec::new();
+    for window in cx.windows() {
+        window.read_with(cx, |window| {
+            if let Some(handle) = window
+                .root_view()
+                .clone()
+                .downcast::<ProjectSharedNotification>()
+            {
+                ret.push(handle)
+            }
+        });
+    }
+    ret
+}
+
 #[gpui::test(iterations = 10)]
 async fn test_following_across_workspaces(
     deterministic: Arc<Deterministic>,
@@ -1126,17 +1147,22 @@ async fn test_following_across_workspaces(
     let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
     let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
 
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+
+    cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
+    cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
+
     let project_a_id = active_call_a
         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
         .await
         .unwrap();
+    /*
     let project_b_id = active_call_b
         .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
         .await
         .unwrap();
-
-    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+        */
 
     active_call_a
         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
@@ -1157,7 +1183,9 @@ async fn test_following_across_workspaces(
         .unwrap();
 
     deterministic.run_until_parked();
-    assert_eq!(cx_b.windows().len(), 1);
+    assert_eq!(cx_b.windows().len(), 2);
+
+    assert_eq!(visible_push_notifications(cx_b).len(), 1);
 
     workspace_b.update(cx_b, |workspace, cx| {
         workspace
@@ -1186,4 +1214,5 @@ async fn test_following_across_workspaces(
     });
 
     // assert that there are no share notifications open
+    assert_eq!(visible_push_notifications(cx_b).len(), 0);
 }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -948,7 +948,7 @@ impl CollabTitlebarItem {
     fn render_face_pile(
         &self,
         user: &User,
-        replica_id: Option<ReplicaId>,
+        _replica_id: Option<ReplicaId>,
         peer_id: PeerId,
         location: Option<ParticipantLocation>,
         muted: bool,

crates/collab_ui/src/collab_ui.rs 🔗

@@ -7,7 +7,7 @@ mod face_pile;
 mod incoming_call_notification;
 mod notifications;
 mod panel_settings;
-mod project_shared_notification;
+pub mod project_shared_notification;
 mod sharing_status_indicator;
 
 use call::{report_call_event_for_room, ActiveCall, Room};

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -40,7 +40,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
                     .push(window);
             }
         }
-        room::Event::RemoteProjectUnshared { project_id } => {
+        room::Event::RemoteProjectUnshared { project_id }
+        | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
             if let Some(windows) = notification_windows.remove(&project_id) {
                 for window in windows {
                     window.remove(cx);
@@ -54,6 +55,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
                 }
             }
         }
+        room::Event::RemoteProjectJoined { project_id } => {
+            if let Some(windows) = notification_windows.remove(&project_id) {
+                for window in windows {
+                    window.remove(cx);
+                }
+            }
+        }
         _ => {}
     })
     .detach();
@@ -82,7 +90,6 @@ impl ProjectSharedNotification {
     }
 
     fn join(&mut self, cx: &mut ViewContext<Self>) {
-        cx.remove_window();
         if let Some(app_state) = self.app_state.upgrade() {
             workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
                 .detach_and_log_err(cx);
@@ -90,7 +97,15 @@ impl ProjectSharedNotification {
     }
 
     fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.remove_window();
+        if let Some(active_room) =
+            ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
+        {
+            active_room.update(cx, |_, cx| {
+                cx.emit(room::Event::RemoteProjectInvitationDiscarded {
+                    project_id: self.project_id,
+                });
+            });
+        }
     }
 
     fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {

crates/workspace/src/workspace.rs 🔗

@@ -2529,7 +2529,6 @@ impl Workspace {
 
         if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
             if leader_id == prev_leader_id {
-                dbg!("oh no!");
                 return None;
             }
         }
@@ -2618,7 +2617,6 @@ impl Workspace {
         let project = self.project.read(cx);
 
         let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
-            dbg!("no remote participant yet...");
             return None;
         };
 
@@ -2633,7 +2631,6 @@ impl Workspace {
                 }
             }
         };
-        dbg!(other_project_id);
 
         // if they are active in another project, follow there.
         if let Some(project_id) = other_project_id {
@@ -2650,7 +2647,6 @@ impl Workspace {
         for (existing_leader_id, states_by_pane) in &mut self.follower_states_by_leader {
             if leader_id == *existing_leader_id {
                 for (pane, _) in states_by_pane {
-                    dbg!("focusing pane");
                     cx.focus(pane);
                     return None;
                 }
@@ -4249,7 +4245,6 @@ pub fn join_remote_project(
     app_state: Arc<AppState>,
     cx: &mut AppContext,
 ) -> Task<Result<()>> {
-    dbg!("huh??");
     cx.spawn(|mut cx| async move {
         let existing_workspace = cx
             .windows()
@@ -4268,10 +4263,8 @@ pub fn join_remote_project(
             .flatten();
 
         let workspace = if let Some(existing_workspace) = existing_workspace {
-            dbg!("huh");
             existing_workspace
         } else {
-            dbg!("huh/");
             let active_call = cx.read(ActiveCall::global);
             let room = active_call
                 .read_with(&cx, |call, _| call.room().cloned())
@@ -4287,7 +4280,6 @@ pub fn join_remote_project(
                 })
                 .await?;
 
-            dbg!("huh//");
             let window_bounds_override = window_bounds_env_override(&cx);
             let window = cx.add_window(
                 (app_state.build_window_options)(
@@ -4310,7 +4302,6 @@ pub fn join_remote_project(
             workspace.downgrade()
         };
 
-        dbg!("huh///");
         workspace.window().activate(&mut cx);
         cx.platform().activate(true);
 
@@ -4333,8 +4324,6 @@ pub fn join_remote_project(
                         Some(collaborator.peer_id)
                     });
 
-                dbg!(follow_peer_id);
-
                 if let Some(follow_peer_id) = follow_peer_id {
                     workspace
                         .follow(follow_peer_id, cx)