incoming_call_notification.rs

  1use call::{ActiveCall, IncomingCall};
  2use futures::StreamExt;
  3use gpui::{
  4    elements::*,
  5    geometry::{rect::RectF, vector::vec2f},
  6    impl_internal_actions, Entity, MouseButton, MutableAppContext, RenderContext, View,
  7    ViewContext, WindowBounds, WindowKind, WindowOptions,
  8};
  9use settings::Settings;
 10use util::ResultExt;
 11use workspace::JoinProject;
 12
 13impl_internal_actions!(incoming_call_notification, [RespondToCall]);
 14
 15pub fn init(cx: &mut MutableAppContext) {
 16    cx.add_action(IncomingCallNotification::respond_to_call);
 17
 18    let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
 19    cx.spawn(|mut cx| async move {
 20        let mut notification_window = None;
 21        while let Some(incoming_call) = incoming_call.next().await {
 22            if let Some(window_id) = notification_window.take() {
 23                cx.remove_window(window_id);
 24            }
 25
 26            if let Some(incoming_call) = incoming_call {
 27                let (window_id, _) = cx.add_window(
 28                    WindowOptions {
 29                        bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(300., 400.))),
 30                        titlebar: None,
 31                        center: true,
 32                        kind: WindowKind::PopUp,
 33                        is_movable: false,
 34                    },
 35                    |_| IncomingCallNotification::new(incoming_call),
 36                );
 37                notification_window = Some(window_id);
 38            }
 39        }
 40    })
 41    .detach();
 42}
 43
 44#[derive(Clone, PartialEq)]
 45struct RespondToCall {
 46    accept: bool,
 47}
 48
 49pub struct IncomingCallNotification {
 50    call: IncomingCall,
 51}
 52
 53impl IncomingCallNotification {
 54    pub fn new(call: IncomingCall) -> Self {
 55        Self { call }
 56    }
 57
 58    fn respond_to_call(&mut self, action: &RespondToCall, cx: &mut ViewContext<Self>) {
 59        let active_call = ActiveCall::global(cx);
 60        if action.accept {
 61            let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
 62            let caller_user_id = self.call.caller.id;
 63            let initial_project_id = self.call.initial_project_id;
 64            cx.spawn_weak(|_, mut cx| async move {
 65                join.await?;
 66                if let Some(project_id) = initial_project_id {
 67                    cx.update(|cx| {
 68                        cx.dispatch_global_action(JoinProject {
 69                            project_id,
 70                            follow_user_id: caller_user_id,
 71                        })
 72                    });
 73                }
 74                anyhow::Ok(())
 75            })
 76            .detach_and_log_err(cx);
 77        } else {
 78            active_call.update(cx, |active_call, _| {
 79                active_call.decline_incoming().log_err();
 80            });
 81        }
 82    }
 83
 84    fn render_caller(&self, cx: &mut RenderContext<Self>) -> ElementBox {
 85        let theme = &cx.global::<Settings>().theme.contacts_popover;
 86        Flex::row()
 87            .with_children(
 88                self.call
 89                    .caller
 90                    .avatar
 91                    .clone()
 92                    .map(|avatar| Image::new(avatar).with_style(theme.contact_avatar).boxed()),
 93            )
 94            .with_child(
 95                Label::new(
 96                    self.call.caller.github_login.clone(),
 97                    theme.contact_username.text.clone(),
 98                )
 99                .boxed(),
100            )
101            .boxed()
102    }
103
104    fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
105        enum Accept {}
106        enum Decline {}
107
108        Flex::row()
109            .with_child(
110                MouseEventHandler::<Accept>::new(0, cx, |_, cx| {
111                    let theme = &cx.global::<Settings>().theme.contacts_popover;
112                    Label::new("Accept".to_string(), theme.contact_username.text.clone()).boxed()
113                })
114                .on_click(MouseButton::Left, |_, cx| {
115                    cx.dispatch_action(RespondToCall { accept: true });
116                })
117                .boxed(),
118            )
119            .with_child(
120                MouseEventHandler::<Decline>::new(0, cx, |_, cx| {
121                    let theme = &cx.global::<Settings>().theme.contacts_popover;
122                    Label::new("Decline".to_string(), theme.contact_username.text.clone()).boxed()
123                })
124                .on_click(MouseButton::Left, |_, cx| {
125                    cx.dispatch_action(RespondToCall { accept: false });
126                })
127                .boxed(),
128            )
129            .boxed()
130    }
131}
132
133impl Entity for IncomingCallNotification {
134    type Event = ();
135}
136
137impl View for IncomingCallNotification {
138    fn ui_name() -> &'static str {
139        "IncomingCallNotification"
140    }
141
142    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
143        Flex::column()
144            .with_child(self.render_caller(cx))
145            .with_child(self.render_buttons(cx))
146            .boxed()
147    }
148}