contact_notification.rs

  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}