diff --git a/assets/icons/info.svg b/assets/icons/info.svg
new file mode 100644
index 0000000..1b3641a
--- /dev/null
+++ b/assets/icons/info.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/app/src/states/chat/room.rs b/crates/app/src/states/chat/room.rs
index 72769dd..c67a9bb 100644
--- a/crates/app/src/states/chat/room.rs
+++ b/crates/app/src/states/chat/room.rs
@@ -11,6 +11,12 @@ pub struct Member {
metadata: Metadata,
}
+impl PartialEq for Member {
+ fn eq(&self, other: &Self) -> bool {
+ self.public_key() == other.public_key()
+ }
+}
+
impl Member {
pub fn new(public_key: PublicKey, metadata: Metadata) -> Self {
Self {
diff --git a/crates/app/src/views/chat/message.rs b/crates/app/src/views/chat/message.rs
index 80f42c5..d48de6d 100644
--- a/crates/app/src/views/chat/message.rs
+++ b/crates/app/src/views/chat/message.rs
@@ -15,6 +15,16 @@ pub struct Message {
ago: SharedString,
}
+impl PartialEq for Message {
+ fn eq(&self, other: &Self) -> bool {
+ let content = self.content == other.content;
+ let member = self.member == other.member;
+ let ago = self.ago == other.ago;
+
+ content && member && ago
+ }
+}
+
impl Message {
pub fn new(member: Member, content: SharedString, ago: SharedString) -> Self {
Self {
diff --git a/crates/app/src/views/chat/mod.rs b/crates/app/src/views/chat/mod.rs
index e384f05..573320c 100644
--- a/crates/app/src/views/chat/mod.rs
+++ b/crates/app/src/views/chat/mod.rs
@@ -17,7 +17,7 @@ use nostr_sdk::prelude::*;
use smol::fs;
use tokio::sync::oneshot;
use ui::{
- button::{Button, ButtonVariants},
+ button::{Button, ButtonRounded, ButtonVariants},
dock_area::{
panel::{Panel, PanelEvent},
state::PanelState,
@@ -26,7 +26,7 @@ use ui::{
popup_menu::PopupMenu,
prelude::FluentBuilder,
theme::{scale::ColorScaleStep, ActiveTheme},
- v_flex, Icon, IconName,
+ v_flex, ContextModal, Icon, IconName, Sizable,
};
mod message;
@@ -73,7 +73,6 @@ impl ChatPanel {
.appearance(false)
.text_size(ui::Size::Small)
.placeholder("Message...")
- .cleanable()
});
// List
@@ -235,7 +234,18 @@ impl ChatPanel {
.collect();
cx.update_model(&self.state, |model, cx| {
- model.items.extend(items);
+ let messages: Vec = items
+ .into_iter()
+ .filter_map(|new| {
+ if !model.items.iter().any(|old| old == &new) {
+ Some(new)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ model.items.extend(messages);
model.count = model.items.len();
cx.notify();
});
@@ -245,10 +255,17 @@ impl ChatPanel {
fn send_message(&mut self, view: WeakView, cx: &mut ViewContext) {
let room = self.room.read(cx);
let owner = room.owner.clone();
- let members = room.members.to_vec();
+ let mut members = room.members.to_vec();
+ members.push(owner.clone());
// Get message
let mut content = self.input.read(cx).text().to_string();
+
+ if content.is_empty() {
+ cx.push_notification("Message cannot be empty");
+ return;
+ }
+
// Get all attaches and merge with message
if let Some(attaches) = self.attaches.read(cx).as_ref() {
let merged = attaches
@@ -260,59 +277,68 @@ impl ChatPanel {
content = format!("{}\n{}", content, merged)
}
- // Async
- let async_state = self.state.clone();
- let mut async_cx = cx.to_async();
+ // Update input state
+ if let Some(input) = view.upgrade() {
+ cx.update_view(&input, |input, cx| {
+ input.set_loading(true, cx);
+ input.set_disabled(true, cx);
+ });
+ }
- cx.foreground_executor()
- .spawn(async move {
- // Send message to all members
- async_cx
- .background_executor()
- .spawn({
- let client = get_client();
- let content = content.clone().to_string();
- let tags: Vec = members
- .iter()
- .filter_map(|m| {
- if m.public_key() != owner.public_key() {
- Some(Tag::public_key(m.public_key()))
- } else {
- None
- }
- })
- .collect();
-
- async move {
- // Send message to all members
- for member in members.iter() {
- _ = client
- .send_private_msg(member.public_key(), &content, tags.clone())
- .await
+ cx.spawn(|this, mut async_cx| async move {
+ // Send message to all members
+ async_cx
+ .background_executor()
+ .spawn({
+ let client = get_client();
+ let content = content.clone().to_string();
+ let tags: Vec = members
+ .iter()
+ .filter_map(|m| {
+ if m.public_key() != owner.public_key() {
+ Some(Tag::public_key(m.public_key()))
+ } else {
+ None
}
+ })
+ .collect();
+
+ async move {
+ // Send message to all members
+ for member in members.iter() {
+ _ = client
+ .send_private_msg(member.public_key(), &content, tags.clone())
+ .await
}
+ }
+ })
+ .detach();
+
+ if let Some(view) = this.upgrade() {
+ _ = async_cx.update_view(&view, |this, cx| {
+ cx.update_model(&this.state, |model, cx| {
+ let message = Message::new(
+ owner,
+ content.to_string().into(),
+ message_time(Timestamp::now()).into(),
+ );
+
+ model.items.extend(vec![message]);
+ model.count = model.items.len();
+ cx.notify();
})
- .detach();
-
- _ = async_cx.update_model(&async_state, |model, cx| {
- let message = Message::new(
- owner,
- content.to_string().into(),
- message_time(Timestamp::now()).into(),
- );
-
- model.items.extend(vec![message]);
- model.count = model.items.len();
- cx.notify();
});
+ }
- if let Some(input) = view.upgrade() {
- _ = async_cx.update_view(&input, |input, cx| {
- input.set_text("", cx);
- });
- }
- })
- .detach();
+ if let Some(input) = view.upgrade() {
+ _ = async_cx.update_view(&input, |input, cx| {
+ input.set_loading(false, cx);
+ input.set_disabled(false, cx);
+ input.set_text("", cx);
+ });
+ }
+ })
+ .detach();
}
fn upload(&mut self, cx: &mut ViewContext) {
@@ -501,10 +527,23 @@ impl Render for ChatPanel {
div()
.flex_1()
.flex()
+ .items_center()
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
.rounded(px(cx.theme().radius))
- .px_2()
- .child(self.input.clone()),
+ .pl_2()
+ .pr_1()
+ .child(self.input.clone())
+ .child(
+ Button::new("send")
+ .ghost()
+ .xsmall()
+ .bold()
+ .rounded(ButtonRounded::Medium)
+ .label("SEND")
+ .on_click(cx.listener(|this, _, cx| {
+ this.send_message(this.input.downgrade(), cx)
+ })),
+ ),
),
),
),
diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs
index c6dc325..5fc046f 100644
--- a/crates/ui/src/button.rs
+++ b/crates/ui/src/button.rs
@@ -497,6 +497,7 @@ impl ButtonVariant {
_ => cx.theme().accent.step(cx, ColorScaleStep::ONE),
},
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE),
+ ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
ButtonVariant::Custom(colors) => colors.foreground,
_ => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
}
@@ -543,13 +544,14 @@ impl ButtonVariant {
fn hovered(&self, cx: &WindowContext) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
- ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
+ ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR),
ButtonVariant::Link => cx.theme().transparent,
ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Custom(colors) => colors.hover,
};
let border = self.border_color(cx);
let fg = match self {
+ ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
_ => self.text_color(cx),
};
diff --git a/crates/ui/src/divider.rs b/crates/ui/src/divider.rs
index 82771bf..2f82849 100644
--- a/crates/ui/src/divider.rs
+++ b/crates/ui/src/divider.rs
@@ -65,7 +65,7 @@ impl RenderOnce for Divider {
})
.bg(self
.color
- .unwrap_or(cx.theme().base.step(cx, ColorScaleStep::THREE))),
+ .unwrap_or(cx.theme().base.step(cx, ColorScaleStep::FIVE))),
)
.when_some(self.label, |this, label| {
this.child(
diff --git a/crates/ui/src/dock_area/dock.rs b/crates/ui/src/dock_area/dock.rs
index ebab635..01fb80d 100644
--- a/crates/ui/src/dock_area/dock.rs
+++ b/crates/ui/src/dock_area/dock.rs
@@ -274,7 +274,7 @@ impl Dock {
})
.child(
div()
- .bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
+ .bg(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
)
diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs
index a8fece9..ddc55b1 100644
--- a/crates/ui/src/input/input.rs
+++ b/crates/ui/src/input/input.rs
@@ -1389,16 +1389,12 @@ impl Render for TextInput {
.cursor_text()
.when(self.multi_line, |this| this.h_auto())
.when(self.appearance, |this| {
- this.bg(if self.disabled {
- cx.theme().transparent
- } else {
- cx.theme().base.step(cx, ColorScaleStep::THREE)
- })
- .rounded(px(cx.theme().radius))
- .when(cx.theme().shadow, |this| this.shadow_sm())
- .when(focused, |this| this.outline(cx))
- .when(prefix.is_none(), |this| this.input_pl(self.size))
- .when(suffix.is_none(), |this| this.input_pr(self.size))
+ this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
+ .rounded(px(cx.theme().radius))
+ .when(cx.theme().shadow, |this| this.shadow_sm())
+ .when(focused, |this| this.outline(cx))
+ .when(prefix.is_none(), |this| this.input_pl(self.size))
+ .when(suffix.is_none(), |this| this.input_pr(self.size))
})
.children(prefix)
.gap_1()
diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs
index 2baf943..73e6734 100644
--- a/crates/ui/src/notification.rs
+++ b/crates/ui/src/notification.rs
@@ -200,8 +200,11 @@ impl Notification {
.detach()
}
}
+
impl EventEmitter for Notification {}
+
impl FluentBuilder for Notification {}
+
impl Render for Notification {
fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
let closing = self.closing;
@@ -209,15 +212,15 @@ impl Render for Notification {
Some(icon) => icon,
None => match self.type_ {
NotificationType::Info => {
- Icon::new(IconName::Info).text_color(blue().step(cx, ColorScaleStep::FIVE))
+ Icon::new(IconName::Info).text_color(blue().step(cx, ColorScaleStep::NINE))
}
NotificationType::Error => {
- Icon::new(IconName::CircleX).text_color(red().step(cx, ColorScaleStep::FIVE))
+ Icon::new(IconName::CircleX).text_color(red().step(cx, ColorScaleStep::NINE))
}
NotificationType::Success => Icon::new(IconName::CircleCheck)
- .text_color(green().step(cx, ColorScaleStep::FIVE)),
+ .text_color(green().step(cx, ColorScaleStep::NINE)),
NotificationType::Warning => Icon::new(IconName::TriangleAlert)
- .text_color(yellow().step(cx, ColorScaleStep::FIVE)),
+ .text_color(yellow().step(cx, ColorScaleStep::NINE)),
},
};
@@ -226,16 +229,15 @@ impl Render for Notification {
.group("")
.occlude()
.relative()
- .w_96()
+ .w_72()
.border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.bg(cx.theme().background)
- .rounded_md()
+ .rounded(px(cx.theme().radius))
.shadow_md()
- .py_2()
- .px_4()
+ .p_2()
.gap_3()
- .child(div().absolute().top_3().left_4().child(icon))
+ .child(div().absolute().top_3().left_2().child(icon))
.child(
v_flex()
.pl_6()