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