1use client::{ContactEvent, ContactEventKind, UserStore};
2use gpui::{
3 elements::*, impl_internal_actions, platform::CursorStyle, Entity, ModelHandle,
4 MutableAppContext, RenderContext, View, ViewContext,
5};
6use settings::Settings;
7use workspace::Notification;
8
9use crate::render_icon_button;
10
11impl_internal_actions!(contact_notifications, [Dismiss, RespondToContactRequest]);
12
13pub fn init(cx: &mut MutableAppContext) {
14 cx.add_action(ContactNotification::dismiss);
15 cx.add_action(ContactNotification::respond_to_contact_request);
16}
17
18pub struct ContactNotification {
19 user_store: ModelHandle<UserStore>,
20 event: ContactEvent,
21}
22
23#[derive(Clone)]
24struct Dismiss(u64);
25
26#[derive(Clone)]
27pub struct RespondToContactRequest {
28 pub user_id: u64,
29 pub accept: bool,
30}
31
32pub enum Event {
33 Dismiss,
34}
35
36enum Decline {}
37enum Accept {}
38
39impl Entity for ContactNotification {
40 type Event = Event;
41}
42
43impl View for ContactNotification {
44 fn ui_name() -> &'static str {
45 "ContactNotification"
46 }
47
48 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
49 match self.event.kind {
50 ContactEventKind::Requested => self.render_incoming_request(cx),
51 ContactEventKind::Accepted => self.render_acceptance(cx),
52 _ => unreachable!(),
53 }
54 }
55}
56
57impl Notification for ContactNotification {
58 fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
59 matches!(event, Event::Dismiss)
60 }
61}
62
63impl ContactNotification {
64 pub fn new(
65 event: ContactEvent,
66 user_store: ModelHandle<UserStore>,
67 cx: &mut ViewContext<Self>,
68 ) -> Self {
69 cx.subscribe(&user_store, move |this, _, event, cx| {
70 if let client::ContactEvent {
71 kind: ContactEventKind::Cancelled,
72 user,
73 } = event
74 {
75 if user.id == this.event.user.id {
76 cx.emit(Event::Dismiss);
77 }
78 }
79 })
80 .detach();
81
82 Self { event, user_store }
83 }
84
85 fn render_incoming_request(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
86 let theme = cx.global::<Settings>().theme.clone();
87 let theme = &theme.contact_notification;
88 let user = &self.event.user;
89 let user_id = user.id;
90
91 Flex::column()
92 .with_child(self.render_header("wants to add you as a contact.", theme, cx))
93 .with_child(
94 Label::new(
95 "They won't know if you decline.".to_string(),
96 theme.body_message.text.clone(),
97 )
98 .contained()
99 .with_style(theme.body_message.container)
100 .boxed(),
101 )
102 .with_child(
103 Flex::row()
104 .with_child(
105 MouseEventHandler::new::<Decline, _, _>(
106 self.event.user.id as usize,
107 cx,
108 |state, _| {
109 let button = theme.button.style_for(state, false);
110 Label::new("Decline".to_string(), button.text.clone())
111 .contained()
112 .with_style(button.container)
113 .boxed()
114 },
115 )
116 .with_cursor_style(CursorStyle::PointingHand)
117 .on_click(move |_, cx| {
118 cx.dispatch_action(RespondToContactRequest {
119 user_id,
120 accept: false,
121 });
122 })
123 .boxed(),
124 )
125 .with_child(
126 MouseEventHandler::new::<Accept, _, _>(user.id as usize, cx, |state, _| {
127 let button = theme.button.style_for(state, false);
128 Label::new("Accept".to_string(), button.text.clone())
129 .contained()
130 .with_style(button.container)
131 .boxed()
132 })
133 .with_cursor_style(CursorStyle::PointingHand)
134 .on_click(move |_, cx| {
135 cx.dispatch_action(RespondToContactRequest {
136 user_id,
137 accept: true,
138 });
139 })
140 .boxed(),
141 )
142 .aligned()
143 .right()
144 .boxed(),
145 )
146 .contained()
147 .boxed()
148 }
149
150 fn render_acceptance(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
151 let theme = cx.global::<Settings>().theme.clone();
152 let theme = &theme.contact_notification;
153
154 self.render_header("accepted your contact request", theme, cx)
155 }
156
157 fn render_header(
158 &self,
159 message: &'static str,
160 theme: &theme::ContactNotification,
161 cx: &mut RenderContext<Self>,
162 ) -> ElementBox {
163 let user = &self.event.user;
164 let user_id = user.id;
165 Flex::row()
166 .with_children(user.avatar.clone().map(|avatar| {
167 Image::new(avatar)
168 .with_style(theme.header_avatar)
169 .aligned()
170 .constrained()
171 .with_height(
172 cx.font_cache()
173 .line_height(theme.header_message.text.font_size),
174 )
175 .aligned()
176 .top()
177 .boxed()
178 }))
179 .with_child(
180 Text::new(
181 format!("{} {}", user.github_login, message),
182 theme.header_message.text.clone(),
183 )
184 .contained()
185 .with_style(theme.header_message.container)
186 .aligned()
187 .top()
188 .left()
189 .flex(1., true)
190 .boxed(),
191 )
192 .with_child(
193 MouseEventHandler::new::<Dismiss, _, _>(user.id as usize, cx, |state, _| {
194 render_icon_button(
195 theme.dismiss_button.style_for(state, false),
196 "icons/decline.svg",
197 )
198 .boxed()
199 })
200 .with_cursor_style(CursorStyle::PointingHand)
201 .with_padding(Padding::uniform(5.))
202 .on_click(move |_, cx| cx.dispatch_action(Dismiss(user_id)))
203 .aligned()
204 .constrained()
205 .with_height(
206 cx.font_cache()
207 .line_height(theme.header_message.text.font_size),
208 )
209 .aligned()
210 .top()
211 .flex_float()
212 .boxed(),
213 )
214 .named("contact notification header")
215 }
216
217 fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
218 self.user_store.update(cx, |store, cx| {
219 store
220 .dismiss_contact_request(self.event.user.id, cx)
221 .detach_and_log_err(cx);
222 });
223 cx.emit(Event::Dismiss);
224 }
225
226 fn respond_to_contact_request(
227 &mut self,
228 action: &RespondToContactRequest,
229 cx: &mut ViewContext<Self>,
230 ) {
231 self.user_store
232 .update(cx, |store, cx| {
233 store.respond_to_contact_request(action.user_id, action.accept, cx)
234 })
235 .detach();
236 }
237}