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}