add group picture upload to new group dialog

This commit is contained in:
2026-10-10 09:50:56 +07:00
parent 1236f40d83
commit ea95756637
+92 -7
View File
@@ -2,19 +2,21 @@ use anyhow::Error;
use gpui::prelude::FluentBuilder;
use gpui::{
Anchor, App, AppContext, Context, Div, Entity, InteractiveElement as _, IntoElement,
ParentElement, Render, SharedString, StatefulInteractiveElement as _, Styled, Subscription,
Task, WeakEntity, Window, div, px,
ParentElement, PathPromptOptions, Render, SharedString, StatefulInteractiveElement as _,
Styled, Subscription, Task, WeakEntity, Window, div, px,
};
use nip29::{GroupCreateOptions, GroupKey, GroupsEvent, GroupsRegistry};
use nostr_sdk::prelude::*;
use state::NostrRegistry;
use settings::AppSettings;
use state::{NostrRegistry, upload};
use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
use ui::dock::{DockArea, DockPlacement, PanelHandle};
use ui::input::{Input, InputEvent, InputState, Textarea, TextareaState};
use ui::menu::{DropdownMenu, PopupMenuItem};
use ui::switch::Switch;
use ui::{Disableable, StyledExt, WindowExtension, h_flex, v_flex};
use ui::{Disableable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
const SUGGESTED_RELAY: &str = "wss://relay.ditto.pub";
const PRIVATE_DESC: &str = "Only members can read group messages";
@@ -47,6 +49,10 @@ struct NewGroup {
selected_relay: Option<RelayUrl>,
/// Whether the optional custom group id input is shown.
show_custom_id: bool,
/// The uploaded group picture, ready to be published with the metadata.
picture: Option<SharedString>,
/// Whether a picture upload is in flight.
uploading: bool,
private: bool,
closed: bool,
restricted: bool,
@@ -126,6 +132,8 @@ impl NewGroup {
relays,
selected_relay,
show_custom_id: false,
picture: None,
uploading: false,
private: false,
closed: false,
restricted: false,
@@ -206,7 +214,7 @@ impl NewGroup {
}
fn create(&mut self, cx: &mut Context<Self>) {
if self.creating {
if self.creating || self.uploading {
return;
}
@@ -244,6 +252,7 @@ impl NewGroup {
let options = GroupCreateOptions {
name,
about: (!about.is_empty()).then_some(about),
picture: self.picture.as_ref().map(|picture| picture.to_string()),
private: self.private,
closed: self.closed,
restricted: self.restricted,
@@ -282,6 +291,50 @@ impl NewGroup {
cx.notify();
}
fn set_uploading(&mut self, uploading: bool, cx: &mut Context<Self>) {
self.uploading = uploading;
cx.notify();
}
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let server = AppSettings::get_file_server(cx);
let path = cx.prompt_for_paths(PathPromptOptions {
files: true,
directories: false,
multiple: false,
prompt: None,
});
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
// Selecting no file means the prompt was cancelled
let Some(path) = path.await??.and_then(|mut paths| paths.pop()) else {
return Ok(());
};
this.update(cx, |this, cx| {
this.set_uploading(true, cx);
})?;
match upload(server, path, cx).await {
Ok(url) => {
this.update(cx, |this, cx| {
this.picture = Some(url.to_string().into());
this.set_uploading(false, cx);
})?;
}
Err(error) => {
this.update(cx, |this, cx| {
this.set_uploading(false, cx);
this.set_error(friendly_error(&error.to_string()), cx);
})?;
}
}
Ok(())
}));
}
fn set_error(&mut self, message: impl Into<SharedString>, cx: &mut Context<Self>) {
self.error = Some(message.into());
cx.notify();
@@ -450,13 +503,24 @@ impl Render for NewGroup {
.selected_relay
.as_ref()
.map(relay_label)
.unwrap_or_else(|| "Custom relay...".into());
.unwrap_or_else(|| "Custom relay…".into());
let web_url = self
.effective_relay(cx)
.ok()
.map(|relay| relay_web_url(&relay));
// The avatar fallback is seeded with the id, falling back to the name.
let id = self.id_input.read(cx).value().trim().to_lowercase();
let name = self.name_input.read(cx).value().trim().to_owned();
let seed: String = if !id.is_empty() {
id
} else if !name.is_empty() {
name
} else {
"group".to_owned()
};
v_flex()
.gap_3()
.child(
@@ -467,6 +531,27 @@ impl Render for NewGroup {
"Give your new group a name and description. You can always change these later.",
),
)
.child(
h_flex()
.items_center()
.gap_3()
.child(Avatar::new(self.picture.clone()).seed(seed).large())
.child(
Button::new("upload-picture")
.label(if self.picture.is_some() {
"Change picture"
} else {
"Add a picture"
})
.xsmall()
.ghost()
.disabled(self.uploading)
.loading(self.uploading)
.on_click(cx.listener(|this, _event, window, cx| {
this.upload(window, cx);
})),
),
)
.child(field("Group Name", Input::new(&self.name_input), cx))
.when(self.show_custom_id, |this| {
this.child(
@@ -520,7 +605,7 @@ impl Render for NewGroup {
.primary()
.font_semibold()
.loading(self.creating)
.disabled(self.creating)
.disabled(self.creating || self.uploading)
.on_click(cx.listener(|this, _event, _window, cx| this.create(cx))),
),
)