From cb8a34894567bb3694cd2b477e20840774f86eaa Mon Sep 17 00:00:00 2001 From: reya Date: Thu, 6 Feb 2025 14:38:05 +0700 Subject: [PATCH] feat: add empty state to some elements --- crates/app/src/views/sidebar/compose.rs | 169 ++++++++++++++---------- crates/app/src/views/sidebar/inbox.rs | 29 +++- crates/app/src/views/sidebar/mod.rs | 11 +- crates/common/src/profile.rs | 12 +- 4 files changed, 139 insertions(+), 82 deletions(-) diff --git a/crates/app/src/views/sidebar/compose.rs b/crates/app/src/views/sidebar/compose.rs index ec1611f..ae70956 100644 --- a/crates/app/src/views/sidebar/compose.rs +++ b/crates/app/src/views/sidebar/compose.rs @@ -5,9 +5,9 @@ use common::{ utils::{random_name, room_hash}, }; use gpui::{ - div, img, impl_internal_actions, prelude::FluentBuilder, px, uniform_list, App, AppContext, - Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement, Render, - SharedString, StatefulInteractiveElement, Styled, Window, + div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App, + AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement, + Render, SharedString, StatefulInteractiveElement, Styled, TextAlign, Window, }; use nostr_sdk::prelude::*; use serde::Deserialize; @@ -319,73 +319,104 @@ impl Render for Compose { ) .map(|this| { if let Some(contacts) = self.contacts.read(cx).clone() { - this.child( - uniform_list( - cx.entity().clone(), - "contacts", - contacts.len(), - move |this, range, _window, cx| { - let selected = this.selected.read(cx); - let mut items = Vec::new(); - - for ix in range { - let item = contacts.get(ix).unwrap().clone(); - let is_select = selected.contains(&item.public_key()); - - items.push( - div() - .id(ix) - .w_full() - .h_9() - .px_2() - .flex() - .items_center() - .justify_between() - .child( - div() - .flex() - .items_center() - .gap_2() - .text_xs() - .child( - div().flex_shrink_0().child( - img(item.avatar()).size_6(), - ), - ) - .child(item.name()), - ) - .when(is_select, |this| { - this.child( - Icon::new(IconName::CircleCheck) - .size_3() - .text_color(cx.theme().base.step( - cx, - ColorScaleStep::TWELVE, - )), - ) - }) - .hover(|this| { - this.bg(cx - .theme() - .base - .step(cx, ColorScaleStep::THREE)) - }) - .on_click(move |_, window, cx| { - window.dispatch_action( - Box::new(SelectContact( - item.public_key(), - )), - cx, - ); - }), - ); - } - - items - }, + if contacts.is_empty() { + this.child( + div() + .w_full() + .h_24() + .flex() + .flex_col() + .items_center() + .justify_center() + .text_align(TextAlign::Center) + .child( + div() + .text_xs() + .font_semibold() + .line_height(relative(1.2)) + .child("No contacts"), + ) + .child( + div() + .text_xs() + .text_color( + cx.theme() + .base + .step(cx, ColorScaleStep::ELEVEN), + ) + .child("Your recently contacts will appear here."), + ), ) - .h(px(300.)), - ) + } else { + this.child( + uniform_list( + cx.entity().clone(), + "contacts", + contacts.len(), + move |this, range, _window, cx| { + let selected = this.selected.read(cx); + let mut items = Vec::new(); + + for ix in range { + let item = contacts.get(ix).unwrap().clone(); + let is_select = + selected.contains(&item.public_key()); + + items.push( + div() + .id(ix) + .w_full() + .h_9() + .px_2() + .flex() + .items_center() + .justify_between() + .child( + div() + .flex() + .items_center() + .gap_2() + .text_xs() + .child(div().flex_shrink_0().child( + img(item.avatar()).size_6(), + )) + .child(item.name()), + ) + .when(is_select, |this| { + this.child( + Icon::new(IconName::CircleCheck) + .size_3() + .text_color( + cx.theme().base.step( + cx, + ColorScaleStep::TWELVE, + ), + ), + ) + }) + .hover(|this| { + this.bg(cx + .theme() + .base + .step(cx, ColorScaleStep::THREE)) + }) + .on_click(move |_, window, cx| { + window.dispatch_action( + Box::new(SelectContact( + item.public_key(), + )), + cx, + ); + }), + ); + } + + items + }, + ) + .min_h(px(300.)), + ) + } } else { this.flex() .items_center() diff --git a/crates/app/src/views/sidebar/inbox.rs b/crates/app/src/views/sidebar/inbox.rs index 685d15c..375843e 100644 --- a/crates/app/src/views/sidebar/inbox.rs +++ b/crates/app/src/views/sidebar/inbox.rs @@ -2,8 +2,9 @@ use crate::views::app::{AddPanel, PanelKind}; use chat_state::registry::ChatRegistry; use common::utils::message_ago; use gpui::{ - div, img, percentage, prelude::FluentBuilder, px, Context, InteractiveElement, IntoElement, - ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window, + div, img, percentage, prelude::FluentBuilder, px, relative, Context, InteractiveElement, + IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, + TextAlign, Window, }; use ui::{ dock_area::dock::DockPlacement, @@ -47,6 +48,30 @@ impl Inbox { if inbox.is_loading { this.children(self.render_skeleton(5)) + } else if inbox.rooms.is_empty() { + this.px_1() + .w_full() + .h_20() + .flex() + .flex_col() + .items_center() + .justify_center() + .text_align(TextAlign::Center) + .rounded(px(cx.theme().radius)) + .bg(cx.theme().base.step(cx, ColorScaleStep::THREE)) + .child( + div() + .text_xs() + .font_semibold() + .line_height(relative(1.2)) + .child("No chats"), + ) + .child( + div() + .text_xs() + .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) + .child("Recent chats will appear here."), + ) } else { this.children(inbox.rooms.iter().map(|model| { let room = model.read(cx); diff --git a/crates/app/src/views/sidebar/mod.rs b/crates/app/src/views/sidebar/mod.rs index f7ed93f..0f6b761 100644 --- a/crates/app/src/views/sidebar/mod.rs +++ b/crates/app/src/views/sidebar/mod.rs @@ -2,15 +2,14 @@ use crate::views::sidebar::inbox::Inbox; use chat_state::registry::ChatRegistry; use compose::Compose; use gpui::{ - div, px, AnyElement, App, AppContext, BorrowAppContext, Context, Entity, EntityId, - EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, - SharedString, StatefulInteractiveElement, Styled, Window, + div, px, AnyElement, App, AppContext, BorrowAppContext, Context, Entity, EventEmitter, + FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, + StatefulInteractiveElement, Styled, Window, }; use ui::{ button::{Button, ButtonRounded, ButtonVariants}, dock_area::panel::{Panel, PanelEvent}, popup_menu::PopupMenu, - scroll::ScrollbarAxis, theme::{scale::ColorScaleStep, ActiveTheme}, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt, }; @@ -30,7 +29,6 @@ pub struct Sidebar { focus_handle: FocusHandle, // Dock inbox: Entity, - view_id: EntityId, } impl Sidebar { @@ -46,7 +44,6 @@ impl Sidebar { closable: true, zoomable: true, focus_handle: cx.focus_handle(), - view_id: cx.entity().entity_id(), inbox, } } @@ -125,7 +122,7 @@ impl Focusable for Sidebar { impl Render for Sidebar { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() - .scrollable(self.view_id, ScrollbarAxis::Vertical) + .w_full() .py_3() .gap_3() .child( diff --git a/crates/common/src/profile.rs b/crates/common/src/profile.rs index f45f5a5..f3ae832 100644 --- a/crates/common/src/profile.rs +++ b/crates/common/src/profile.rs @@ -29,10 +29,14 @@ impl NostrProfile { /// Get contact's avatar pub fn avatar(&self) -> String { if let Some(picture) = &self.metadata.picture { - format!( - "{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1", - IMAGE_SERVICE, picture - ) + if picture.len() > 1 { + format!( + "{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1", + IMAGE_SERVICE, picture + ) + } else { + "brand/avatar.png".into() + } } else { "brand/avatar.png".into() }