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}