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