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