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