contact_finder.rs

  1use client::{ContactRequestStatus, User, UserStore};
  2use gpui::{
  3    div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle,
  4    FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View,
  5    ViewContext, VisualContext, WeakView,
  6};
  7use picker::{Picker, PickerDelegate};
  8use std::sync::Arc;
  9use theme::ActiveTheme as _;
 10use ui::{prelude::*, Avatar};
 11use util::{ResultExt as _, TryFutureExt};
 12use workspace::ModalView;
 13
 14pub fn init(cx: &mut AppContext) {
 15    //Picker::<ContactFinderDelegate>::init(cx);
 16    //cx.add_action(ContactFinder::dismiss)
 17}
 18
 19pub struct ContactFinder {
 20    picker: View<Picker<ContactFinderDelegate>>,
 21    has_focus: bool,
 22}
 23
 24impl ContactFinder {
 25    pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
 26        let delegate = ContactFinderDelegate {
 27            parent: cx.view().downgrade(),
 28            user_store,
 29            potential_contacts: Arc::from([]),
 30            selected_index: 0,
 31        };
 32        let picker = cx.build_view(|cx| Picker::new(delegate, cx));
 33
 34        Self {
 35            picker,
 36            has_focus: false,
 37        }
 38    }
 39
 40    pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
 41        self.picker.update(cx, |picker, cx| {
 42            // todo!()
 43            // picker.set_query(query, cx);
 44        });
 45    }
 46}
 47
 48impl Render for ContactFinder {
 49    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
 50        fn render_mode_button(text: &'static str) -> AnyElement {
 51            Label::new(text).into_any_element()
 52        }
 53
 54        v_stack()
 55            .child(
 56                v_stack()
 57                    .child(Label::new("Contacts"))
 58                    .child(h_stack().children([render_mode_button("Invite new contacts")]))
 59                    .bg(cx.theme().colors().element_background),
 60            )
 61            .child(self.picker.clone())
 62            .w(rems(34.))
 63    }
 64
 65    // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
 66    //     self.has_focus = true;
 67    //     if cx.is_self_focused() {
 68    //         cx.focus(&self.picker)
 69    //     }
 70    // }
 71
 72    // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
 73    //     self.has_focus = false;
 74    // }
 75
 76    type Element = Div;
 77}
 78
 79// impl Modal for ContactFinder {
 80//     fn has_focus(&self) -> bool {
 81//         self.has_focus
 82//     }
 83
 84//     fn dismiss_on_event(event: &Self::Event) -> bool {
 85//         match event {
 86//             PickerEvent::Dismiss => true,
 87//         }
 88//     }
 89// }
 90
 91pub struct ContactFinderDelegate {
 92    parent: WeakView<ContactFinder>,
 93    potential_contacts: Arc<[Arc<User>]>,
 94    user_store: Model<UserStore>,
 95    selected_index: usize,
 96}
 97
 98impl EventEmitter<DismissEvent> for ContactFinder {}
 99impl ModalView for ContactFinder {}
100
101impl FocusableView for ContactFinder {
102    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
103        self.picker.focus_handle(cx)
104    }
105}
106
107impl PickerDelegate for ContactFinderDelegate {
108    type ListItem = Div;
109    fn match_count(&self) -> usize {
110        self.potential_contacts.len()
111    }
112
113    fn selected_index(&self) -> usize {
114        self.selected_index
115    }
116
117    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
118        self.selected_index = ix;
119    }
120
121    fn placeholder_text(&self) -> Arc<str> {
122        "Search collaborator by username...".into()
123    }
124
125    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
126        let search_users = self
127            .user_store
128            .update(cx, |store, cx| store.fuzzy_search_users(query, cx));
129
130        cx.spawn(|picker, mut cx| async move {
131            async {
132                let potential_contacts = search_users.await?;
133                picker.update(&mut cx, |picker, cx| {
134                    picker.delegate.potential_contacts = potential_contacts.into();
135                    cx.notify();
136                })?;
137                anyhow::Ok(())
138            }
139            .log_err()
140            .await;
141        })
142    }
143
144    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
145        if let Some(user) = self.potential_contacts.get(self.selected_index) {
146            let user_store = self.user_store.read(cx);
147            match user_store.contact_request_status(user) {
148                ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
149                    self.user_store
150                        .update(cx, |store, cx| store.request_contact(user.id, cx))
151                        .detach();
152                }
153                ContactRequestStatus::RequestSent => {
154                    self.user_store
155                        .update(cx, |store, cx| store.remove_contact(user.id, cx))
156                        .detach();
157                }
158                _ => {}
159            }
160        }
161    }
162
163    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
164        //cx.emit(PickerEvent::Dismiss);
165        self.parent
166            .update(cx, |_, cx| cx.emit(DismissEvent))
167            .log_err();
168    }
169
170    fn render_match(
171        &self,
172        ix: usize,
173        selected: bool,
174        cx: &mut ViewContext<Picker<Self>>,
175    ) -> Option<Self::ListItem> {
176        let user = &self.potential_contacts[ix];
177        let request_status = self.user_store.read(cx).contact_request_status(user);
178
179        let icon_path = match request_status {
180            ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
181                Some("icons/check.svg")
182            }
183            ContactRequestStatus::RequestSent => Some("icons/x.svg"),
184            ContactRequestStatus::RequestAccepted => None,
185        };
186        Some(
187            div()
188                .flex_1()
189                .justify_between()
190                .child(Avatar::new(user.avatar_uri.clone()))
191                .child(Label::new(user.github_login.clone()))
192                .children(icon_path.map(|icon_path| svg().path(icon_path))),
193        )
194        // Flex::row()
195        //     .with_children(user.avatar.clone().map(|avatar| {
196        //         Image::from_data(avatar)
197        //             .with_style(theme.contact_avatar)
198        //             .aligned()
199        //             .left()
200        //     }))
201        //     .with_child(
202        //         Label::new(user.github_login.clone(), style.label.clone())
203        //             .contained()
204        //             .with_style(theme.contact_username)
205        //             .aligned()
206        //             .left(),
207        //     )
208        //     .with_children(icon_path.map(|icon_path| {
209        //         Svg::new(icon_path)
210        //             .with_color(button_style.color)
211        //             .constrained()
212        //             .with_width(button_style.icon_width)
213        //             .aligned()
214        //             .contained()
215        //             .with_style(button_style.container)
216        //             .constrained()
217        //             .with_width(button_style.button_width)
218        //             .with_height(button_style.button_width)
219        //             .aligned()
220        //             .flex_float()
221        //     }))
222        //     .contained()
223        //     .with_style(style.container)
224        //     .constrained()
225        //     .with_height(tabbed_modal.row_height)
226        //     .into_any()
227    }
228}