diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index f5e724b626..998ce04636 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1491,6 +1491,7 @@ impl Client { pub async fn sign_out(self: &Arc, cx: &AsyncApp) { self.state.write().credentials = None; + self.cloud_client.clear_credentials(); self.disconnect(cx); if self.has_credentials(cx).await { diff --git a/crates/client/src/cloud/user_store.rs b/crates/client/src/cloud/user_store.rs index 025bf79b5e..da468ad618 100644 --- a/crates/client/src/cloud/user_store.rs +++ b/crates/client/src/cloud/user_store.rs @@ -7,35 +7,56 @@ use gpui::{Context, Task}; use util::{ResultExt as _, maybe}; pub struct CloudUserStore { - authenticated_user: Option, - _fetch_authenticated_user_task: Task<()>, + authenticated_user: Option>, + _maintain_authenticated_user_task: Task<()>, } impl CloudUserStore { pub fn new(cloud_client: Arc, cx: &mut Context) -> Self { Self { authenticated_user: None, - _fetch_authenticated_user_task: cx.spawn(async move |this, cx| { + _maintain_authenticated_user_task: cx.spawn(async move |this, cx| { maybe!(async move { loop { + let Some(this) = this.upgrade() else { + return anyhow::Ok(()); + }; + if cloud_client.has_credentials() { - break; + if let Some(response) = cloud_client + .get_authenticated_user() + .await + .context("failed to fetch authenticated user") + .log_err() + { + this.update(cx, |this, _cx| { + this.authenticated_user = Some(Arc::new(response.user)); + }) + .ok(); + } + } else { + this.update(cx, |this, _cx| { + this.authenticated_user = None; + }) + .ok(); } cx.background_executor() .timer(Duration::from_millis(100)) .await; } - - let response = cloud_client.get_authenticated_user().await?; - this.update(cx, |this, _cx| { - this.authenticated_user = Some(response.user); - }) }) .await - .context("failed to fetch authenticated user") .log_err(); }), } } + + pub fn is_authenticated(&self) -> bool { + self.authenticated_user.is_some() + } + + pub fn authenticated_user(&self) -> Option> { + self.authenticated_user.clone() + } } diff --git a/crates/cloud_api_client/src/cloud_api_client.rs b/crates/cloud_api_client/src/cloud_api_client.rs index b92136b02f..2d017cf2ee 100644 --- a/crates/cloud_api_client/src/cloud_api_client.rs +++ b/crates/cloud_api_client/src/cloud_api_client.rs @@ -35,6 +35,10 @@ impl CloudApiClient { }); } + pub fn clear_credentials(&self) { + *self.credentials.write() = None; + } + fn authorization_header(&self) -> Result { let guard = self.credentials.read(); let credentials = guard diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 17c4c85b6d..6e03b52ef8 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -20,7 +20,7 @@ use crate::application_menu::{ use auto_update::AutoUpdateStatus; use call::ActiveCall; -use client::{Client, UserStore, zed_urls}; +use client::{Client, CloudUserStore, UserStore, zed_urls}; use gpui::{ Action, AnyElement, App, Context, Corner, Element, Entity, Focusable, InteractiveElement, IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled, @@ -126,6 +126,7 @@ pub struct TitleBar { platform_titlebar: Entity, project: Entity, user_store: Entity, + cloud_user_store: Entity, client: Arc, workspace: WeakEntity, application_menu: Option>, @@ -179,24 +180,25 @@ impl Render for TitleBar { children.push(self.banner.clone().into_any_element()) } + let is_authenticated = self.cloud_user_store.read(cx).is_authenticated(); + let status = self.client.status(); + let status = &*status.borrow(); + + let show_sign_in = !is_authenticated || !matches!(status, client::Status::Connected { .. }); + children.push( h_flex() .gap_1() .pr_1() .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .children(self.render_call_controls(window, cx)) - .map(|el| { - let status = self.client.status(); - let status = &*status.borrow(); - if matches!(status, client::Status::Connected { .. }) { - el.child(self.render_user_menu_button(cx)) - } else { - el.children(self.render_connection_status(status, cx)) - .when(TitleBarSettings::get_global(cx).show_sign_in, |el| { - el.child(self.render_sign_in_button(cx)) - }) - .child(self.render_user_menu_button(cx)) - } + .children(self.render_connection_status(status, cx)) + .when( + show_sign_in && TitleBarSettings::get_global(cx).show_sign_in, + |el| el.child(self.render_sign_in_button(cx)), + ) + .when(is_authenticated, |parent| { + parent.child(self.render_user_menu_button(cx)) }) .into_any_element(), ); @@ -246,6 +248,7 @@ impl TitleBar { ) -> Self { let project = workspace.project().clone(); let user_store = workspace.app_state().user_store.clone(); + let cloud_user_store = workspace.app_state().cloud_user_store.clone(); let client = workspace.app_state().client.clone(); let active_call = ActiveCall::global(cx); @@ -293,6 +296,7 @@ impl TitleBar { workspace: workspace.weak_handle(), project, user_store, + cloud_user_store, client, _subscriptions: subscriptions, banner, @@ -628,15 +632,15 @@ impl TitleBar { } pub fn render_user_menu_button(&mut self, cx: &mut Context) -> impl Element { - let user_store = self.user_store.read(cx); - if let Some(user) = user_store.current_user() { + let cloud_user_store = self.cloud_user_store.read(cx); + if let Some(user) = cloud_user_store.authenticated_user() { let has_subscription_period = self.user_store.read(cx).subscription_period().is_some(); let plan = self.user_store.read(cx).current_plan().filter(|_| { // Since the user might be on the legacy free plan we filter based on whether we have a subscription period. has_subscription_period }); - let user_avatar = user.avatar_uri.clone(); + let user_avatar = user.avatar_url.clone(); let free_chip_bg = cx .theme() .colors()