collab_ui: Show channel list while reconnecting (#37107)

Marshall Bowers created

This PR makes it so the channel list will still be shown while
reconnecting to Collab instead of showing the signed-out state.

In order to model the transitional states that occur while reconnecting,
we needed to introduce a new `Status::Reauthenticated` state that we go
through when signing in as part of a reconnect. This is because we
cannot tell from `Status::Authenticated` alone if we're authenticating
for the first time or reauthenticating.

Release Notes:

- N/A

Change summary

crates/client/src/client.rs          | 32 +++++++++++++++++++++++++++--
crates/client/src/user.rs            |  4 ++
crates/collab_ui/src/collab_panel.rs |  2 
crates/workspace/src/workspace.rs    |  3 +
4 files changed, 35 insertions(+), 6 deletions(-)

Detailed changes

crates/client/src/client.rs 🔗

@@ -287,6 +287,7 @@ pub enum Status {
     },
     ConnectionLost,
     Reauthenticating,
+    Reauthenticated,
     Reconnecting,
     ReconnectionError {
         next_reconnection: Instant,
@@ -298,6 +299,21 @@ impl Status {
         matches!(self, Self::Connected { .. })
     }
 
+    pub fn was_connected(&self) -> bool {
+        matches!(
+            self,
+            Self::ConnectionLost
+                | Self::Reauthenticating
+                | Self::Reauthenticated
+                | Self::Reconnecting
+        )
+    }
+
+    /// Returns whether the client is currently connected or was connected at some point.
+    pub fn is_or_was_connected(&self) -> bool {
+        self.is_connected() || self.was_connected()
+    }
+
     pub fn is_signing_in(&self) -> bool {
         matches!(
             self,
@@ -857,11 +873,13 @@ impl Client {
         try_provider: bool,
         cx: &AsyncApp,
     ) -> Result<Credentials> {
-        if self.status().borrow().is_signed_out() {
+        let is_reauthenticating = if self.status().borrow().is_signed_out() {
             self.set_status(Status::Authenticating, cx);
+            false
         } else {
             self.set_status(Status::Reauthenticating, cx);
-        }
+            true
+        };
 
         let mut credentials = None;
 
@@ -919,7 +937,14 @@ impl Client {
         self.cloud_client
             .set_credentials(credentials.user_id as u32, credentials.access_token.clone());
         self.state.write().credentials = Some(credentials.clone());
-        self.set_status(Status::Authenticated, cx);
+        self.set_status(
+            if is_reauthenticating {
+                Status::Reauthenticated
+            } else {
+                Status::Authenticated
+            },
+            cx,
+        );
 
         Ok(credentials)
     }
@@ -1034,6 +1059,7 @@ impl Client {
             | Status::Authenticating
             | Status::AuthenticationError
             | Status::Reauthenticating
+            | Status::Reauthenticated
             | Status::ReconnectionError { .. } => false,
             Status::Connected { .. } | Status::Connecting | Status::Reconnecting => {
                 return ConnectionResult::Result(Ok(()));

crates/client/src/user.rs 🔗

@@ -216,7 +216,9 @@ impl UserStore {
                         return Ok(());
                     };
                     match status {
-                        Status::Authenticated | Status::Connected { .. } => {
+                        Status::Authenticated
+                        | Status::Reauthenticated
+                        | Status::Connected { .. } => {
                             if let Some(user_id) = client.user_id() {
                                 let response = client
                                     .cloud_client()

crates/collab_ui/src/collab_panel.rs 🔗

@@ -3047,7 +3047,7 @@ impl Render for CollabPanel {
             .on_action(cx.listener(CollabPanel::move_channel_down))
             .track_focus(&self.focus_handle)
             .size_full()
-            .child(if !self.client.status().borrow().is_connected() {
+            .child(if !self.client.status().borrow().is_or_was_connected() {
                 self.render_signed_out(cx)
             } else {
                 self.render_signed_in(window, cx)

crates/workspace/src/workspace.rs 🔗

@@ -6890,7 +6890,8 @@ async fn join_channel_internal(
             | Status::Authenticating
             | Status::Authenticated
             | Status::Reconnecting
-            | Status::Reauthenticating => continue,
+            | Status::Reauthenticating
+            | Status::Reauthenticated => continue,
             Status::Connected { .. } => break 'outer,
             Status::SignedOut | Status::AuthenticationError => {
                 return Err(ErrorCode::SignedOut.into());