Use the user from the CloudUserStore
to drive the user menu (#35375)
This PR updates the user menu in the title bar to base the "signed in" state on the user in the `CloudUserStore` rather than the `UserStore`. This makes it possible to be signed-in—at least, as far as the user menu is concerned—even when disconnected from Collab. Release Notes: - N/A
This commit is contained in:
parent
296bb66b65
commit
fbc784d323
4 changed files with 56 additions and 26 deletions
|
@ -1491,6 +1491,7 @@ impl Client {
|
||||||
|
|
||||||
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncApp) {
|
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncApp) {
|
||||||
self.state.write().credentials = None;
|
self.state.write().credentials = None;
|
||||||
|
self.cloud_client.clear_credentials();
|
||||||
self.disconnect(cx);
|
self.disconnect(cx);
|
||||||
|
|
||||||
if self.has_credentials(cx).await {
|
if self.has_credentials(cx).await {
|
||||||
|
|
|
@ -7,35 +7,56 @@ use gpui::{Context, Task};
|
||||||
use util::{ResultExt as _, maybe};
|
use util::{ResultExt as _, maybe};
|
||||||
|
|
||||||
pub struct CloudUserStore {
|
pub struct CloudUserStore {
|
||||||
authenticated_user: Option<AuthenticatedUser>,
|
authenticated_user: Option<Arc<AuthenticatedUser>>,
|
||||||
_fetch_authenticated_user_task: Task<()>,
|
_maintain_authenticated_user_task: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CloudUserStore {
|
impl CloudUserStore {
|
||||||
pub fn new(cloud_client: Arc<CloudApiClient>, cx: &mut Context<Self>) -> Self {
|
pub fn new(cloud_client: Arc<CloudApiClient>, cx: &mut Context<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
authenticated_user: None,
|
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 {
|
maybe!(async move {
|
||||||
loop {
|
loop {
|
||||||
|
let Some(this) = this.upgrade() else {
|
||||||
|
return anyhow::Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
if cloud_client.has_credentials() {
|
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()
|
cx.background_executor()
|
||||||
.timer(Duration::from_millis(100))
|
.timer(Duration::from_millis(100))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = cloud_client.get_authenticated_user().await?;
|
|
||||||
this.update(cx, |this, _cx| {
|
|
||||||
this.authenticated_user = Some(response.user);
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.context("failed to fetch authenticated user")
|
|
||||||
.log_err();
|
.log_err();
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_authenticated(&self) -> bool {
|
||||||
|
self.authenticated_user.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authenticated_user(&self) -> Option<Arc<AuthenticatedUser>> {
|
||||||
|
self.authenticated_user.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,10 @@ impl CloudApiClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_credentials(&self) {
|
||||||
|
*self.credentials.write() = None;
|
||||||
|
}
|
||||||
|
|
||||||
fn authorization_header(&self) -> Result<String> {
|
fn authorization_header(&self) -> Result<String> {
|
||||||
let guard = self.credentials.read();
|
let guard = self.credentials.read();
|
||||||
let credentials = guard
|
let credentials = guard
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::application_menu::{
|
||||||
|
|
||||||
use auto_update::AutoUpdateStatus;
|
use auto_update::AutoUpdateStatus;
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{Client, UserStore, zed_urls};
|
use client::{Client, CloudUserStore, UserStore, zed_urls};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, App, Context, Corner, Element, Entity, Focusable, InteractiveElement,
|
Action, AnyElement, App, Context, Corner, Element, Entity, Focusable, InteractiveElement,
|
||||||
IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled,
|
IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled,
|
||||||
|
@ -126,6 +126,7 @@ pub struct TitleBar {
|
||||||
platform_titlebar: Entity<PlatformTitleBar>,
|
platform_titlebar: Entity<PlatformTitleBar>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
user_store: Entity<UserStore>,
|
user_store: Entity<UserStore>,
|
||||||
|
cloud_user_store: Entity<CloudUserStore>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
application_menu: Option<Entity<ApplicationMenu>>,
|
application_menu: Option<Entity<ApplicationMenu>>,
|
||||||
|
@ -179,24 +180,25 @@ impl Render for TitleBar {
|
||||||
children.push(self.banner.clone().into_any_element())
|
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(
|
children.push(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.pr_1()
|
.pr_1()
|
||||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||||
.children(self.render_call_controls(window, cx))
|
.children(self.render_call_controls(window, cx))
|
||||||
.map(|el| {
|
.children(self.render_connection_status(status, cx))
|
||||||
let status = self.client.status();
|
.when(
|
||||||
let status = &*status.borrow();
|
show_sign_in && TitleBarSettings::get_global(cx).show_sign_in,
|
||||||
if matches!(status, client::Status::Connected { .. }) {
|
|el| el.child(self.render_sign_in_button(cx)),
|
||||||
el.child(self.render_user_menu_button(cx))
|
)
|
||||||
} else {
|
.when(is_authenticated, |parent| {
|
||||||
el.children(self.render_connection_status(status, cx))
|
parent.child(self.render_user_menu_button(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))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
);
|
);
|
||||||
|
@ -246,6 +248,7 @@ impl TitleBar {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let user_store = workspace.app_state().user_store.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 client = workspace.app_state().client.clone();
|
||||||
let active_call = ActiveCall::global(cx);
|
let active_call = ActiveCall::global(cx);
|
||||||
|
|
||||||
|
@ -293,6 +296,7 @@ impl TitleBar {
|
||||||
workspace: workspace.weak_handle(),
|
workspace: workspace.weak_handle(),
|
||||||
project,
|
project,
|
||||||
user_store,
|
user_store,
|
||||||
|
cloud_user_store,
|
||||||
client,
|
client,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
banner,
|
banner,
|
||||||
|
@ -628,15 +632,15 @@ impl TitleBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_user_menu_button(&mut self, cx: &mut Context<Self>) -> impl Element {
|
pub fn render_user_menu_button(&mut self, cx: &mut Context<Self>) -> impl Element {
|
||||||
let user_store = self.user_store.read(cx);
|
let cloud_user_store = self.cloud_user_store.read(cx);
|
||||||
if let Some(user) = user_store.current_user() {
|
if let Some(user) = cloud_user_store.authenticated_user() {
|
||||||
let has_subscription_period = self.user_store.read(cx).subscription_period().is_some();
|
let has_subscription_period = self.user_store.read(cx).subscription_period().is_some();
|
||||||
let plan = self.user_store.read(cx).current_plan().filter(|_| {
|
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.
|
// Since the user might be on the legacy free plan we filter based on whether we have a subscription period.
|
||||||
has_subscription_period
|
has_subscription_period
|
||||||
});
|
});
|
||||||
|
|
||||||
let user_avatar = user.avatar_uri.clone();
|
let user_avatar = user.avatar_url.clone();
|
||||||
let free_chip_bg = cx
|
let free_chip_bg = cx
|
||||||
.theme()
|
.theme()
|
||||||
.colors()
|
.colors()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue