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}