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