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