feat: sharpen chat experiences (#9)

* feat: add global account and refactor chat registry

* chore: improve last seen

* chore: reduce string alloc

* wip: refactor room

* chore: fix edit profile panel

* chore: refactor open window in main

* chore: refactor sidebar

* chore: refactor room
This commit is contained in:
reya
2025-02-23 08:29:05 +07:00
committed by GitHub
parent cfa628a8a6
commit bbc778d5ca
23 changed files with 1167 additions and 1150 deletions
+95 -84
View File
@@ -1,4 +1,4 @@
use common::profile::NostrProfile;
use account::registry::Account;
use gpui::{
actions, div, img, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis,
Context, Entity, InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, Styled,
@@ -38,21 +38,22 @@ impl AddPanel {
}
}
// Dock actions
impl_internal_actions!(dock, [AddPanel]);
// Account actions
actions!(account, [Logout]);
pub fn init(account: NostrProfile, window: &mut Window, cx: &mut App) -> Entity<AppView> {
AppView::new(account, window, cx)
pub fn init(window: &mut Window, cx: &mut App) -> Entity<AppView> {
AppView::new(window, cx)
}
pub struct AppView {
account: NostrProfile,
relays: Entity<Option<Vec<String>>>,
dock: Entity<DockArea>,
}
impl AppView {
pub fn new(account: NostrProfile, window: &mut Window, cx: &mut App) -> Entity<Self> {
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
// Initialize dock layout
let dock = cx.new(|cx| DockArea::new(window, cx));
let weak_dock = dock.downgrade();
@@ -83,76 +84,74 @@ impl AppView {
});
cx.new(|cx| {
let public_key = account.public_key();
let relays = cx.new(|_| None);
let async_relays = relays.downgrade();
let this = Self { relays, dock };
// Check user's messaging relays and determine user is ready for NIP17 or not.
// If not, show the setup modal and instruct user setup inbox relays
let client = get_client();
let window_handle = window.window_handle();
let (tx, rx) = oneshot::channel::<Option<Vec<String>>>();
let this = Self {
account,
relays,
dock,
};
cx.background_spawn(async move {
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let relays = if let Ok(events) = client.database().query(filter).await {
if let Some(event) = events.first_owned() {
Some(
event
.tags
.filter_standardized(TagKind::Relay)
.filter_map(|t| match t {
TagStandard::Relay(url) => Some(url.to_string()),
_ => None,
})
.collect::<Vec<_>>(),
)
} else {
None
}
} else {
None
};
_ = tx.send(relays);
})
.detach();
cx.spawn(|this, mut cx| async move {
if let Ok(result) = rx.await {
if let Some(relays) = result {
_ = cx.update(|cx| {
_ = async_relays.update(cx, |this, cx| {
*this = Some(relays);
cx.notify();
});
});
} else {
_ = cx.update_window(window_handle, |_, window, cx| {
this.update(cx, |this: &mut Self, cx| {
this.render_setup_relays(window, cx)
})
});
}
}
})
.detach();
this.verify_user_relays(window, cx);
this
})
}
fn verify_user_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
let Some(account) = Account::global(cx) else {
return;
};
let public_key = account.read(cx).get().public_key();
let client = get_client();
let window_handle = window.window_handle();
let (tx, rx) = oneshot::channel::<Option<Vec<String>>>();
cx.background_spawn(async move {
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let relays = client
.database()
.query(filter)
.await
.ok()
.and_then(|events| events.first_owned())
.map(|event| {
event
.tags
.filter_standardized(TagKind::Relay)
.filter_map(|t| match t {
TagStandard::Relay(url) => Some(url.to_string()),
_ => None,
})
.collect::<Vec<_>>()
});
_ = tx.send(relays);
})
.detach();
cx.spawn(|this, mut cx| async move {
if let Ok(Some(relays)) = rx.await {
_ = cx.update(|cx| {
_ = this.update(cx, |this, cx| {
let relays = cx.new(|_| Some(relays));
this.relays = relays;
cx.notify();
});
});
} else {
_ = cx.update_window(window_handle, |_, window, cx| {
this.update(cx, |this: &mut Self, cx| {
this.render_setup_relays(window, cx)
})
});
}
})
.detach();
}
fn render_setup_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
let relays = cx.new(|cx| Relays::new(None, window, cx));
@@ -254,18 +253,22 @@ impl AppView {
}))
}
fn render_account(&self) -> impl IntoElement {
fn render_account(&self, cx: &mut Context<Self>) -> impl IntoElement {
Button::new("account")
.ghost()
.xsmall()
.reverse()
.icon(Icon::new(IconName::ChevronDownSmall))
.child(
img(self.account.avatar())
.size_5()
.rounded_full()
.object_fit(ObjectFit::Cover),
)
.when_some(Account::global(cx), |this, account| {
let profile = account.read(cx).get();
this.child(
img(profile.avatar())
.size_5()
.rounded_full()
.object_fit(ObjectFit::Cover),
)
})
.popup_menu(move |this, _, _cx| {
this.menu(
"Profile",
@@ -286,16 +289,19 @@ impl AppView {
fn on_panel_action(&mut self, action: &AddPanel, window: &mut Window, cx: &mut Context<Self>) {
match &action.panel {
PanelKind::Room(id) => match chat::init(id, window, cx) {
Ok(panel) => {
self.dock.update(cx, |dock_area, cx| {
dock_area.add_panel(panel, action.position, window, cx);
});
PanelKind::Room(id) => {
// User must be logged in to open a room
match chat::init(id, window, cx) {
Ok(panel) => {
self.dock.update(cx, |dock_area, cx| {
dock_area.add_panel(panel, action.position, window, cx);
});
}
Err(e) => window.push_notification(e.to_string(), cx),
}
Err(e) => window.push_notification(e.to_string(), cx),
},
}
PanelKind::Profile => {
let panel = Arc::new(profile::init(self.account.clone(), window, cx));
let panel = profile::init(window, cx);
self.dock.update(cx, |dock_area, cx| {
dock_area.add_panel(panel, action.position, window, cx);
@@ -319,8 +325,13 @@ impl AppView {
}
fn on_logout_action(&mut self, _action: &Logout, window: &mut Window, cx: &mut Context<Self>) {
cx.background_spawn(async move { get_client().reset().await })
.detach();
let client = get_client();
cx.background_spawn(async move {
// Reset nostr client
client.reset().await
})
.detach();
window.replace_root(cx, |window, cx| {
Root::new(onboarding::init(window, cx).into(), window, cx)
@@ -353,7 +364,7 @@ impl Render for AppView {
.px_2()
.child(self.render_appearance_button(window, cx))
.child(self.render_relays_button(window, cx))
.child(self.render_account()),
.child(self.render_account(cx)),
),
)
.child(self.dock.clone())