1use crate::notification_window_options;
2use crate::notifications::collab_notification::CollabNotification;
3use call::{room, ActiveCall};
4use client::User;
5use collections::HashMap;
6use gpui::{AppContext, Size};
7use settings::Settings;
8use std::sync::{Arc, Weak};
9use theme::ThemeSettings;
10use ui::{prelude::*, Button, Label};
11use util::ResultExt;
12use workspace::AppState;
13
14pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
15 let app_state = Arc::downgrade(app_state);
16 let active_call = ActiveCall::global(cx);
17 let mut notification_windows = HashMap::default();
18 cx.subscribe(&active_call, move |_, event, cx| match event {
19 room::Event::RemoteProjectShared {
20 owner,
21 project_id,
22 worktree_root_names,
23 } => {
24 let window_size = Size {
25 width: px(400.),
26 height: px(72.),
27 };
28
29 for screen in cx.displays() {
30 let options = notification_window_options(screen, window_size, cx);
31 let Some(window) = cx
32 .open_window(options, |cx| {
33 cx.new_view(|_| {
34 ProjectSharedNotification::new(
35 owner.clone(),
36 *project_id,
37 worktree_root_names.clone(),
38 app_state.clone(),
39 )
40 })
41 })
42 .log_err()
43 else {
44 continue;
45 };
46 notification_windows
47 .entry(*project_id)
48 .or_insert(Vec::new())
49 .push(window);
50 }
51 }
52
53 room::Event::RemoteProjectUnshared { project_id }
54 | room::Event::RemoteProjectJoined { project_id }
55 | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
56 if let Some(windows) = notification_windows.remove(&project_id) {
57 for window in windows {
58 window
59 .update(cx, |_, cx| {
60 cx.remove_window();
61 })
62 .ok();
63 }
64 }
65 }
66
67 room::Event::RoomLeft { .. } => {
68 for (_, windows) in notification_windows.drain() {
69 for window in windows {
70 window
71 .update(cx, |_, cx| {
72 cx.remove_window();
73 })
74 .ok();
75 }
76 }
77 }
78 _ => {}
79 })
80 .detach();
81}
82
83pub struct ProjectSharedNotification {
84 project_id: u64,
85 worktree_root_names: Vec<String>,
86 owner: Arc<User>,
87 app_state: Weak<AppState>,
88}
89
90impl ProjectSharedNotification {
91 fn new(
92 owner: Arc<User>,
93 project_id: u64,
94 worktree_root_names: Vec<String>,
95 app_state: Weak<AppState>,
96 ) -> Self {
97 Self {
98 project_id,
99 worktree_root_names,
100 owner,
101 app_state,
102 }
103 }
104
105 fn join(&mut self, cx: &mut ViewContext<Self>) {
106 if let Some(app_state) = self.app_state.upgrade() {
107 workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
108 .detach_and_log_err(cx);
109 }
110 }
111
112 fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
113 if let Some(active_room) =
114 ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
115 {
116 active_room.update(cx, |_, cx| {
117 cx.emit(room::Event::RemoteProjectInvitationDiscarded {
118 project_id: self.project_id,
119 });
120 });
121 }
122 }
123}
124
125impl Render for ProjectSharedNotification {
126 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
127 // TODO: Is there a better place for us to initialize the font?
128 let (ui_font, ui_font_size) = {
129 let theme_settings = ThemeSettings::get_global(cx);
130 (theme_settings.ui_font.clone(), theme_settings.ui_font_size)
131 };
132
133 cx.set_rem_size(ui_font_size);
134
135 div().size_full().font(ui_font).child(
136 CollabNotification::new(
137 self.owner.avatar_uri.clone(),
138 Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
139 this.join(cx);
140 })),
141 Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
142 this.dismiss(cx);
143 })),
144 )
145 .child(Label::new(self.owner.github_login.clone()))
146 .child(Label::new(format!(
147 "is sharing a project in Zed{}",
148 if self.worktree_root_names.is_empty() {
149 ""
150 } else {
151 ":"
152 }
153 )))
154 .children(if self.worktree_root_names.is_empty() {
155 None
156 } else {
157 Some(Label::new(self.worktree_root_names.join(", ")))
158 }),
159 )
160 }
161}