Merge pull request #1806 from zed-industries/pending-state-when-calling

Antonio Scandurra created

Show a `Calling` indicator right away when initiating a call

Change summary

crates/call/src/call.rs              | 63 ++++++++++++++++++++---------
crates/collab_ui/src/contact_list.rs | 45 ++++++++++++++++----
2 files changed, 78 insertions(+), 30 deletions(-)

Detailed changes

crates/call/src/call.rs 🔗

@@ -3,6 +3,7 @@ pub mod room;
 
 use anyhow::{anyhow, Result};
 use client::{proto, Client, TypedEnvelope, User, UserStore};
+use collections::HashSet;
 use gpui::{
     AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
     Subscription, Task, WeakModelHandle,
@@ -27,8 +28,9 @@ pub struct IncomingCall {
 }
 
 pub struct ActiveCall {
-    location: Option<WeakModelHandle<Project>>,
     room: Option<(ModelHandle<Room>, Vec<Subscription>)>,
+    location: Option<WeakModelHandle<Project>>,
+    pending_invites: HashSet<u64>,
     incoming_call: (
         watch::Sender<Option<IncomingCall>>,
         watch::Receiver<Option<IncomingCall>>,
@@ -51,6 +53,7 @@ impl ActiveCall {
         Self {
             room: None,
             location: None,
+            pending_invites: Default::default(),
             incoming_call: watch::channel(),
             _subscriptions: vec![
                 client.add_request_handler(cx.handle(), Self::handle_incoming_call),
@@ -113,33 +116,49 @@ impl ActiveCall {
     ) -> Task<Result<()>> {
         let client = self.client.clone();
         let user_store = self.user_store.clone();
+        if !self.pending_invites.insert(recipient_user_id) {
+            return Task::ready(Err(anyhow!("user was already invited")));
+        }
+
+        cx.notify();
         cx.spawn(|this, mut cx| async move {
-            if let Some(room) = this.read_with(&cx, |this, _| this.room().cloned()) {
-                let initial_project_id = if let Some(initial_project) = initial_project {
-                    Some(
-                        room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))
+            let invite = async {
+                if let Some(room) = this.read_with(&cx, |this, _| this.room().cloned()) {
+                    let initial_project_id = if let Some(initial_project) = initial_project {
+                        Some(
+                            room.update(&mut cx, |room, cx| {
+                                room.share_project(initial_project, cx)
+                            })
                             .await?,
-                    )
-                } else {
-                    None
-                };
+                        )
+                    } else {
+                        None
+                    };
 
-                room.update(&mut cx, |room, cx| {
-                    room.call(recipient_user_id, initial_project_id, cx)
-                })
-                .await?;
-            } else {
-                let room = cx
-                    .update(|cx| {
-                        Room::create(recipient_user_id, initial_project, client, user_store, cx)
+                    room.update(&mut cx, |room, cx| {
+                        room.call(recipient_user_id, initial_project_id, cx)
                     })
                     .await?;
+                } else {
+                    let room = cx
+                        .update(|cx| {
+                            Room::create(recipient_user_id, initial_project, client, user_store, cx)
+                        })
+                        .await?;
 
-                this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))
-                    .await?;
+                    this.update(&mut cx, |this, cx| this.set_room(Some(room), cx))
+                        .await?;
+                };
+
+                Ok(())
             };
 
-            Ok(())
+            let result = invite.await;
+            this.update(&mut cx, |this, cx| {
+                this.pending_invites.remove(&recipient_user_id);
+                cx.notify();
+            });
+            result
         })
     }
 
@@ -274,4 +293,8 @@ impl ActiveCall {
     pub fn room(&self) -> Option<&ModelHandle<Room>> {
         self.room.as_ref().map(|(room, _)| room)
     }
+
+    pub fn pending_invites(&self) -> &HashSet<u64> {
+        &self.pending_invites
+    }
 }

crates/collab_ui/src/contact_list.rs 🔗

@@ -73,7 +73,10 @@ enum ContactEntry {
     },
     IncomingRequest(Arc<User>),
     OutgoingRequest(Arc<User>),
-    Contact(Arc<Contact>),
+    Contact {
+        contact: Arc<Contact>,
+        calling: bool,
+    },
 }
 
 impl PartialEq for ContactEntry {
@@ -121,8 +124,13 @@ impl PartialEq for ContactEntry {
                     return user_1.id == user_2.id;
                 }
             }
-            ContactEntry::Contact(contact_1) => {
-                if let ContactEntry::Contact(contact_2) = other {
+            ContactEntry::Contact {
+                contact: contact_1, ..
+            } => {
+                if let ContactEntry::Contact {
+                    contact: contact_2, ..
+                } = other
+                {
                     return contact_1.user.id == contact_2.user.id;
                 }
             }
@@ -255,8 +263,9 @@ impl ContactList {
                     is_selected,
                     cx,
                 ),
-                ContactEntry::Contact(contact) => Self::render_contact(
+                ContactEntry::Contact { contact, calling } => Self::render_contact(
                     contact,
+                    *calling,
                     &this.project,
                     &theme.contact_list,
                     is_selected,
@@ -349,8 +358,8 @@ impl ContactList {
                         let section = *section;
                         self.toggle_expanded(&ToggleExpanded(section), cx);
                     }
-                    ContactEntry::Contact(contact) => {
-                        if contact.online && !contact.busy {
+                    ContactEntry::Contact { contact, calling } => {
+                        if contact.online && !contact.busy && !calling {
                             self.call(
                                 &Call {
                                     recipient_user_id: contact.user.id,
@@ -621,9 +630,13 @@ impl ContactList {
                 if !matches.is_empty() {
                     self.entries.push(ContactEntry::Header(section));
                     if !self.collapsed_sections.contains(&section) {
+                        let active_call = &ActiveCall::global(cx).read(cx);
                         for mat in matches {
                             let contact = &contacts[mat.candidate_id];
-                            self.entries.push(ContactEntry::Contact(contact.clone()));
+                            self.entries.push(ContactEntry::Contact {
+                                contact: contact.clone(),
+                                calling: active_call.pending_invites().contains(&contact.user.id),
+                            });
                         }
                     }
                 }
@@ -968,13 +981,14 @@ impl ContactList {
 
     fn render_contact(
         contact: &Contact,
+        calling: bool,
         project: &ModelHandle<Project>,
         theme: &theme::ContactList,
         is_selected: bool,
         cx: &mut RenderContext<Self>,
     ) -> ElementBox {
         let online = contact.online;
-        let busy = contact.busy;
+        let busy = contact.busy || calling;
         let user_id = contact.user.id;
         let initial_project = project.clone();
         let mut element =
@@ -986,7 +1000,7 @@ impl ContactList {
                                 Empty::new()
                                     .collapsed()
                                     .contained()
-                                    .with_style(if contact.busy {
+                                    .with_style(if busy {
                                         theme.contact_status_busy
                                     } else {
                                         theme.contact_status_free
@@ -1020,6 +1034,17 @@ impl ContactList {
                         .flex(1., true)
                         .boxed(),
                     )
+                    .with_children(if calling {
+                        Some(
+                            Label::new("Calling".to_string(), theme.calling_indicator.text.clone())
+                                .contained()
+                                .with_style(theme.calling_indicator.container)
+                                .aligned()
+                                .boxed(),
+                        )
+                    } else {
+                        None
+                    })
                     .constrained()
                     .with_height(theme.row_height)
                     .contained()
@@ -1164,7 +1189,7 @@ impl ContactList {
         let initial_project = action.initial_project.clone();
         ActiveCall::global(cx)
             .update(cx, |call, cx| {
-                call.invite(recipient_user_id, initial_project.clone(), cx)
+                call.invite(recipient_user_id, initial_project, cx)
             })
             .detach_and_log_err(cx);
     }