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
+106 -65
View File
@@ -1,5 +1,5 @@
use async_utility::task::spawn;
use common::{constants::IMAGE_SERVICE, profile::NostrProfile, utils::nip96_upload};
use common::{constants::IMAGE_SERVICE, utils::nip96_upload};
use gpui::{
div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity, EventEmitter,
Flatten, FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render,
@@ -8,7 +8,7 @@ use gpui::{
use nostr_sdk::prelude::*;
use smol::fs;
use state::get_client;
use std::str::FromStr;
use std::{str::FromStr, sync::Arc, time::Duration};
use ui::{
button::{Button, ButtonVariants},
dock_area::panel::{Panel, PanelEvent},
@@ -17,12 +17,12 @@ use ui::{
ContextModal, Disableable, Sizable, Size,
};
pub fn init(profile: NostrProfile, window: &mut Window, cx: &mut App) -> Entity<Profile> {
Profile::new(profile, window, cx)
pub fn init(window: &mut Window, cx: &mut App) -> Arc<Entity<Profile>> {
Arc::new(Profile::new(window, cx))
}
pub struct Profile {
profile: NostrProfile,
profile: Option<Metadata>,
// Form
name_input: Entity<TextInput>,
avatar_input: Entity<TextInput>,
@@ -32,60 +32,108 @@ pub struct Profile {
is_submitting: bool,
// Panel
name: SharedString,
closable: bool,
zoomable: bool,
focus_handle: FocusHandle,
}
impl Profile {
pub fn new(mut profile: NostrProfile, window: &mut Window, cx: &mut App) -> Entity<Self> {
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
let window_handle = window.window_handle();
let name_input = cx.new(|cx| {
let mut input = TextInput::new(window, cx).text_size(Size::XSmall);
if let Some(name) = profile.metadata().display_name.as_ref() {
input.set_text(name, window, cx);
}
input
});
let avatar_input = cx.new(|cx| {
let mut input = TextInput::new(window, cx).text_size(Size::XSmall).small();
if let Some(picture) = profile.metadata().picture.as_ref() {
input.set_text(picture, window, cx);
}
input
});
let bio_input = cx.new(|cx| {
let mut input = TextInput::new(window, cx)
TextInput::new(window, cx)
.text_size(Size::XSmall)
.multi_line();
if let Some(about) = profile.metadata().about.as_ref() {
input.set_text(about, window, cx);
} else {
input.set_placeholder("A short introduce about you.");
}
input
});
let website_input = cx.new(|cx| {
let mut input = TextInput::new(window, cx).text_size(Size::XSmall);
if let Some(website) = profile.metadata().website.as_ref() {
input.set_text(website, window, cx);
} else {
input.set_placeholder("https://your-website.com");
}
input
.placeholder("Alice")
});
cx.new(|cx| Self {
profile,
name_input,
avatar_input,
bio_input,
website_input,
is_loading: false,
is_submitting: false,
name: "Profile".into(),
closable: true,
zoomable: true,
focus_handle: cx.focus_handle(),
let avatar_input = cx.new(|cx| {
TextInput::new(window, cx)
.text_size(Size::XSmall)
.small()
.placeholder("https://example.com/avatar.png")
});
let website_input = cx.new(|cx| {
TextInput::new(window, cx)
.text_size(Size::XSmall)
.placeholder("https://your-website.com")
});
let bio_input = cx.new(|cx| {
TextInput::new(window, cx)
.text_size(Size::XSmall)
.multi_line()
.placeholder("A short introduce about you.")
});
cx.new(|cx| {
let this = Self {
name_input,
avatar_input,
bio_input,
website_input,
profile: None,
is_loading: false,
is_submitting: false,
name: "Profile".into(),
focus_handle: cx.focus_handle(),
};
let client = get_client();
let (tx, rx) = oneshot::channel::<Option<Metadata>>();
cx.background_spawn(async move {
let result = async {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let metadata = client
.fetch_metadata(public_key, Duration::from_secs(2))
.await?;
Ok::<_, anyhow::Error>(metadata)
}
.await;
if let Ok(metadata) = result {
_ = tx.send(Some(metadata));
} else {
_ = tx.send(None);
};
})
.detach();
cx.spawn(|this, mut cx| async move {
if let Ok(Some(metadata)) = rx.await {
_ = cx.update_window(window_handle, |_, window, cx| {
_ = this.update(cx, |this: &mut Profile, cx| {
this.avatar_input.update(cx, |this, cx| {
if let Some(avatar) = metadata.picture.as_ref() {
this.set_text(avatar, window, cx);
}
});
this.bio_input.update(cx, |this, cx| {
if let Some(bio) = metadata.about.as_ref() {
this.set_text(bio, window, cx);
}
});
this.name_input.update(cx, |this, cx| {
if let Some(display_name) = metadata.display_name.as_ref() {
this.set_text(display_name, window, cx);
}
});
this.website_input.update(cx, |this, cx| {
if let Some(website) = metadata.website.as_ref() {
this.set_text(website, window, cx);
}
});
this.profile = Some(metadata);
cx.notify();
});
});
}
})
.detach();
this
})
}
@@ -164,12 +212,13 @@ impl Profile {
let bio = self.bio_input.read(cx).text().to_string();
let website = self.website_input.read(cx).text().to_string();
let mut new_metadata = self
.profile
.metadata()
.to_owned()
.display_name(name)
.about(bio);
let old_metadata = if let Some(metadata) = self.profile.as_ref() {
metadata.clone()
} else {
Metadata::default()
};
let mut new_metadata = old_metadata.display_name(name).about(bio);
if let Ok(url) = Url::from_str(&avatar) {
new_metadata = new_metadata.picture(url);
@@ -221,14 +270,6 @@ impl Panel for Profile {
self.name.clone().into_any_element()
}
fn closable(&self, _cx: &App) -> bool {
self.closable
}
fn zoomable(&self, _cx: &App) -> bool {
self.zoomable
}
fn popup_menu(&self, menu: PopupMenu, _cx: &App) -> PopupMenu {
menu.track_focus(&self.focus_handle)
}