Show project root names when displaying incoming call notification

Antonio Scandurra created

Change summary

crates/call/src/call.rs                            |  4 
crates/collab/src/integration_tests.rs             |  6 +
crates/collab/src/rpc/store.rs                     | 35 ++++++++----
crates/collab_ui/src/incoming_call_notification.rs | 44 ++++++++++++++-
crates/rpc/proto/zed.proto                         |  2 
crates/theme/src/theme.rs                          |  3 +
styles/src/styleTree/incomingCallNotification.ts   |  6 ++
styles/src/styleTree/projectSharedNotification.ts  |  2 
8 files changed, 80 insertions(+), 22 deletions(-)

Detailed changes

crates/call/src/call.rs 🔗

@@ -23,7 +23,7 @@ pub struct IncomingCall {
     pub room_id: u64,
     pub caller: Arc<User>,
     pub participants: Vec<Arc<User>>,
-    pub initial_project_id: Option<u64>,
+    pub initial_project: Option<proto::ParticipantProject>,
 }
 
 pub struct ActiveCall {
@@ -78,7 +78,7 @@ impl ActiveCall {
                     user_store.get_user(envelope.payload.caller_user_id, cx)
                 })
                 .await?,
-            initial_project_id: envelope.payload.initial_project_id,
+            initial_project: envelope.payload.initial_project,
         };
         this.update(&mut cx, |this, _| {
             *this.incoming_call.0.borrow_mut() = Some(call);

crates/collab/src/integration_tests.rs 🔗

@@ -541,13 +541,15 @@ async fn test_share_project(
     deterministic.run_until_parked();
     let call = incoming_call_b.borrow().clone().unwrap();
     assert_eq!(call.caller.github_login, "user_a");
-    let project_id = call.initial_project_id.unwrap();
+    let initial_project = call.initial_project.unwrap();
     active_call_b
         .update(cx_b, |call, cx| call.accept_incoming(cx))
         .await
         .unwrap();
     let client_b_peer_id = client_b.peer_id;
-    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    let project_b = client_b
+        .build_remote_project(initial_project.id, cx_b)
+        .await;
     let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
 
     deterministic.run_until_parked();

crates/collab/src/rpc/store.rs 🔗

@@ -176,9 +176,9 @@ impl Store {
                         .iter()
                         .map(|participant| participant.user_id)
                         .collect(),
-                    initial_project_id: active_call
+                    initial_project: active_call
                         .initial_project_id
-                        .map(|project_id| project_id.to_proto()),
+                        .and_then(|id| Self::build_participant_project(id, &self.projects)),
                 })
             }
         } else {
@@ -572,7 +572,8 @@ impl Store {
                     .iter()
                     .map(|participant| participant.user_id)
                     .collect(),
-                initial_project_id: initial_project_id.map(|project_id| project_id.to_proto()),
+                initial_project: initial_project_id
+                    .and_then(|id| Self::build_participant_project(id, &self.projects)),
             },
         ))
     }
@@ -726,14 +727,6 @@ impl Store {
             .iter_mut()
             .find(|participant| participant.peer_id == host_connection_id.0)
             .ok_or_else(|| anyhow!("no such room"))?;
-        participant.projects.push(proto::ParticipantProject {
-            id: project_id.to_proto(),
-            worktree_root_names: worktrees
-                .iter()
-                .filter(|worktree| worktree.visible)
-                .map(|worktree| worktree.root_name.clone())
-                .collect(),
-        });
 
         connection.projects.insert(project_id);
         self.projects.insert(
@@ -767,6 +760,10 @@ impl Store {
             },
         );
 
+        participant
+            .projects
+            .extend(Self::build_participant_project(project_id, &self.projects));
+
         Ok(room)
     }
 
@@ -1011,6 +1008,22 @@ impl Store {
         Ok(connection_ids)
     }
 
