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