incoming_call_notification.rs

  1use crate::notification_window_options;
  2use call::{ActiveCall, IncomingCall};
  3use client::proto;
  4use futures::StreamExt;
  5use gpui::{
  6    elements::*,
  7    geometry::vector::vec2f,
  8    platform::{CursorStyle, MouseButton},
  9    AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
 10};
 11use std::sync::{Arc, Weak};
 12use util::ResultExt;
 13use workspace::AppState;
 14
 15pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
 16    let app_state = Arc::downgrade(app_state);
 17    let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
 18    cx.spawn(|mut cx| async move {
 19        let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
 20        while let Some(incoming_call) = incoming_call.next().await {
 21            for window in notification_windows.drain(..) {
 22                window.remove(&mut cx);
 23            }
 24
 25            if let Some(incoming_call) = incoming_call {
 26                let window_size = cx.read(|cx| {
 27                    let theme = &theme::current(cx).incoming_call_notification;
 28                    vec2f(theme.window_width, theme.window_height)
 29                });
 30
 31                for screen in cx.platform().screens() {
 32                    let window = cx
 33                        .add_window(notification_window_options(screen, window_size), |_| {
 34                            IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
 35                        });
 36
 37                    notification_windows.push(window);
 38                }
 39            }
 40        }
 41    })
 42    .detach();
 43}
 44
 45#[derive(Clone, PartialEq)]
 46struct RespondToCall {
 47    accept: bool,
 48}
 49
 50pub struct IncomingCallNotification {
 51    call: IncomingCall,
 52    app_state: Weak<AppState>,
 53}
 54
 55impl IncomingCallNotification {
 56    pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
 57        Self { call, app_state }
 58    }
 59
 60    fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
 61        let active_call = ActiveCall::global(cx);
 62        if accept {
 63            let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
 64            let caller_user_id = self.call.calling_user.id;
 65            let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
 66            let app_state = self.app_state.clone();
 67            cx.app_context()
 68                .spawn(|mut cx| async move {
 69                    join.await?;
 70                    if let Some(project_id) = initial_project_id {
 71                        cx.update(|cx| {
 72                            if let Some(app_state) = app_state.upgrade() {
 73                                workspace::join_remote_project(
 74                                    project_id,
 75                                    caller_user_id,
 76                                    app_state,
 77                                    cx,
 78                                )
 79                                .detach_and_log_err(cx);
 80                            }
 81                        });
 82                    }
 83                    anyhow::Ok(())
 84                })
 85                .detach_and_log_err(cx);
 86        } else {
 87            active_call.update(cx, |active_call, cx| {
 88                active_call.decline_incoming(cx).log_err();
 89            });
 90        }
 91    }
 92
 93    fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
 94        let theme = &theme::current(cx).incoming_call_notification;
 95        let default_project = proto::ParticipantProject::default();
 96        let initial_project = self
 97            .call
 98            .initial_project
 99            .as_ref()
100            .unwrap_or(&default_project);
101        Flex::row()
102            .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
103                Image::from_data(avatar)
104                    .with_style(theme.caller_avatar)
105                    .aligned()
106            }))
107            .with_child(
108                Flex::column()
109                    .with_child(
110                        Label::new(
111                            self.call.calling_user.github_login.clone(),
112                            theme.caller_username.text.clone(),
113                        )
114                        .contained()
115                        .with_style(theme.caller_username.container),
116                    )
117                    .with_child(
118                        Label::new(
119                            format!(
120                                "is sharing a project in Zed{}",
121                                if initial_project.worktree_root_names.is_empty() {
122                                    ""
123                                } else {
124                                    ":"
125                                }
126                            ),
127                            theme.caller_message.text.clone(),
128                        )
129                        .contained()
130                        .with_style(theme.caller_message.container),
131                    )
132                    .with_children(if initial_project.worktree_root_names.is_empty() {
133                        None
134                    } else {
135                        Some(
136                            Label::new(
137                                initial_project.worktree_root_names.join(", "),
138                                theme.worktree_roots.text.clone(),
139                            )
140                            .contained()
141                            .with_style(theme.worktree_roots.container),
142                        )
143                    })
144                    .contained()
145                    .with_style(theme.caller_metadata)
146                    .aligned(),
147            )
148            .contained()
149            .with_style(theme.caller_container)
150            .flex(1., true)
151            .into_any()
152    }
153
154    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
155        enum Accept {}
156        enum Decline {}
157
158        let theme = theme::current(cx);
159        Flex::column()
160            .with_child(
161                MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
162                    let theme = &theme.incoming_call_notification;
163                    Label::new("Accept", theme.accept_button.text.clone())
164                        .aligned()
165                        .contained()
166                        .with_style(theme.accept_button.container)
167                })
168                .with_cursor_style(CursorStyle::PointingHand)
169                .on_click(MouseButton::Left, |_, this, cx| {
170                    this.respond(true, cx);
171                })
172                .flex(1., true),
173            )
174            .with_child(
175                MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
176                    let theme = &theme.incoming_call_notification;
177                    Label::new("Decline", theme.decline_button.text.clone())
178                        .aligned()
179                        .contained()
180                        .with_style(theme.decline_button.container)
181                })
182                .with_cursor_style(CursorStyle::PointingHand)
183                .on_click(MouseButton::Left, |_, this, cx| {
184                    this.respond(false, cx);
185                })
186                .flex(1., true),
187            )
188            .constrained()
189            .with_width(theme.incoming_call_notification.button_width)
190            .into_any()
191    }
192}
193
194impl Entity for IncomingCallNotification {
195    type Event = ();
196}
197
198impl View for IncomingCallNotification {
199    fn ui_name() -> &'static str {
200        "IncomingCallNotification"
201    }
202
203    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
204        let background = theme::current(cx).incoming_call_notification.background;
205        Flex::row()
206            .with_child(self.render_caller(cx))
207            .with_child(self.render_buttons(cx))
208            .contained()
209            .with_background_color(background)
210            .expanded()
211            .into_any()
212    }
213}