Re-emit notifications and events from `ActiveCall`

Antonio Scandurra created

This lets us only observe and subscribe to the active call without
needing to track the underlying `Room` if it changes, which implies
writing the same boilerplate over and over.

Change summary

crates/call/src/call.rs                             | 34 +++++++-----
crates/collab_ui/src/collab_titlebar_item.rs        | 17 -----
crates/collab_ui/src/contacts_popover.rs            | 14 ----
crates/collab_ui/src/project_shared_notification.rs | 41 ++++----------
4 files changed, 35 insertions(+), 71 deletions(-)

Detailed changes

crates/call/src/call.rs 🔗

@@ -3,7 +3,7 @@ pub mod room;
 
 use anyhow::{anyhow, Result};
 use client::{incoming_call::IncomingCall, Client, UserStore};
-use gpui::{Entity, ModelContext, ModelHandle, MutableAppContext, Task};
+use gpui::{Entity, ModelContext, ModelHandle, MutableAppContext, Subscription, Task};
 pub use participant::ParticipantLocation;
 pub use room::Room;
 use std::sync::Arc;
@@ -14,13 +14,13 @@ pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut Mu
 }
 
 pub struct ActiveCall {
-    room: Option<ModelHandle<Room>>,
+    room: Option<(ModelHandle<Room>, Vec<Subscription>)>,
     client: Arc<Client>,
     user_store: ModelHandle<UserStore>,
 }
 
 impl Entity for ActiveCall {
-    type Event = ();
+    type Event = room::Event;
 }
 
 impl ActiveCall {
@@ -41,8 +41,7 @@ impl ActiveCall {
         recipient_user_id: u64,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
-        let room = self.room.clone();
-
+        let room = self.room.as_ref().map(|(room, _)| room.clone());
         let client = self.client.clone();
         let user_store = self.user_store.clone();
         cx.spawn(|this, mut cx| async move {
@@ -50,10 +49,7 @@ impl ActiveCall {
                 room
             } else {
                 let room = cx.update(|cx| Room::create(client, user_store, cx)).await?;
-                this.update(&mut cx, |this, cx| {
-                    this.room = Some(room.clone());
-                    cx.notify();
-                });
+                this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx));
                 room
             };
             room.update(&mut cx, |room, cx| room.call(recipient_user_id, cx))
@@ -71,15 +67,25 @@ impl ActiveCall {
         let join = Room::join(call, self.client.clone(), self.user_store.clone(), cx);
         cx.spawn(|this, mut cx| async move {
             let room = join.await?;
-            this.update(&mut cx, |this, cx| {
-                this.room = Some(room);
-                cx.notify();
-            });
+            this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx));
             Ok(())
         })
     }
 
+    fn set_room(&mut self, room: Option<ModelHandle<Room>>, cx: &mut ModelContext<Self>) {
+        if let Some(room) = room {
+            let subscriptions = vec![
+                cx.observe(&room, |_, _, cx| cx.notify()),
+                cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
+            ];
+            self.room = Some((room, subscriptions));
+        } else {
+            self.room = None;
+        }
+        cx.notify();
+    }
+
     pub fn room(&self) -> Option<&ModelHandle<Room>> {
-        self.room.as_ref()
+        self.room.as_ref().map(|(room, _)| room)
     }
 }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -30,7 +30,6 @@ pub fn init(cx: &mut MutableAppContext) {
 pub struct CollabTitlebarItem {
     workspace: WeakViewHandle<Workspace>,
     contacts_popover: Option<ViewHandle<ContactsPopover>>,
-    room_subscription: Option<Subscription>,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -73,24 +72,12 @@ impl CollabTitlebarItem {
         let active_call = ActiveCall::global(cx);
         let mut subscriptions = Vec::new();
         subscriptions.push(cx.observe(workspace, |_, _, cx| cx.notify()));
-        subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
-        let mut this = Self {
+        subscriptions.push(cx.observe(&active_call, |_, _, cx| cx.notify()));
+        Self {
             workspace: workspace.downgrade(),
             contacts_popover: None,
-            room_subscription: None,
             _subscriptions: subscriptions,
-        };
-        this.active_call_changed(cx);
-        this
-    }
-
-    fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-            self.room_subscription = Some(cx.observe(&room, |_, _, cx| cx.notify()));
-        } else {
-            self.room_subscription = None;
         }
-        cx.notify();
     }
 
     fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {

crates/collab_ui/src/contacts_popover.rs 🔗

@@ -80,7 +80,6 @@ pub enum Event {
 }
 
 pub struct ContactsPopover {
-    room_subscription: Option<Subscription>,
     entries: Vec<ContactEntry>,
     match_candidates: Vec<StringMatchCandidate>,
     list_state: ListState,
@@ -159,10 +158,9 @@ impl ContactsPopover {
         let active_call = ActiveCall::global(cx);
         let mut subscriptions = Vec::new();
         subscriptions.push(cx.observe(&user_store, |this, _, cx| this.update_entries(cx)));
-        subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
+        subscriptions.push(cx.observe(&active_call, |_, _, cx| cx.notify()));
 
         let mut this = Self {
-            room_subscription: None,
             list_state,
             selection: None,
             collapsed_sections: Default::default(),
@@ -173,19 +171,9 @@ impl ContactsPopover {
             user_store,
         };
         this.update_entries(cx);
-        this.active_call_changed(cx);
         this
     }
 
-    fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-            self.room_subscription = Some(cx.observe(&room, |_, _, cx| cx.notify()));
-        } else {
-            self.room_subscription = None;
-        }
-        cx.notify();
-    }
-
     fn clear_filter(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
         let did_clear = self.filter_editor.update(cx, |editor, cx| {
             if editor.buffer().read(cx).len(cx) > 0 {

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -19,35 +19,18 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
     cx.add_action(ProjectSharedNotification::dismiss);
 
     let active_call = ActiveCall::global(cx);
-    let mut _room_subscription = None;
-    cx.observe(&active_call, move |active_call, cx| {
-        if let Some(room) = active_call.read(cx).room().cloned() {
-            let app_state = app_state.clone();
-            _room_subscription = Some(cx.subscribe(&room, move |_, event, cx| match event {
-                room::Event::RemoteProjectShared { owner, project_id } => {
-                    cx.add_window(
-                        WindowOptions {
-                            bounds: WindowBounds::Fixed(RectF::new(
-                                vec2f(0., 0.),
-                                vec2f(300., 400.),
-                            )),
-                            titlebar: None,
-                            center: true,
-                            kind: WindowKind::PopUp,
-                            is_movable: false,
-                        },
-                        |_| {
-                            ProjectSharedNotification::new(
-                                *project_id,
-                                owner.clone(),
-                                app_state.clone(),
-                            )
-                        },
-                    );
-                }
-            }));
-        } else {
-            _room_subscription = None;
+    cx.subscribe(&active_call, move |_, event, cx| match event {
+        room::Event::RemoteProjectShared { owner, project_id } => {
+            cx.add_window(
+                WindowOptions {
+                    bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(300., 400.))),
+                    titlebar: None,
+                    center: true,
+                    kind: WindowKind::PopUp,
+                    is_movable: false,
+                },
+                |_| ProjectSharedNotification::new(*project_id, owner.clone(), app_state.clone()),
+            );
         }
     })
     .detach();