contact_finder.rs

  1use client::{ContactRequestStatus, User, UserStore};
  2use gpui::{
  3    actions, elements::*, AnyViewHandle, Entity, ModelHandle, MouseState, MutableAppContext,
  4    RenderContext, Task, View, ViewContext, ViewHandle,
  5};
  6use picker::{Picker, PickerDelegate};
  7use settings::Settings;
  8use std::sync::Arc;
  9use util::TryFutureExt;
 10use workspace::Workspace;
 11
 12use crate::render_icon_button;
 13
 14actions!(contact_finder, [Toggle]);
 15
 16pub fn init(cx: &mut MutableAppContext) {
 17    Picker::<ContactFinder>::init(cx);
 18    cx.add_action(ContactFinder::toggle);
 19}
 20
 21pub struct ContactFinder {
 22    picker: ViewHandle<Picker<Self>>,
 23    potential_contacts: Arc<[Arc<User>]>,
 24    user_store: ModelHandle<UserStore>,
 25    selected_index: usize,
 26}
 27
 28pub enum Event {
 29    Dismissed,
 30}
 31
 32impl Entity for ContactFinder {
 33    type Event = Event;
 34}
 35
 36impl View for ContactFinder {
 37    fn ui_name() -> &'static str {
 38        "ContactFinder"
 39    }
 40
 41    fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
 42        ChildView::new(self.picker.clone()).boxed()
 43    }
 44
 45    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
 46        if cx.is_self_focused() {
 47            cx.focus(&self.picker);
 48        }
 49    }
 50}
 51
 52impl PickerDelegate for ContactFinder {
 53    fn match_count(&self) -> usize {
 54        self.potential_contacts.len()
 55    }
 56
 57    fn selected_index(&self) -> usize {
 58        self.selected_index
 59    }
 60
 61    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
 62        self.selected_index = ix;
 63    }
 64
 65    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
 66        let search_users = self
 67            .user_store
 68            .update(cx, |store, cx| store.fuzzy_search_users(query, cx));
 69
 70        cx.spawn(|this, mut cx| async move {
 71            async {
 72                let potential_contacts = search_users.await?;
 73                this.update(&mut cx, |this, cx| {
 74                    this.potential_contacts = potential_contacts.into();
 75                    cx.notify();
 76                });
 77                Ok(())
 78            }
 79            .log_err()
 80            .await;
 81        })
 82    }
 83
 84    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
 85        if let Some(user) = self.potential_contacts.get(self.selected_index) {
 86            let user_store = self.user_store.read(cx);
 87            match user_store.contact_request_status(user) {
 88                ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
 89                    self.user_store
 90                        .update(cx, |store, cx| store.request_contact(user.id, cx))
 91                        .detach();
 92                }
 93                ContactRequestStatus::RequestSent => {
 94                    self.user_store
 95                        .update(cx, |store, cx| store.remove_contact(user.id, cx))
 96                        .detach();
 97                }
 98                _ => {}
 99            }
100        }
101    }
102
103    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
104        cx.emit(Event::Dismissed);
105    }
106
107    fn render_match(
108        &self,
109        ix: usize,
110        mouse_state: MouseState,
111        selected: bool,
112        cx: &gpui::AppContext,
113    ) -> ElementBox {
114        let theme = &cx.global::<Settings>().theme;
115        let user = &self.potential_contacts[ix];
116        let request_status = self.user_store.read(cx).contact_request_status(user);
117
118        let icon_path = match request_status {
119            ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
120                "icons/check_8.svg"
121            }
122            ContactRequestStatus::RequestSent | ContactRequestStatus::RequestAccepted => {
123                "icons/x_mark_8.svg"
124            }
125        };
126        let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
127            &theme.contact_finder.disabled_contact_button
128        } else {
129            &theme.contact_finder.contact_button
130        };
131        let style = theme.picker.item.style_for(mouse_state, selected);
132        Flex::row()
133            .with_children(user.avatar.clone().map(|avatar| {
134                Image::new(avatar)
135                    .with_style(theme.contact_finder.contact_avatar)
136                    .aligned()
137                    .left()
138                    .boxed()
139            }))
140            .with_child(
141                Label::new(user.github_login.clone(), style.label.clone())
142                    .contained()
143                    .with_style(theme.contact_finder.contact_username)
144                    .aligned()
145                    .left()
146                    .boxed(),
147            )
148            .with_child(
149                render_icon_button(button_style, icon_path)
150                    .aligned()
151                    .flex_float()
152                    .boxed(),
153            )
154            .contained()
155            .with_style(style.container)
156            .constrained()
157            .with_height(theme.contact_finder.row_height)
158            .boxed()
159    }
160}
161
162impl ContactFinder {
163    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
164        workspace.toggle_modal(cx, |workspace, cx| {
165            let finder = cx.add_view(|cx| Self::new(workspace.user_store().clone(), cx));
166            cx.subscribe(&finder, Self::on_event).detach();
167            finder
168        });
169    }
170
171    pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
172        let this = cx.weak_handle();
173        Self {
174            picker: cx.add_view(|cx| Picker::new(this, cx)),
175            potential_contacts: Arc::from([]),
176            user_store,
177            selected_index: 0,
178        }
179    }
180
181    fn on_event(
182        workspace: &mut Workspace,
183        _: ViewHandle<Self>,
184        event: &Event,
185        cx: &mut ViewContext<Workspace>,
186    ) {
187        match event {
188            Event::Dismissed => {
189                workspace.dismiss_modal(cx);
190            }
191        }
192    }
193}