@@ -595,7 +595,16 @@ impl Database {
.await
}
- pub async fn remove_contact(&self, requester_id: UserId, responder_id: UserId) -> Result<()> {
+ /// Returns a bool indicating whether the removed contact had originally accepted or not
+ ///
+ /// Deletes the contact identified by the requester and responder ids, and then returns
+ /// whether the deleted contact had originally accepted or was a pending contact request.
+ ///
+ /// # Arguments
+ ///
+ /// * `requester_id` - The user that initiates this request
+ /// * `responder_id` - The user that will be removed
+ pub async fn remove_contact(&self, requester_id: UserId, responder_id: UserId) -> Result<bool> {
self.transaction(|tx| async move {
let (id_a, id_b) = if responder_id < requester_id {
(responder_id, requester_id)
@@ -603,20 +612,18 @@ impl Database {
(requester_id, responder_id)
};
- let result = contact::Entity::delete_many()
+ let contact = contact::Entity::find()
.filter(
contact::Column::UserIdA
.eq(id_a)
.and(contact::Column::UserIdB.eq(id_b)),
)
- .exec(&*tx)
- .await?;
+ .one(&*tx)
+ .await?
+ .ok_or_else(|| anyhow!("no such contact"))?;
- if result.rows_affected == 1 {
- Ok(())
- } else {
- Err(anyhow!("no such contact"))?
- }
+ contact::Entity::delete_by_id(contact.id).exec(&*tx).await?;
+ Ok(contact.accepted)
})
.await
}
@@ -5291,6 +5291,27 @@ async fn test_contacts(
[("user_b".to_string(), "online", "free")]
);
+ // Test removing a contact
+ client_b
+ .user_store
+ .update(cx_b, |store, cx| {
+ store.remove_contact(client_c.user_id().unwrap(), cx)
+ })
+ .await
+ .unwrap();
+ deterministic.run_until_parked();
+ assert_eq!(
+ contacts(&client_b, cx_b),
+ [
+ ("user_a".to_string(), "offline", "free"),
+ ("user_d".to_string(), "online", "free")
+ ]
+ );
+ assert_eq!(
+ contacts(&client_c, cx_c),
+ [("user_a".to_string(), "offline", "free"),]
+ );
+
fn contacts(
client: &TestClient,
cx: &TestAppContext,
@@ -1,22 +1,22 @@
-use std::{mem, sync::Arc};
-
use crate::contacts_popover;
use call::ActiveCall;
use client::{proto::PeerId, Contact, User, UserStore};
use editor::{Cancel, Editor};
+use futures::StreamExt;
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
impl_actions, impl_internal_actions,
keymap_matcher::KeymapContext,
- AppContext, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext,
- Subscription, View, ViewContext, ViewHandle,
+ AppContext, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, PromptLevel,
+ RenderContext, Subscription, View, ViewContext, ViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::Project;
use serde::Deserialize;
use settings::Settings;
+use std::{mem, sync::Arc};
use theme::IconButton;
use util::ResultExt;
use workspace::{JoinProject, OpenSharedScreen};
@@ -299,9 +299,19 @@ impl ContactList {
}
fn remove_contact(&mut self, request: &RemoveContact, cx: &mut ViewContext<Self>) {
- self.user_store
- .update(cx, |store, cx| store.remove_contact(request.0, cx))
- .detach();
+ let user_id = request.0;
+ let user_store = self.user_store.clone();
+ let prompt_message = "Are you sure you want to remove this contact?";
+ let mut answer = cx.prompt(PromptLevel::Warning, prompt_message, &["Remove", "Cancel"]);
+ cx.spawn(|_, mut cx| async move {
+ if answer.next().await == Some(0) {
+ user_store
+ .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
+ .await
+ .unwrap();
+ }
+ })
+ .detach();
}
fn respond_to_contact_request(
@@ -1051,7 +1061,7 @@ impl ContactList {
let user_id = contact.user.id;
let initial_project = project.clone();
let mut element =
- MouseEventHandler::<Contact>::new(contact.user.id as usize, cx, |_, _| {
+ MouseEventHandler::<Contact>::new(contact.user.id as usize, cx, |_, cx| {
Flex::row()
.with_children(contact.user.avatar.clone().map(|avatar| {
let status_badge = if contact.online {
@@ -1093,6 +1103,27 @@ impl ContactList {
.flex(1., true)
.boxed(),
)
+ .with_child(
+ MouseEventHandler::<Cancel>::new(
+ contact.user.id as usize,
+ cx,
+ |mouse_state, _| {
+ let button_style =
+ theme.contact_button.style_for(mouse_state, false);
+ render_icon_button(button_style, "icons/x_mark_8.svg")
+ .aligned()
+ .flex_float()
+ .boxed()
+ },
+ )
+ .with_padding(Padding::uniform(2.))
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, cx| {
+ cx.dispatch_action(RemoveContact(user_id))
+ })
+ .flex_float()
+ .boxed(),
+ )
.with_children(if calling {
Some(
Label::new("Calling".to_string(), theme.calling_indicator.text.clone())