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