chore: Improve Request Screening (#101)
* open chat while screening * close panel on ignore * bypass screening * . * improve settings * refine modal * . * . * . * . * .
This commit is contained in:
@@ -20,15 +20,14 @@ use ui::actions::OpenProfile;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::dock_area::dock::DockPlacement;
|
||||
use ui::dock_area::panel::PanelView;
|
||||
use ui::dock_area::{DockArea, DockItem};
|
||||
use ui::dock_area::{ClosePanel, DockArea, DockItem};
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::{ContextModal, IconName, Root, Sizable, StyledExt, TitleBar};
|
||||
|
||||
use crate::views::chat::{self, Chat};
|
||||
use crate::views::screening::Screening;
|
||||
use crate::views::user_profile::UserProfile;
|
||||
use crate::views::{
|
||||
login, new_account, onboarding, preferences, sidebar, startup, user_profile, welcome,
|
||||
chat, login, new_account, onboarding, preferences, sidebar, startup, user_profile, welcome,
|
||||
};
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> {
|
||||
@@ -74,7 +73,7 @@ pub struct ChatSpace {
|
||||
dock: Entity<DockArea>,
|
||||
toolbar: bool,
|
||||
#[allow(unused)]
|
||||
subscriptions: SmallVec<[Subscription; 6]>,
|
||||
subscriptions: SmallVec<[Subscription; 5]>,
|
||||
}
|
||||
|
||||
impl ChatSpace {
|
||||
@@ -112,7 +111,6 @@ impl ChatSpace {
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_10()
|
||||
.w_full()
|
||||
.h_40()
|
||||
.flex()
|
||||
@@ -166,13 +164,6 @@ impl ChatSpace {
|
||||
},
|
||||
));
|
||||
|
||||
// Automatically load messages when chat panel opens
|
||||
subscriptions.push(cx.observe_new::<Chat>(|this, window, cx| {
|
||||
if let Some(window) = window {
|
||||
this.load_messages(window, cx);
|
||||
}
|
||||
}));
|
||||
|
||||
// Automatically run on_load function when UserProfile is created
|
||||
subscriptions.push(cx.observe_new::<UserProfile>(|this, window, cx| {
|
||||
if let Some(window) = window {
|
||||
@@ -183,7 +174,7 @@ impl ChatSpace {
|
||||
// Automatically run on_load function when Screening is created
|
||||
subscriptions.push(cx.observe_new::<Screening>(|this, window, cx| {
|
||||
if let Some(window) = window {
|
||||
this.on_load(window, cx);
|
||||
this.load(window, cx);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -192,17 +183,33 @@ impl ChatSpace {
|
||||
®istry,
|
||||
window,
|
||||
|this: &mut Self, _state, event, window, cx| {
|
||||
if let RoomEmitter::Open(room) = event {
|
||||
if let Some(room) = room.upgrade() {
|
||||
this.dock.update(cx, |this, cx| {
|
||||
let panel = chat::init(room, window, cx);
|
||||
let placement = DockPlacement::Center;
|
||||
match event {
|
||||
RoomEmitter::Open(room) => {
|
||||
if let Some(room) = room.upgrade() {
|
||||
this.dock.update(cx, |this, cx| {
|
||||
let panel = chat::init(room, window, cx);
|
||||
// Load messages on panel creation
|
||||
panel.update(cx, |this, cx| {
|
||||
this.load_messages(window, cx);
|
||||
});
|
||||
|
||||
this.add_panel(panel, placement, window, cx);
|
||||
});
|
||||
} else {
|
||||
window.push_notification(t!("chatspace.failed_to_open_room"), cx);
|
||||
this.add_panel(panel, DockPlacement::Center, window, cx);
|
||||
});
|
||||
} else {
|
||||
window.push_notification(t!("chatspace.failed_to_open_room"), cx);
|
||||
}
|
||||
}
|
||||
RoomEmitter::Close(..) => {
|
||||
this.dock.update(cx, |this, cx| {
|
||||
this.focus_tab_panel(window, cx);
|
||||
|
||||
cx.defer_in(window, |_, window, cx| {
|
||||
window.dispatch_action(Box::new(ClosePanel), cx);
|
||||
window.close_all_modals(cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
));
|
||||
@@ -283,10 +290,11 @@ impl ChatSpace {
|
||||
|
||||
pub fn open_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let settings = preferences::init(window, cx);
|
||||
let title = SharedString::new(t!("chatspace.preferences_title"));
|
||||
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.title(SharedString::new(t!("chatspace.preferences_title")))
|
||||
.title(title.clone())
|
||||
.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.child(settings.clone())
|
||||
});
|
||||
|
||||
@@ -94,19 +94,23 @@ impl Chat {
|
||||
subscriptions.push(cx.subscribe_in(
|
||||
&input,
|
||||
window,
|
||||
move |this: &mut Self, input, event, window, cx| match event {
|
||||
InputEvent::PressEnter { .. } => {
|
||||
this.send_message(window, cx);
|
||||
}
|
||||
InputEvent::Change(text) => {
|
||||
this.mention_popup(text, input, cx);
|
||||
}
|
||||
_ => {}
|
||||
move |this: &mut Self, input, event, window, cx| {
|
||||
match event {
|
||||
InputEvent::PressEnter { .. } => {
|
||||
this.send_message(window, cx);
|
||||
}
|
||||
InputEvent::Change(text) => {
|
||||
this.mention_popup(text, input, cx);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
},
|
||||
));
|
||||
|
||||
subscriptions.push(
|
||||
cx.subscribe_in(&room, window, move |this, _, incoming, _w, cx| {
|
||||
subscriptions.push(cx.subscribe_in(
|
||||
&room,
|
||||
window,
|
||||
move |this, _, incoming, _window, cx| {
|
||||
// Check if the incoming message is the same as the new message created by optimistic update
|
||||
if this.prevent_duplicate_message(&incoming.0, cx) {
|
||||
return;
|
||||
@@ -121,8 +125,8 @@ impl Chat {
|
||||
});
|
||||
|
||||
this.list_state.splice(old_len..old_len, 1);
|
||||
}),
|
||||
);
|
||||
},
|
||||
));
|
||||
|
||||
// Initialize list state
|
||||
// [item_count] always equal to 1 at the beginning
|
||||
@@ -251,7 +255,7 @@ impl Chat {
|
||||
// Get the message which includes all attachments
|
||||
let content = self.input_content(cx);
|
||||
// Get the backup setting
|
||||
let backup = AppSettings::get_global(cx).settings.backup_messages;
|
||||
let backup = AppSettings::get_backup_messages(cx);
|
||||
|
||||
// Return if message is empty
|
||||
if content.trim().is_empty() {
|
||||
@@ -397,7 +401,7 @@ impl Chat {
|
||||
self.uploading(true, cx);
|
||||
|
||||
// Get the user's configured NIP96 server
|
||||
let nip96_server = AppSettings::get_global(cx).settings.media_server.clone();
|
||||
let nip96_server = AppSettings::get_media_server(cx);
|
||||
|
||||
// Open native file dialog
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
@@ -575,8 +579,8 @@ impl Chat {
|
||||
return div().id(ix);
|
||||
};
|
||||
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let hide_avatar = AppSettings::get_global(cx).settings.hide_user_avatars;
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let hide_avatar = AppSettings::get_hide_user_avatars(cx);
|
||||
let registry = Registry::read_global(cx);
|
||||
let author = registry.get_person(&message.author, cx);
|
||||
|
||||
@@ -715,8 +719,6 @@ impl Chat {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.px_3()
|
||||
.pb_3()
|
||||
.children(errors.iter().map(|error| {
|
||||
div()
|
||||
.text_sm()
|
||||
@@ -792,7 +794,7 @@ impl Panel for Chat {
|
||||
|
||||
fn title(&self, cx: &App) -> AnyElement {
|
||||
self.room.read_with(cx, |this, cx| {
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let label = this.display_name(cx);
|
||||
let url = this.display_image(proxy, cx);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
div, App, AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, SharedString, Styled, Window,
|
||||
div, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString,
|
||||
Styled, Window,
|
||||
};
|
||||
use i18n::t;
|
||||
use registry::Registry;
|
||||
@@ -21,7 +21,6 @@ pub fn init(
|
||||
pub struct Subject {
|
||||
id: u64,
|
||||
input: Entity<InputState>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl Subject {
|
||||
@@ -39,11 +38,7 @@ impl Subject {
|
||||
this
|
||||
});
|
||||
|
||||
cx.new(|cx| Self {
|
||||
id,
|
||||
input,
|
||||
focus_handle: cx.focus_handle(),
|
||||
})
|
||||
cx.new(|_| Self { id, input })
|
||||
}
|
||||
|
||||
pub fn update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -65,13 +60,9 @@ impl Subject {
|
||||
impl Render for Subject {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.px_3()
|
||||
.pb_3()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::ops::Range;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use common::display::DisplayProfile;
|
||||
use common::display::{DisplayProfile, TextUtils};
|
||||
use common::nip05::nip05_profile;
|
||||
use global::constants::BOOTSTRAP_RELAYS;
|
||||
use global::nostr_client;
|
||||
@@ -24,7 +24,7 @@ use theme::ActiveTheme;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::notification::Notification;
|
||||
use ui::{ContextModal, Disableable, Icon, IconName, Sizable, StyledExt};
|
||||
use ui::{v_flex, ContextModal, Disableable, Icon, IconName, Sizable, StyledExt};
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Compose> {
|
||||
cx.new(|cx| Compose::new(window, cx))
|
||||
@@ -278,7 +278,7 @@ impl Compose {
|
||||
Err(anyhow!(t!("common.not_found")))
|
||||
}
|
||||
})
|
||||
} else if let Ok(public_key) = common::parse_pubkey_from_str(&content) {
|
||||
} else if let Ok(public_key) = content.to_public_key() {
|
||||
cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let contact = Contact::new(public_key).select();
|
||||
@@ -357,7 +357,7 @@ impl Compose {
|
||||
}
|
||||
|
||||
fn list_items(&self, range: Range<usize>, cx: &Context<Self>) -> Vec<impl IntoElement> {
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let registry = Registry::read_global(cx);
|
||||
let mut items = Vec::with_capacity(self.contacts.len());
|
||||
|
||||
@@ -420,22 +420,19 @@ impl Render for Compose {
|
||||
t!("compose.create_dm_button")
|
||||
};
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::new(t!("compose.description"))),
|
||||
)
|
||||
.when_some(self.error_message.read(cx).as_ref(), |this, msg| {
|
||||
this.child(div().px_3().text_xs().text_color(red()).child(msg.clone()))
|
||||
this.child(div().text_xs().text_color(red()).child(msg.clone()))
|
||||
})
|
||||
.child(
|
||||
div().px_3().flex().flex_col().child(
|
||||
div().flex().flex_col().child(
|
||||
div()
|
||||
.h_10()
|
||||
.border_b_1()
|
||||
@@ -460,7 +457,6 @@ impl Render for Compose {
|
||||
.mt_1()
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
@@ -535,17 +531,15 @@ impl Render for Compose {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div().p_3().child(
|
||||
Button::new("create_dm_btn")
|
||||
.label(label)
|
||||
.primary()
|
||||
.w_full()
|
||||
.loading(self.submitting)
|
||||
.disabled(self.submitting || self.adding)
|
||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
||||
this.compose(window, cx);
|
||||
})),
|
||||
),
|
||||
Button::new("create_dm_btn")
|
||||
.label(label)
|
||||
.primary()
|
||||
.w_full()
|
||||
.loading(self.submitting)
|
||||
.disabled(self.submitting || self.adding)
|
||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
||||
this.compose(window, cx);
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ impl EditProfile {
|
||||
}
|
||||
|
||||
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let nip96 = AppSettings::get_global(cx).settings.media_server.clone();
|
||||
let nip96 = AppSettings::get_media_server(cx);
|
||||
let avatar_input = self.avatar_input.downgrade();
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
@@ -233,7 +233,6 @@ impl EditProfile {
|
||||
impl Render for EditProfile {
|
||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use client_keys::ClientKeys;
|
||||
use common::display::TextUtils;
|
||||
use common::handle_auth::CoopAuthUrlHandler;
|
||||
use common::string_to_qr;
|
||||
use global::constants::{APP_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -99,7 +99,7 @@ impl Login {
|
||||
|
||||
// Update the QR Image with the new connection string
|
||||
this.qr_image.update(cx, |this, cx| {
|
||||
*this = string_to_qr(&connection_string.to_string());
|
||||
*this = connection_string.to_string().to_qr();
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
@@ -234,8 +234,6 @@ impl Login {
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.pt_4()
|
||||
.px_4()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
|
||||
@@ -118,8 +118,6 @@ impl NewAccount {
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.pt_4()
|
||||
.px_4()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
@@ -132,7 +130,7 @@ impl NewAccount {
|
||||
}
|
||||
|
||||
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let nip96 = AppSettings::get_global(cx).settings.media_server.clone();
|
||||
let nip96 = AppSettings::get_media_server(cx);
|
||||
let avatar_input = self.avatar_input.downgrade();
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
|
||||
@@ -134,8 +134,8 @@ impl Focusable for Onboarding {
|
||||
|
||||
impl Render for Onboarding {
|
||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let auto_login = AppSettings::get_global(cx).settings.auto_login;
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let auto_login = AppSettings::get_auto_login(cx);
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
|
||||
div()
|
||||
.py_4()
|
||||
@@ -239,11 +239,8 @@ impl Render for Onboarding {
|
||||
Checkbox::new("auto_login")
|
||||
.label(SharedString::new(t!("onboarding.auto_login")))
|
||||
.checked(auto_login)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.auto_login = !this.settings.auto_login;
|
||||
cx.notify();
|
||||
})
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_auto_login(!auto_login, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -3,8 +3,8 @@ use global::constants::{DEFAULT_MODAL_WIDTH, NIP96_SERVER};
|
||||
use gpui::http_client::Url;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, px, relative, rems, App, AppContext, Context, Entity, FocusHandle, InteractiveElement,
|
||||
IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
div, px, relative, rems, App, AppContext, Context, Entity, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
use i18n::t;
|
||||
use identity::Identity;
|
||||
@@ -15,7 +15,7 @@ use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::input::{InputState, TextInput};
|
||||
use ui::switch::Switch;
|
||||
use ui::{ContextModal, IconName, Sizable, Size, StyledExt};
|
||||
use ui::{v_flex, ContextModal, IconName, Sizable, Size, StyledExt};
|
||||
|
||||
use crate::views::{edit_profile, relays};
|
||||
|
||||
@@ -25,27 +25,19 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Preferences> {
|
||||
|
||||
pub struct Preferences {
|
||||
media_input: Entity<InputState>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl Preferences {
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let media_server = AppSettings::get_global(cx)
|
||||
.settings
|
||||
.media_server
|
||||
.to_string();
|
||||
|
||||
let media_server = AppSettings::get_media_server(cx).to_string();
|
||||
let media_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.default_value(media_server)
|
||||
.placeholder(NIP96_SERVER)
|
||||
});
|
||||
|
||||
Self {
|
||||
media_input,
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
Self { media_input }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,22 +67,18 @@ impl Preferences {
|
||||
|
||||
impl Render for Preferences {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let registry = Registry::read_global(cx);
|
||||
let settings = AppSettings::get_global(cx).settings.as_ref();
|
||||
|
||||
let input_state = self.media_input.downgrade();
|
||||
let profile = Identity::read_global(cx)
|
||||
.public_key()
|
||||
.map(|pk| registry.get_person(&pk, cx));
|
||||
.map(|pk| Registry::read_global(cx).get_person(&pk, cx));
|
||||
|
||||
let input_state = self.media_input.downgrade();
|
||||
let backup_messages = AppSettings::get_backup_messages(cx);
|
||||
let screening = AppSettings::get_screening(cx);
|
||||
let contact_bypass = AppSettings::get_contact_bypass(cx);
|
||||
let proxy_avatar = AppSettings::get_proxy_user_avatars(cx);
|
||||
let hide_avatar = AppSettings::get_hide_user_avatars(cx);
|
||||
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.px_3()
|
||||
.pb_3()
|
||||
.flex()
|
||||
.flex_col()
|
||||
v_flex()
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
@@ -118,10 +106,8 @@ impl Render for Preferences {
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
Avatar::new(
|
||||
profile.avatar_url(settings.proxy_user_avatars),
|
||||
)
|
||||
.size(rems(2.4)),
|
||||
Avatar::new(profile.avatar_url(proxy_avatar))
|
||||
.size(rems(2.4)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -187,19 +173,14 @@ impl Render for Preferences {
|
||||
.with_size(Size::Size(px(26.)))
|
||||
.on_click(move |_, window, cx| {
|
||||
if let Some(input) = input_state.upgrade() {
|
||||
let value = input.read(cx).value();
|
||||
let Ok(url) = Url::parse(value) else {
|
||||
let Ok(url) = Url::parse(input.read(cx).value()) else {
|
||||
window.push_notification(
|
||||
t!("preferences.url_not_valid"),
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.media_server = url;
|
||||
cx.notify();
|
||||
});
|
||||
AppSettings::update_media_server(url, cx);
|
||||
}
|
||||
}),
|
||||
),
|
||||
@@ -227,19 +208,37 @@ impl Render for Preferences {
|
||||
.child(SharedString::new(t!("preferences.messages_header"))),
|
||||
)
|
||||
.child(
|
||||
div().flex().flex_col().gap_2().child(
|
||||
Switch::new("backup_messages")
|
||||
.label(t!("preferences.backup_messages_label"))
|
||||
.description(t!("preferences.backup_description"))
|
||||
.checked(settings.backup_messages)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.backup_messages =
|
||||
!this.settings.backup_messages;
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
),
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
Switch::new("screening")
|
||||
.label(t!("preferences.screening_label"))
|
||||
.description(t!("preferences.screening_description"))
|
||||
.checked(screening)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_screening(!screening, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("bypass")
|
||||
.label(t!("preferences.bypass_label"))
|
||||
.description(t!("preferences.bypass_description"))
|
||||
.checked(contact_bypass)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_contact_bypass(!contact_bypass, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("backup_messages")
|
||||
.label(t!("preferences.backup_messages_label"))
|
||||
.description(t!("preferences.backup_description"))
|
||||
.checked(backup_messages)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_backup_messages(!backup_messages, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -266,26 +265,18 @@ impl Render for Preferences {
|
||||
Switch::new("hide_user_avatars")
|
||||
.label(t!("preferences.hide_avatars_label"))
|
||||
.description(t!("preferences.hide_avatar_description"))
|
||||
.checked(settings.hide_user_avatars)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.hide_user_avatars =
|
||||
!this.settings.hide_user_avatars;
|
||||
cx.notify();
|
||||
})
|
||||
.checked(hide_avatar)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_hide_user_avatars(!hide_avatar, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("proxy_user_avatars")
|
||||
.label(t!("preferences.proxy_avatars_label"))
|
||||
.description(t!("preferences.proxy_description"))
|
||||
.checked(settings.proxy_user_avatars)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.proxy_user_avatars =
|
||||
!this.settings.proxy_user_avatars;
|
||||
cx.notify();
|
||||
})
|
||||
.checked(proxy_avatar)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_proxy_user_avatars(!proxy_avatar, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use common::display::{shorten_pubkey, DisplayProfile};
|
||||
use common::nip05::nip05_verify;
|
||||
use global::nostr_client;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, relative, rems, App, AppContext, Context, Entity, IntoElement, ParentElement, Render,
|
||||
div, relative, rems, App, AppContext, Context, Div, Entity, IntoElement, ParentElement, Render,
|
||||
SharedString, Styled, Task, Window,
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use i18n::t;
|
||||
use i18n::{shared_t, t};
|
||||
use identity::Identity;
|
||||
use nostr_sdk::prelude::*;
|
||||
use registry::Registry;
|
||||
@@ -23,30 +22,31 @@ pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<
|
||||
|
||||
pub struct Screening {
|
||||
public_key: PublicKey,
|
||||
followed: bool,
|
||||
connections: usize,
|
||||
verified: bool,
|
||||
followed: bool,
|
||||
dm_relays: bool,
|
||||
mutual_contacts: usize,
|
||||
}
|
||||
|
||||
impl Screening {
|
||||
pub fn new(public_key: PublicKey, _window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|_| Self {
|
||||
public_key,
|
||||
followed: false,
|
||||
connections: 0,
|
||||
verified: false,
|
||||
followed: false,
|
||||
dm_relays: false,
|
||||
mutual_contacts: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_load(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub fn load(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
// Skip if user isn't logged in
|
||||
let Some(identity) = Identity::read_global(cx).public_key() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let public_key = self.public_key;
|
||||
|
||||
let check_trust_score: Task<(bool, usize)> = cx.background_spawn(async move {
|
||||
let check_trust_score: Task<(bool, usize, bool)> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
|
||||
let follow = Filter::new()
|
||||
@@ -55,15 +55,21 @@ impl Screening {
|
||||
.pubkey(public_key)
|
||||
.limit(1);
|
||||
|
||||
let connection = Filter::new()
|
||||
let contacts = Filter::new()
|
||||
.kind(Kind::ContactList)
|
||||
.pubkey(public_key)
|
||||
.limit(1);
|
||||
|
||||
let is_follow = client.database().count(follow).await.unwrap_or(0) >= 1;
|
||||
let connects = client.database().count(connection).await.unwrap_or(0);
|
||||
let relays = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
(is_follow, connects)
|
||||
let is_follow = client.database().count(follow).await.unwrap_or(0) >= 1;
|
||||
let mutual_contacts = client.database().count(contacts).await.unwrap_or(0);
|
||||
let dm_relays = client.database().count(relays).await.unwrap_or(0) >= 1;
|
||||
|
||||
(is_follow, mutual_contacts, dm_relays)
|
||||
});
|
||||
|
||||
let verify_nip05 = if let Some(address) = self.address(cx) {
|
||||
@@ -75,11 +81,12 @@ impl Screening {
|
||||
};
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let (followed, connections) = check_trust_score.await;
|
||||
let (followed, mutual_contacts, dm_relays) = check_trust_score.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.followed = followed;
|
||||
this.connections = connections;
|
||||
this.mutual_contacts = mutual_contacts;
|
||||
this.dm_relays = dm_relays;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
@@ -140,15 +147,11 @@ impl Screening {
|
||||
|
||||
impl Render for Screening {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let profile = self.profile(cx);
|
||||
let shorten_pubkey = shorten_pubkey(profile.public_key(), 8);
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.px_4()
|
||||
.pt_8()
|
||||
.pb_4()
|
||||
.gap_4()
|
||||
.child(
|
||||
v_flex()
|
||||
@@ -166,7 +169,7 @@ impl Render for Screening {
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.p_1()
|
||||
@@ -176,7 +179,7 @@ impl Render for Screening {
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.bg(cx.theme().surface_background)
|
||||
.text_sm()
|
||||
.truncate()
|
||||
.text_ellipsis()
|
||||
@@ -185,104 +188,138 @@ impl Render for Screening {
|
||||
.child(shorten_pubkey),
|
||||
)
|
||||
.child(
|
||||
Button::new("njump")
|
||||
.label(t!("profile.njump"))
|
||||
.secondary()
|
||||
.small()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
this.open_njump(window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("report")
|
||||
.tooltip(t!("screening.report"))
|
||||
.icon(IconName::Info)
|
||||
.danger_alt()
|
||||
.small()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
this.report(window, cx);
|
||||
})),
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Button::new("njump")
|
||||
.label(t!("profile.njump"))
|
||||
.secondary()
|
||||
.small()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
this.open_njump(window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("report")
|
||||
.tooltip(t!("screening.report"))
|
||||
.icon(IconName::Report)
|
||||
.danger()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
this.report(window, cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.when_some(self.address(cx), |this, address| {
|
||||
this.child(h_flex().gap_2().map(|this| {
|
||||
if self.verified {
|
||||
this.text_sm()
|
||||
.child(
|
||||
Icon::new(IconName::CheckCircleFill)
|
||||
.small()
|
||||
.flex_shrink_0()
|
||||
.text_color(cx.theme().icon_accent),
|
||||
)
|
||||
.child(div().flex_1().child(SharedString::new(t!(
|
||||
"screening.verified",
|
||||
address = address
|
||||
))))
|
||||
} else {
|
||||
this.text_sm()
|
||||
.child(
|
||||
Icon::new(IconName::CheckCircleFill)
|
||||
.small()
|
||||
.text_color(cx.theme().icon_muted),
|
||||
)
|
||||
.child(div().flex_1().child(SharedString::new(t!(
|
||||
"screening.not_verified",
|
||||
address = address
|
||||
))))
|
||||
}
|
||||
}))
|
||||
})
|
||||
.child(h_flex().gap_2().map(|this| {
|
||||
if !self.followed {
|
||||
this.text_sm()
|
||||
.child(
|
||||
Icon::new(IconName::CheckCircleFill)
|
||||
.small()
|
||||
.text_color(cx.theme().icon_muted),
|
||||
)
|
||||
.child(SharedString::new(t!("screening.not_contact")))
|
||||
} else {
|
||||
this.text_sm()
|
||||
.child(
|
||||
Icon::new(IconName::CheckCircleFill)
|
||||
.small()
|
||||
.text_color(cx.theme().icon_accent),
|
||||
)
|
||||
.child(SharedString::new(t!("screening.contact")))
|
||||
}
|
||||
}))
|
||||
.gap_3()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(status_badge(self.followed, cx))
|
||||
.child(
|
||||
Icon::new(IconName::CheckCircleFill)
|
||||
.small()
|
||||
.flex_shrink_0()
|
||||
.text_color({
|
||||
if self.connections > 0 {
|
||||
cx.theme().icon_accent
|
||||
v_flex()
|
||||
.text_sm()
|
||||
.child(shared_t!("screening.contact_label"))
|
||||
.child(div().text_color(cx.theme().text_muted).child({
|
||||
if self.followed {
|
||||
shared_t!("screening.contact")
|
||||
} else {
|
||||
cx.theme().icon_muted
|
||||
shared_t!("screening.not_contact")
|
||||
}
|
||||
}),
|
||||
)
|
||||
.map(|this| {
|
||||
if self.connections > 0 {
|
||||
this.child(SharedString::new(t!(
|
||||
"screening.total_connections",
|
||||
u = self.connections
|
||||
)))
|
||||
} else {
|
||||
this.child(SharedString::new(t!("screening.no_connections")))
|
||||
}
|
||||
}),
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap_2()
|
||||
.child(status_badge(self.verified, cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.text_sm()
|
||||
.child({
|
||||
if let Some(addr) = self.address(cx) {
|
||||
shared_t!("screening.nip05_addr", addr = addr)
|
||||
} else {
|
||||
shared_t!("screening.nip05_label")
|
||||
}
|
||||
})
|
||||
.child(div().text_color(cx.theme().text_muted).child({
|
||||
if self.address(cx).is_some() {
|
||||
if self.verified {
|
||||
shared_t!("screening.nip05_ok")
|
||||
} else {
|
||||
shared_t!("screening.nip05_failed")
|
||||
}
|
||||
} else {
|
||||
shared_t!("screening.nip05_empty")
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap_2()
|
||||
.child(status_badge(self.mutual_contacts > 0, cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.text_sm()
|
||||
.child(shared_t!("screening.mutual_label"))
|
||||
.child(div().text_color(cx.theme().text_muted).child({
|
||||
if self.mutual_contacts > 0 {
|
||||
shared_t!("screening.mutual", u = self.mutual_contacts)
|
||||
} else {
|
||||
shared_t!("screening.no_mutual")
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap_2()
|
||||
.child(status_badge(self.dm_relays, cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.text_sm()
|
||||
.child({
|
||||
if self.dm_relays {
|
||||
shared_t!("screening.relay_found")
|
||||
} else {
|
||||
shared_t!("screening.relay_empty")
|
||||
}
|
||||
})
|
||||
.child(div().w_full().text_color(cx.theme().text_muted).child(
|
||||
{
|
||||
if self.dm_relays {
|
||||
shared_t!("screening.relay_found_desc")
|
||||
} else {
|
||||
shared_t!("screening.relay_empty_desc")
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn status_badge(status: bool, cx: &App) -> Div {
|
||||
div()
|
||||
.pt_1()
|
||||
.flex_shrink_0()
|
||||
.child(Icon::new(IconName::CheckCircleFill).small().text_color({
|
||||
if status {
|
||||
cx.theme().icon_accent
|
||||
} else {
|
||||
cx.theme().icon_muted
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ use std::rc::Rc;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, img, rems, App, ClickEvent, Div, InteractiveElement, IntoElement,
|
||||
ParentElement as _, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
div, rems, App, ClickEvent, Div, InteractiveElement, IntoElement, ParentElement as _,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
use i18n::t;
|
||||
use nostr_sdk::prelude::*;
|
||||
use registry::room::RoomKind;
|
||||
use registry::Registry;
|
||||
use settings::AppSettings;
|
||||
use theme::ActiveTheme;
|
||||
use ui::actions::OpenProfile;
|
||||
@@ -22,49 +23,39 @@ use crate::views::screening;
|
||||
pub struct RoomListItem {
|
||||
ix: usize,
|
||||
base: Div,
|
||||
room_id: u64,
|
||||
public_key: PublicKey,
|
||||
name: Option<SharedString>,
|
||||
avatar: Option<SharedString>,
|
||||
created_at: Option<SharedString>,
|
||||
kind: Option<RoomKind>,
|
||||
name: SharedString,
|
||||
avatar: SharedString,
|
||||
created_at: SharedString,
|
||||
kind: RoomKind,
|
||||
#[allow(clippy::type_complexity)]
|
||||
handler: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
|
||||
}
|
||||
|
||||
impl RoomListItem {
|
||||
pub fn new(ix: usize, public_key: PublicKey) -> Self {
|
||||
pub fn new(
|
||||
ix: usize,
|
||||
room_id: u64,
|
||||
public_key: PublicKey,
|
||||
name: SharedString,
|
||||
avatar: SharedString,
|
||||
created_at: SharedString,
|
||||
kind: RoomKind,
|
||||
) -> Self {
|
||||
Self {
|
||||
ix,
|
||||
public_key,
|
||||
room_id,
|
||||
name,
|
||||
avatar,
|
||||
created_at,
|
||||
kind,
|
||||
base: h_flex().h_9().w_full().px_1p5(),
|
||||
name: None,
|
||||
avatar: None,
|
||||
created_at: None,
|
||||
kind: None,
|
||||
handler: Rc::new(|_, _, _| {}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(mut self, name: impl Into<SharedString>) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn created_at(mut self, created_at: impl Into<SharedString>) -> Self {
|
||||
self.created_at = Some(created_at.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn avatar(mut self, avatar: impl Into<SharedString>) -> Self {
|
||||
self.avatar = Some(avatar.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn kind(mut self, kind: RoomKind) -> Self {
|
||||
self.kind = Some(kind);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
@@ -77,10 +68,11 @@ impl RoomListItem {
|
||||
impl RenderOnce for RoomListItem {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let public_key = self.public_key;
|
||||
let room_id = self.room_id;
|
||||
let kind = self.kind;
|
||||
let handler = self.handler.clone();
|
||||
let hide_avatar = AppSettings::get_global(cx).settings.hide_user_avatars;
|
||||
let screening = AppSettings::get_global(cx).settings.screening;
|
||||
let hide_avatar = AppSettings::get_hide_user_avatars(cx);
|
||||
let require_screening = AppSettings::get_screening(cx);
|
||||
|
||||
self.base
|
||||
.id(self.ix)
|
||||
@@ -94,18 +86,7 @@ impl RenderOnce for RoomListItem {
|
||||
.size_6()
|
||||
.rounded_full()
|
||||
.overflow_hidden()
|
||||
.map(|this| {
|
||||
if let Some(img) = self.avatar {
|
||||
this.child(Avatar::new(img).size(rems(1.5)))
|
||||
} else {
|
||||
this.child(
|
||||
img("brand/avatar.png")
|
||||
.rounded_full()
|
||||
.size_6()
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}),
|
||||
.child(Avatar::new(self.avatar).size(rems(1.5))),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
@@ -114,26 +95,22 @@ impl RenderOnce for RoomListItem {
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.when_some(self.name, |this, name| {
|
||||
this.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.line_clamp(1)
|
||||
.text_ellipsis()
|
||||
.truncate()
|
||||
.font_medium()
|
||||
.child(name),
|
||||
)
|
||||
})
|
||||
.when_some(self.created_at, |this, ago| {
|
||||
this.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(ago),
|
||||
)
|
||||
}),
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.line_clamp(1)
|
||||
.text_ellipsis()
|
||||
.truncate()
|
||||
.font_medium()
|
||||
.child(self.name),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(self.created_at),
|
||||
),
|
||||
)
|
||||
.context_menu(move |this, _window, _cx| {
|
||||
// TODO: add share chat room
|
||||
@@ -141,33 +118,28 @@ impl RenderOnce for RoomListItem {
|
||||
})
|
||||
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
||||
.on_click(move |event, window, cx| {
|
||||
let handler = handler.clone();
|
||||
handler(event, window, cx);
|
||||
|
||||
if let Some(kind) = kind {
|
||||
if kind != RoomKind::Ongoing && screening {
|
||||
let screening = screening::init(public_key, window, cx);
|
||||
if kind != RoomKind::Ongoing && require_screening {
|
||||
let screening = screening::init(public_key, window, cx);
|
||||
|
||||
window.open_modal(cx, move |this, _window, _cx| {
|
||||
let handler_clone = handler.clone();
|
||||
|
||||
this.confirm()
|
||||
.child(screening.clone())
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
.cancel_text(t!("screening.ignore"))
|
||||
.ok_text(t!("screening.response")),
|
||||
)
|
||||
.on_ok(move |event, window, cx| {
|
||||
handler_clone(event, window, cx);
|
||||
// true to close the modal
|
||||
true
|
||||
})
|
||||
});
|
||||
} else {
|
||||
handler(event, window, cx)
|
||||
}
|
||||
} else {
|
||||
handler(event, window, cx)
|
||||
window.open_modal(cx, move |this, _window, _cx| {
|
||||
this.confirm()
|
||||
.child(screening.clone())
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
.cancel_text(t!("screening.ignore"))
|
||||
.ok_text(t!("screening.response")),
|
||||
)
|
||||
.on_cancel(move |_event, _window, cx| {
|
||||
Registry::global(cx).update(cx, |this, cx| {
|
||||
this.close_room(room_id, cx);
|
||||
});
|
||||
// false to prevent closing the modal
|
||||
// modal will be closed after closing panel
|
||||
false
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use common::debounced_delay::DebouncedDelay;
|
||||
use common::display::DisplayProfile;
|
||||
use common::display::{DisplayProfile, TextUtils};
|
||||
use global::constants::{BOOTSTRAP_RELAYS, DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
|
||||
use global::nostr_client;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
@@ -32,7 +32,7 @@ use ui::indicator::Indicator;
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::popup_menu::PopupMenu;
|
||||
use ui::skeleton::Skeleton;
|
||||
use ui::{ContextModal, IconName, Selectable, Sizable, StyledExt};
|
||||
use ui::{v_flex, ContextModal, IconName, Selectable, Sizable, StyledExt};
|
||||
|
||||
use crate::views::compose;
|
||||
|
||||
@@ -146,6 +146,8 @@ impl Sidebar {
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||
.await?;
|
||||
|
||||
log::info!("Subscribe to get metadata for: {public_key}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -334,7 +336,7 @@ impl Sidebar {
|
||||
}
|
||||
|
||||
fn search_by_pubkey(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Ok(public_key) = common::parse_pubkey_from_str(query) else {
|
||||
let Ok(public_key) = query.to_public_key() else {
|
||||
window.push_notification(t!("common.pubkey_invalid"), cx);
|
||||
self.set_finding(false, window, cx);
|
||||
return;
|
||||
@@ -568,36 +570,34 @@ impl Sidebar {
|
||||
let desc = SharedString::new(t!("sidebar.loading_modal_description"));
|
||||
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
this.child(
|
||||
div()
|
||||
.pt_8()
|
||||
.px_4()
|
||||
.pb_4()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(div().font_semibold().child(title.clone()))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(text_1.clone())
|
||||
.child(text_2.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(desc.clone()),
|
||||
),
|
||||
)
|
||||
this.show_close(true)
|
||||
.keyboard(true)
|
||||
.title(title.clone())
|
||||
.child(
|
||||
v_flex()
|
||||
.pb_4()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(text_1.clone())
|
||||
.child(text_2.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(desc.clone()),
|
||||
),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn account(&self, profile: &Profile, cx: &Context<Self>) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
|
||||
div()
|
||||
.px_3()
|
||||
@@ -666,26 +666,30 @@ impl Sidebar {
|
||||
range: Range<usize>,
|
||||
cx: &Context<Self>,
|
||||
) -> Vec<impl IntoElement> {
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let mut items = Vec::with_capacity(range.end - range.start);
|
||||
|
||||
for ix in range {
|
||||
if let Some(room) = rooms.get(ix) {
|
||||
let this = room.read(cx);
|
||||
let room_id = this.id;
|
||||
let handler = cx.listener({
|
||||
let id = this.id;
|
||||
move |this, _, window, cx| {
|
||||
this.open_room(id, window, cx);
|
||||
this.open_room(room_id, window, cx);
|
||||
}
|
||||
});
|
||||
|
||||
items.push(
|
||||
RoomListItem::new(ix, this.members[0])
|
||||
.avatar(this.display_image(proxy, cx))
|
||||
.name(this.display_name(cx))
|
||||
.created_at(this.ago())
|
||||
.kind(this.kind)
|
||||
.on_click(handler),
|
||||
RoomListItem::new(
|
||||
ix,
|
||||
room_id,
|
||||
this.members[0],
|
||||
this.display_name(cx),
|
||||
this.display_image(proxy, cx),
|
||||
this.ago(),
|
||||
this.kind,
|
||||
)
|
||||
.on_click(handler),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,16 +136,13 @@ impl UserProfile {
|
||||
|
||||
impl Render for UserProfile {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let profile = self.profile(cx);
|
||||
|
||||
let Ok(bech32) = profile.public_key().to_bech32();
|
||||
let shared_bech32 = SharedString::new(bech32);
|
||||
|
||||
v_flex()
|
||||
.px_4()
|
||||
.pt_8()
|
||||
.pb_4()
|
||||
.gap_4()
|
||||
.child(
|
||||
v_flex()
|
||||
|
||||
Reference in New Issue
Block a user