+    fn build_participant_project(
+        project_id: ProjectId,
+        projects: &BTreeMap<ProjectId, Project>,
+    ) -> Option<proto::ParticipantProject> {
+        Some(proto::ParticipantProject {
+            id: project_id.to_proto(),
+            worktree_root_names: projects
+                .get(&project_id)?
+                .worktrees
+                .values()
+                .filter(|worktree| worktree.visible)
+                .map(|worktree| worktree.root_name.clone())
+                .collect(),
+        })
+    }
+
     pub fn project_connection_ids(
         &self,
         project_id: ProjectId,

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -1,4 +1,5 @@
 use call::{ActiveCall, IncomingCall};
+use client::proto;
 use futures::StreamExt;
 use gpui::{
     elements::*,
@@ -26,7 +27,11 @@ pub fn init(cx: &mut MutableAppContext) {
             if let Some(incoming_call) = incoming_call {
                 const PADDING: f32 = 16.;
                 let screen_size = cx.platform().screen_size();
-                let window_size = vec2f(274., 64.);
+
+                let window_size = cx.read(|cx| {
+                    let theme = &cx.global::<Settings>().theme.incoming_call_notification;
+                    vec2f(theme.window_width, theme.window_height)
+                });
                 let (window_id, _) = cx.add_window(
                     WindowOptions {
                         bounds: WindowBounds::Fixed(RectF::new(
@@ -66,7 +71,7 @@ impl IncomingCallNotification {
         if action.accept {
             let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
             let caller_user_id = self.call.caller.id;
-            let initial_project_id = self.call.initial_project_id;
+            let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
             cx.spawn_weak(|_, mut cx| async move {
                 join.await?;
                 if let Some(project_id) = initial_project_id {
@@ -89,6 +94,12 @@ impl IncomingCallNotification {
 
     fn render_caller(&self, cx: &mut RenderContext<Self>) -> ElementBox {
         let theme = &cx.global::<Settings>().theme.incoming_call_notification;
+        let default_project = proto::ParticipantProject::default();
+        let initial_project = self
+            .call
+            .initial_project
+            .as_ref()
+            .unwrap_or(&default_project);
         Flex::row()
             .with_children(self.call.caller.avatar.clone().map(|avatar| {
                 Image::new(avatar)
@@ -108,11 +119,34 @@ impl IncomingCallNotification {
                         .boxed(),
                     )
                     .with_child(
-                        Label::new("is calling you".into(), theme.caller_message.text.clone())
+                        Label::new(
+                            format!(
+                                "is sharing a project in Zed{}",
+                                if initial_project.worktree_root_names.is_empty() {
+                                    ""
+                                } else {
+                                    ":"
+                                }
+                            ),
+                            theme.caller_message.text.clone(),
+                        )
+                        .contained()
+                        .with_style(theme.caller_message.container)
+                        .boxed(),
+                    )
+                    .with_children(if initial_project.worktree_root_names.is_empty() {
+                        None
+                    } else {
+                        Some(
+                            Label::new(
+                                initial_project.worktree_root_names.join(", "),
+                                theme.worktree_roots.text.clone(),
+                            )
                             .contained()
-                            .with_style(theme.caller_message.container)
+                            .with_style(theme.worktree_roots.container)
                             .boxed(),
-                    )
+                        )
+                    })
                     .contained()
                     .with_style(theme.caller_metadata)
                     .aligned()

crates/rpc/proto/zed.proto 🔗

@@ -195,7 +195,7 @@ message IncomingCall {
     uint64 room_id = 1;
     uint64 caller_user_id = 2;
     repeated uint64 participant_user_ids = 3;
-    optional uint64 initial_project_id = 4;
+    optional ParticipantProject initial_project = 4;
 }
 
 message CallCanceled {}

crates/theme/src/theme.rs 🔗

@@ -488,6 +488,8 @@ pub struct ProjectSharedNotification {
 
 #[derive(Deserialize, Default)]
 pub struct IncomingCallNotification {
+    pub window_height: f32,
+    pub window_width: f32,
     #[serde(default)]
     pub background: Color,
     pub caller_container: ContainerStyle,
@@ -495,6 +497,7 @@ pub struct IncomingCallNotification {
     pub caller_metadata: ContainerStyle,
     pub caller_username: ContainedText,
     pub caller_message: ContainedText,
+    pub worktree_roots: ContainedText,
     pub button_width: f32,
     pub accept_button: ContainedText,
     pub decline_button: ContainedText,

styles/src/styleTree/incomingCallNotification.ts 🔗

@@ -4,6 +4,8 @@ import { backgroundColor, borderColor, text } from "./components";
 export default function incomingCallNotification(theme: Theme): Object {
   const avatarSize = 32;
   return {
+    windowHeight: 74,
+    windowWidth: 380,
     background: backgroundColor(theme, 300),
     callerContainer: {
       padding: 12,
@@ -24,6 +26,10 @@ export default function incomingCallNotification(theme: Theme): Object {
       ...text(theme, "sans", "secondary", { size: "xs" }),
       margin: { top: -3 },
     },
+    worktreeRoots: {
+      ...text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
+      margin: { top: -3 },
+    },
     buttonWidth: 96,
     acceptButton: {
       background: backgroundColor(theme, "ok", "active"),

styles/src/styleTree/projectSharedNotification.ts 🔗

@@ -4,7 +4,7 @@ import { backgroundColor, borderColor, text } from "./components";
 export default function projectSharedNotification(theme: Theme): Object {
   const avatarSize = 48;
   return {
-    windowHeight: 72,
+    windowHeight: 74,
     windowWidth: 380,
     background: backgroundColor(theme, 300),
     ownerContainer: {