1use crate::notification_window_options;
2use call::{ActiveCall, IncomingCall};
3use futures::StreamExt;
4use gpui::{App, WindowHandle, prelude::*};
5
6use std::sync::{Arc, Weak};
7use ui::{CollabNotification, prelude::*};
8use util::ResultExt;
9use workspace::AppState;
10
11pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
12 let app_state = Arc::downgrade(app_state);
13 let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
14 cx.spawn(async move |cx| {
15 let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
16 while let Some(incoming_call) = incoming_call.next().await {
17 for window in notification_windows.drain(..) {
18 window
19 .update(cx, |_, window, _| {
20 window.remove_window();
21 })
22 .log_err();
23 }
24
25 if let Some(incoming_call) = incoming_call {
26 let unique_screens = cx.update(|cx| cx.displays());
27 let window_size = gpui::Size {
28 width: px(400.),
29 height: px(72.),
30 };
31
32 for screen in unique_screens {
33 let options =
34 cx.update(|cx| notification_window_options(screen, window_size, cx));
35 if let Ok(window) = cx.open_window(options, |_, cx| {
36 cx.new(|_| {
37 IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
38 })
39 }) {
40 notification_windows.push(window);
41 }
42 }
43 }
44 }
45
46 for window in notification_windows.drain(..) {
47 window
48 .update(cx, |_, window, _| {
49 window.remove_window();
50 })
51 .log_err();
52 }
53 })
54 .detach();
55}
56
57struct IncomingCallNotificationState {
58 call: IncomingCall,
59 app_state: Weak<AppState>,
60}
61
62pub struct IncomingCallNotification {
63 state: Arc<IncomingCallNotificationState>,
64}
65impl IncomingCallNotificationState {
66 pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
67 Self { call, app_state }
68 }
69
70 fn respond(&self, accept: bool, cx: &mut App) {
71 let active_call = ActiveCall::global(cx);
72 if accept {
73 let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
74 let caller_user_id = self.call.calling_user.id;
75 let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
76 let app_state = self.app_state.clone();
77 let cx: &mut App = cx;
78 cx.spawn(async move |cx| {
79 join.await?;
80 if let Some(project_id) = initial_project_id {
81 cx.update(|cx| {
82 if let Some(app_state) = app_state.upgrade() {
83 workspace::join_in_room_project(
84 project_id,
85 caller_user_id,
86 app_state,
87 cx,
88 )
89 .detach_and_log_err(cx);
90 }
91 });
92 }
93 anyhow::Ok(())
94 })
95 .detach_and_log_err(cx);
96 } else {
97 active_call.update(cx, |active_call, cx| {
98 active_call.decline_incoming(cx).log_err();
99 });
100 }
101 }
102}
103
104impl IncomingCallNotification {
105 pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
106 Self {
107 state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
108 }
109 }
110}
111
112impl Render for IncomingCallNotification {
113 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
114 let ui_font = theme_settings::setup_ui_font(window, cx);
115
116 div().size_full().font(ui_font).child(
117 CollabNotification::new(
118 self.state.call.calling_user.avatar_uri.clone(),
119 Button::new("accept", "Accept").on_click({
120 let state = self.state.clone();
121 move |_, _, cx| state.respond(true, cx)
122 }),
123 Button::new("decline", "Decline").on_click({
124 let state = self.state.clone();
125 move |_, _, cx| state.respond(false, cx)
126 }),
127 )
128 .child(Label::new(format!(
129 "{} is sharing a project in Zed",
130 self.state.call.calling_user.github_login
131 ))),
132 )
133 }
134}