contact_finder.rs

  1use client::{ContactRequestStatus, User, UserStore};
  2use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
  3use picker::{Picker, PickerDelegate, PickerEvent};
  4use std::sync::Arc;
  5use util::TryFutureExt;
  6
  7pub fn init(cx: &mut AppContext) {
  8    Picker::<ContactFinderDelegate>::init(cx);
  9}
 10
 11pub type ContactFinder = Picker<ContactFinderDelegate>;
 12
 13pub fn build_contact_finder(
 14    user_store: ModelHandle<UserStore>,
 15    cx: &mut ViewContext<ContactFinder>,
 16) -> ContactFinder {
 17    Picker::new(
 18        ContactFinderDelegate {
 19            user_store,
 20            potential_contacts: Arc::from([]),
 21            selected_index: 0,
 22        },
 23        cx,
 24    )
 25    .with_theme(|theme| theme.contact_finder.picker.clone())
 26}
 27
 28pub struct ContactFinderDelegate {
 29    potential_contacts: Arc<[Arc<User>]>,
 30    user_store: ModelHandle<UserStore>,
 31    selected_index: usize,
 32}
 33
 34impl PickerDelegate for ContactFinderDelegate {
 35    fn placeholder_text(&self) -> Arc<str> {
 36        "Search collaborator by username...".into()
 37    }
 38
 39    fn match_count(&self) -> usize {
 40        self.potential_contacts.len()
 41    }
 42
 43    fn selected_index(&self) -> usize {
 44        self.selected_index
 45    }
 46
 47    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
 48        self.selected_index = ix;
 49    }
 50
 51    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
 52        let search_users = self
 53            .user_store
 54            .update(cx, |store, cx| store.fuzzy_search_users(query, cx));
 55
 56        cx.spawn(|picker, mut cx| async move {
 57            async {
 58                let potential_contacts = search_users.await?;
 59                picker.update(&mut cx, |picker, cx| {
 60                    picker.delegate_mut().potential_contacts = potential_contacts.into();
 61                    cx.notify();
 62                })?;
 63                anyhow::Ok(())
 64            }
 65            .log_err()
 66            .await;
 67        })
 68    }
 69
 70    fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
 71        if let Some(user) = self.potential_contacts.get(self.selected_index) {
 72            let user_store = self.user_store.read(cx);
 73            match user_store.contact_request_status(user) {
 74                ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
 75                    self.user_store
 76                        .update(cx, |store, cx| store.request_contact(user.id, cx))
 77                        .detach();
 78                }
 79                ContactRequestStatus::RequestSent => {
 80                    self.user_store
 81                        .update(cx, |store, cx| store.remove_contact(user.id, cx))
 82                        .detach();
 83                }
 84                _ => {}
 85            }
 86        }
 87    }
 88
 89    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
 90        cx.emit(PickerEvent::Dismiss);
 91    }
 92
 93    fn render_match(
 94        &self,
 95        ix: usize,
 96        mouse_state: &mut MouseState,
 97        selected: bool,
 98        cx: &gpui::AppContext,
 99    ) -> AnyElement<Picker<Self>> {
100        let theme = &theme::current(cx);
101        let user = &self.potential_contacts[ix];
102        let request_status = self.user_store.read(cx).contact_request_status(user);
103
104        let icon_path = match request_status {
105            ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
106                Some("icons/check_8.svg")
107            }
108            ContactRequestStatus::RequestSent => Some("icons/x_mark_8.svg"),
109            ContactRequestStatus::RequestAccepted => None,
110        };
111        let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
112            &theme.contact_finder.disabled_contact_button
113        } else {
114            &theme.contact_finder.contact_button
115        };
116        let style = theme
117            .contact_finder
118            .picker
119            .item
120            .style_for(mouse_state, selected);
121        Flex::row()
122            .with_children(user.avatar.clone().map(|avatar| {
123                Image::from_data(avatar)
124                    .with_style(theme.contact_finder.contact_avatar)
125                    .aligned()
126                    .left()
127            }))
128            .with_child(
129                Label::new(user.github_login.clone(), style.label.clone())
130                    .contained()
131                    .with_style(theme.contact_finder.contact_username)
132                    .aligned()
133                    .left(),
134            )
135            .with_children(icon_path.map(|icon_path| {
136                Svg::new(icon_path)
137                    .with_color(button_style.color)
138                    .constrained()
139                    .with_width(button_style.icon_width)
140                    .aligned()
141                    .contained()
142                    .with_style(button_style.container)
143                    .constrained()
144                    .with_width(button_style.button_width)
145                    .with_height(button_style.button_width)
146                    .aligned()
147                    .flex_float()
148            }))
149            .contained()
150            .with_style(style.container)
151            .constrained()
152            .with_height(theme.contact_finder.row_height)
153            .into_any()
154    }
155}