1use crate::notification_window_options;
2use call::{ActiveCall, room};
3use client::User;
4use collections::HashMap;
5use gpui::{App, Size};
6use std::sync::{Arc, Weak};
7
8use ui::{CollabNotification, prelude::*};
9use util::ResultExt;
10use workspace::AppState;
11
12pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
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, cx);
29 let Some(window) = cx
30 .open_window(options, |_, cx| {
31 cx.new(|_| {
32 ProjectSharedNotification::new(
33 owner.clone(),
34 *project_id,
35 worktree_root_names.clone(),
36 app_state.clone(),
37 )
38 })
39 })
40 .log_err()
41 else {
42 continue;
43 };
44 notification_windows
45 .entry(*project_id)
46 .or_insert(Vec::new())
47 .push(window);
48 }
49 }
50
51 room::Event::RemoteProjectUnshared { project_id }
52 | room::Event::RemoteProjectJoined { project_id }
53 | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
54 if let Some(windows) = notification_windows.remove(project_id) {
55 for window in windows {
56 window
57 .update(cx, |_, window, _| {
58 window.remove_window();
59 })
60 .ok();
61 }
62 }
63 }
64
65 room::Event::RoomLeft { .. } => {
66 for (_, windows) in notification_windows.drain() {
67 for window in windows {
68 window
69 .update(cx, |_, window, _| {
70 window.remove_window();
71 })
72 .ok();
73 }
74 }
75 }
76 _ => {}
77 })
78 .detach();
79}
80
81pub struct ProjectSharedNotification {
82 project_id: u64,
83 worktree_root_names: Vec<String>,
84 owner: Arc<User>,
85 app_state: Weak<AppState>,
86}
87
88impl ProjectSharedNotification {
89 fn new(
90 owner: Arc<User>,
91 project_id: u64,
92 worktree_root_names: Vec<String>,
93 app_state: Weak<AppState>,
94 ) -> Self {
95 Self {
96 project_id,
97 worktree_root_names,
98 owner,
99 app_state,
100 }
101 }
102
103 fn join(&mut self, cx: &mut Context<Self>) {
104 if let Some(app_state) = self.app_state.upgrade() {
105 workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
106 .detach_and_log_err(cx);
107 }
108 }
109
110 fn dismiss(&mut self, cx: &mut Context<Self>) {
111 if let Some(active_room) = ActiveCall::global(cx).read(cx).room().cloned() {
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, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
123 let ui_font = theme::setup_ui_font(window, cx);
124 let no_worktree_root_names = self.worktree_root_names.is_empty();
125
126 let punctuation = if no_worktree_root_names { "" } else { ":" };
127 let main_label = format!(
128 "{} is sharing a project with you{}",
129 self.owner.github_login.clone(),
130 punctuation
131 );
132
133 div().size_full().font(ui_font).child(
134 CollabNotification::new(
135 self.owner.avatar_uri.clone(),
136 Button::new("open", "Open").on_click(cx.listener(move |this, _event, _, cx| {
137 this.join(cx);
138 })),
139 Button::new("dismiss", "Dismiss").on_click(cx.listener(
140 move |this, _event, _, cx| {
141 this.dismiss(cx);
142 },
143 )),
144 )
145 .child(Label::new(main_label))
146 .when(!no_worktree_root_names, |this| {
147 this.child(Label::new(self.worktree_root_names.join(", ")).color(Color::Muted))
148 }),
149 )
150 }
151}