diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index a885b3b1ad..3f52b2b6d9 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -20,6 +20,7 @@ pub struct User { #[derive(Debug)] pub struct Contact { pub user: Arc, + pub online: bool, pub projects: Vec, } @@ -109,6 +110,14 @@ impl UserStore { } Status::SignedOut => { current_user_tx.send(None).await.ok(); + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, _| this.clear_contacts()).await; + } + } + Status::ConnectionLost => { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, _| this.clear_contacts()).await; + } } _ => {} } @@ -499,7 +508,11 @@ impl Contact { guests, }); } - Ok(Self { user, projects }) + Ok(Self { + user, + online: contact.online, + projects, + }) } pub fn non_empty_projects(&self) -> impl Iterator { diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index ea71ab5f00..51aec065cd 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -5000,13 +5000,22 @@ mod tests { .await; deterministic.run_until_parked(); client_a.user_store.read_with(cx_a, |store, _| { - assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])]) + assert_eq!( + contacts(store), + [("user_b", true, vec![]), ("user_c", true, vec![])] + ) }); client_b.user_store.read_with(cx_b, |store, _| { - assert_eq!(contacts(store), [("user_a", vec![]), ("user_c", vec![])]) + assert_eq!( + contacts(store), + [("user_a", true, vec![]), ("user_c", true, vec![])] + ) }); client_c.user_store.read_with(cx_c, |store, _| { - assert_eq!(contacts(store), [("user_a", vec![]), ("user_b", vec![])]) + assert_eq!( + contacts(store), + [("user_a", true, vec![]), ("user_b", true, vec![])] + ) }); // Share a worktree as client A. @@ -5033,18 +5042,27 @@ mod tests { deterministic.run_until_parked(); client_a.user_store.read_with(cx_a, |store, _| { - assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])]) + assert_eq!( + contacts(store), + [("user_b", true, vec![]), ("user_c", true, vec![])] + ) }); client_b.user_store.read_with(cx_b, |store, _| { assert_eq!( contacts(store), - [("user_a", vec![("a", false, vec![])]), ("user_c", vec![])] + [ + ("user_a", true, vec![("a", false, vec![])]), + ("user_c", true, vec![]) + ] ) }); client_c.user_store.read_with(cx_c, |store, _| { assert_eq!( contacts(store), - [("user_a", vec![("a", false, vec![])]), ("user_b", vec![])] + [ + ("user_a", true, vec![("a", false, vec![])]), + ("user_b", true, vec![]) + ] ) }); @@ -5057,18 +5075,27 @@ mod tests { .unwrap(); deterministic.run_until_parked(); client_a.user_store.read_with(cx_a, |store, _| { - assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])]) + assert_eq!( + contacts(store), + [("user_b", true, vec![]), ("user_c", true, vec![])] + ) }); client_b.user_store.read_with(cx_b, |store, _| { assert_eq!( contacts(store), - [("user_a", vec![("a", true, vec![])]), ("user_c", vec![])] + [ + ("user_a", true, vec![("a", true, vec![])]), + ("user_c", true, vec![]) + ] ) }); client_c.user_store.read_with(cx_c, |store, _| { assert_eq!( contacts(store), - [("user_a", vec![("a", true, vec![])]), ("user_b", vec![])] + [ + ("user_a", true, vec![("a", true, vec![])]), + ("user_b", true, vec![]) + ] ) }); @@ -5084,14 +5111,17 @@ mod tests { .unwrap(); deterministic.run_until_parked(); client_a.user_store.read_with(cx_a, |store, _| { - assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])]) + assert_eq!( + contacts(store), + [("user_b", true, vec![]), ("user_c", true, vec![])] + ) }); client_b.user_store.read_with(cx_b, |store, _| { assert_eq!( contacts(store), [ - ("user_a", vec![("a", true, vec!["user_b"])]), - ("user_c", vec![]) + ("user_a", true, vec![("a", true, vec!["user_b"])]), + ("user_c", true, vec![]) ] ) }); @@ -5099,8 +5129,8 @@ mod tests { assert_eq!( contacts(store), [ - ("user_a", vec![("a", true, vec!["user_b"])]), - ("user_b", vec![]) + ("user_a", true, vec![("a", true, vec!["user_b"])]), + ("user_b", true, vec![]) ] ) }); @@ -5114,16 +5144,70 @@ mod tests { cx_a.update(move |_| drop(project_a)); deterministic.run_until_parked(); client_a.user_store.read_with(cx_a, |store, _| { - assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])]) + assert_eq!( + contacts(store), + [("user_b", true, vec![]), ("user_c", true, vec![])] + ) }); client_b.user_store.read_with(cx_b, |store, _| { - assert_eq!(contacts(store), [("user_a", vec![]), ("user_c", vec![])]) + assert_eq!( + contacts(store), + [("user_a", true, vec![]), ("user_c", true, vec![])] + ) }); client_c.user_store.read_with(cx_c, |store, _| { - assert_eq!(contacts(store), [("user_a", vec![]), ("user_b", vec![])]) + assert_eq!( + contacts(store), + [("user_a", true, vec![]), ("user_b", true, vec![])] + ) }); - fn contacts(user_store: &UserStore) -> Vec<(&str, Vec<(&str, bool, Vec<&str>)>)> { + server.disconnect_client(client_c.current_user_id(cx_c)); + server.forbid_connections(); + deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); + + client_a.user_store.read_with(cx_a, |store, _| { + assert_eq!( + contacts(store), + [("user_b", true, vec![]), ("user_c", false, vec![])] + ) + }); + client_b.user_store.read_with(cx_b, |store, _| { + assert_eq!( + contacts(store), + [("user_a", true, vec![]), ("user_c", false, vec![])] + ) + }); + client_c + .user_store + .read_with(cx_c, |store, _| assert_eq!(contacts(store), [])); + + server.allow_connections(); + client_c + .authenticate_and_connect(false, &cx_c.to_async()) + .await + .unwrap(); + deterministic.run_until_parked(); + client_a.user_store.read_with(cx_a, |store, _| { + assert_eq!( + contacts(store), + [("user_b", true, vec![]), ("user_c", true, vec![])] + ) + }); + client_b.user_store.read_with(cx_b, |store, _| { + assert_eq!( + contacts(store), + [("user_a", true, vec![]), ("user_c", true, vec![])] + ) + }); + client_c.user_store.read_with(cx_c, |store, _| { + assert_eq!( + contacts(store), + [("user_a", true, vec![]), ("user_b", true, vec![])] + ) + }); + + fn contacts(user_store: &UserStore) -> Vec<(&str, bool, Vec<(&str, bool, Vec<&str>)>)> { user_store .contacts() .iter() @@ -5139,7 +5223,11 @@ mod tests { ) }) .collect(); - (contact.user.github_login.as_str(), worktrees) + ( + contact.user.github_login.as_str(), + contact.online, + worktrees, + ) }) .collect() } diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 4c354c5644..48386749ef 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -638,10 +638,20 @@ impl ContactsPanel { executor.clone(), )); if !matches.is_empty() { - self.entries.push(ContactEntry::Header("Contacts")); + let (online_contacts, offline_contacts) = matches + .iter() + .partition::, _>(|mat| contacts[mat.candidate_id].online); + + self.entries.push(ContactEntry::Header("Online")); self.entries.extend( - matches - .iter() + online_contacts + .into_iter() + .map(|mat| ContactEntry::Contact(contacts[mat.candidate_id].clone())), + ); + self.entries.push(ContactEntry::Header("Offline")); + self.entries.extend( + offline_contacts + .into_iter() .map(|mat| ContactEntry::Contact(contacts[mat.candidate_id].clone())), ); }