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}