commit 529377a706afefe142735311c9242e4d7259577c
parent 30af03cfccc25889c7bba41b9db3ca2bed71ccbd
Author: kernelkind <kernelkind@gmail.com>
Date: Mon, 25 Aug 2025 20:00:17 -0400
ui: reactions closer approximation of iOS design
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
4 files changed, 136 insertions(+), 109 deletions(-)
diff --git a/crates/notedeck_columns/src/ui/timeline.rs b/crates/notedeck_columns/src/ui/timeline.rs
@@ -1,5 +1,5 @@
use egui::containers::scroll_area::ScrollBarVisibility;
-use egui::{vec2, Direction, Layout, Pos2, ScrollArea, Sense, Stroke};
+use egui::{vec2, Direction, Layout, Margin, Pos2, ScrollArea, Sense, Stroke};
use egui_tabs::TabColor;
use enostr::Pubkey;
use nostrdb::{ProfileRecord, Transaction};
@@ -7,7 +7,7 @@ use notedeck::name::get_display_name;
use notedeck::ui::is_narrow;
use notedeck::{JobsCache, Muted, NoteRef};
use notedeck_ui::app_images::like_image;
-use notedeck_ui::{padding, ProfilePic};
+use notedeck_ui::ProfilePic;
use std::f32::consts::PI;
use tracing::{error, warn};
@@ -540,79 +540,85 @@ fn render_reaction_cluster(
let num_profiles_other = profiles_to_show.len() - 1;
let mut action = None;
- padding(8.0, ui, |ui| {
- ui.allocate_ui_with_layout(
- vec2(ui.available_width(), 32.0),
- Layout::left_to_right(egui::Align::Center),
- |ui| {
- ui.vertical(|ui| {
+ egui::Frame::new()
+ .inner_margin(Margin::symmetric(8, 4))
+ .show(ui, |ui| {
+ ui.allocate_ui_with_layout(
+ vec2(ui.available_width(), 32.0),
+ Layout::left_to_right(egui::Align::Center),
+ |ui| {
+ ui.vertical(|ui| {
+ ui.add_space(4.0);
+ ui.add_sized(vec2(28.0, 28.0), like_image());
+ });
+
ui.add_space(16.0);
- ui.add_sized(vec2(32.0, 32.0), like_image());
- });
- ui.add_space(16.0);
-
- ui.horizontal(|ui| {
- ScrollArea::horizontal()
- .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
- .show(ui, |ui| {
- for entry in profiles_to_show {
- let resp = ui.add(
- &mut ProfilePic::from_profile_or_default(
- note_context.img_cache,
- entry.record.as_ref(),
- )
- .sense(Sense::click()),
- );
-
- if resp.clicked() {
- action = Some(NoteAction::Profile(*entry.pk))
+ ui.horizontal(|ui| {
+ ScrollArea::horizontal()
+ .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
+ .show(ui, |ui| {
+ for entry in profiles_to_show {
+ let resp = ui.add(
+ &mut ProfilePic::from_profile_or_default(
+ note_context.img_cache,
+ entry.record.as_ref(),
+ )
+ .size(24.0)
+ .sense(Sense::click()),
+ );
+
+ if resp.clicked() {
+ action = Some(NoteAction::Profile(*entry.pk))
+ }
}
- }
- });
- });
- },
- );
-
- let note_type_desc = if note_context
- .accounts
- .get_selected_account()
- .key
- .pubkey
- .bytes()
- != reacted_to_note.pubkey()
- {
- "note you were tagged in"
- } else {
- "your note"
- };
+ });
+ });
+ },
+ );
+
+ let note_type_desc = if note_context
+ .accounts
+ .get_selected_account()
+ .key
+ .pubkey
+ .bytes()
+ != reacted_to_note.pubkey()
+ {
+ "note you were tagged in"
+ } else {
+ "your note"
+ };
- ui.add_space(2.0);
- ui.horizontal(|ui| {
- ui.add_space(52.0);
+ ui.add_space(2.0);
+ ui.horizontal(|ui| {
+ ui.add_space(52.0);
- ui.horizontal_wrapped(|ui| {
- if num_profiles_other > 0 {
- ui.label(format!(
+ ui.horizontal_wrapped(|ui| {
+ if num_profiles_other > 0 {
+ ui.label(format!(
"{first_name} and {num_profiles_other} others reacted to {note_type_desc}",
));
- } else {
- ui.label(format!("{first_name} reacted to {note_type_desc}"));
- }
+ } else {
+ ui.label(format!("{first_name} reacted to {note_type_desc}"));
+ }
+ });
});
- });
- ui.add_space(16.0);
+ ui.add_space(16.0);
- ui.horizontal(|ui| {
- ui.add_space(48.0);
- let resp = NoteView::new(note_context, &reacted_to_note, note_options, jobs).show(ui);
+ ui.horizontal(|ui| {
+ ui.add_space(48.0);
+ let options = note_options
+ .difference(NoteOptions::ActionBar | NoteOptions::OptionsButton)
+ .union(NoteOptions::NotificationPreview);
+ let resp = NoteView::new(note_context, &reacted_to_note, options, jobs).show(ui);
- if let Some(note_action) = resp.action {
- action = Some(note_action);
- }
+ if let Some(note_action) = resp.action {
+ action = Some(note_action);
+ }
+ });
});
- });
notedeck_ui::hline(ui);
RenderEntryResponse::Success(action)
diff --git a/crates/notedeck_ui/src/note/contents.rs b/crates/notedeck_ui/src/note/contents.rs
@@ -334,14 +334,14 @@ fn render_undecorated_note_contents<'a>(
.selectable(selectable),
);
} else {
- ui.add(
- Label::new(
- RichText::new(block_str)
- .text_style(NotedeckTextStyle::NoteBody.text_style()),
- )
- .wrap()
- .selectable(selectable),
- );
+ let mut richtext = RichText::new(block_str)
+ .text_style(NotedeckTextStyle::NoteBody.text_style());
+
+ if options.contains(NoteOptions::NotificationPreview) {
+ richtext = richtext.color(egui::Color32::from_rgb(0x87, 0x87, 0x8D));
+ }
+
+ ui.add(Label::new(richtext).wrap().selectable(selectable));
}
// don't render any more blocks
if truncate {
diff --git a/crates/notedeck_ui/src/note/mod.rs b/crates/notedeck_ui/src/note/mod.rs
@@ -426,16 +426,19 @@ impl<'a, 'd> NoteView<'a, 'd> {
) -> egui::InnerResponse<NoteUiResponse> {
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
let mut note_action: Option<NoteAction> = None;
- let pfp_rect = ui
- .horizontal(|ui| {
+ let mut pfp_rect = None;
+
+ if !self.flags.contains(NoteOptions::NotificationPreview) {
+ ui.horizontal(|ui| {
let pfp_resp = self.pfp(note_key, profile, ui);
- let pfp_rect = pfp_resp.bounding_rect;
+ pfp_rect = Some(pfp_resp.bounding_rect);
note_action = pfp_resp
.into_action(self.note.pubkey())
.or(note_action.take());
let size = ui.available_size();
- ui.vertical(|ui| 's: {
+
+ ui.vertical(|ui| {
ui.add_sized(
[size.x, self.options().pfp_size() as f32],
|ui: &mut egui::Ui| {
@@ -460,7 +463,7 @@ impl<'a, 'd> NoteView<'a, 'd> {
.borrow(self.note.tags());
if note_reply.reply().is_none() {
- break 's;
+ return;
}
ui.horizontal_wrapped(|ui| {
@@ -477,10 +480,8 @@ impl<'a, 'd> NoteView<'a, 'd> {
.or(note_action.take());
});
});
-
- pfp_rect
- })
- .inner;
+ });
+ }
let mut contents =
NoteContents::new(self.note_context, txn, self.note, self.flags, self.jobs);
@@ -530,37 +531,51 @@ impl<'a, 'd> NoteView<'a, 'd> {
) -> egui::InnerResponse<NoteUiResponse> {
// main design
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
- let pfp_resp = self.pfp(note_key, profile, ui);
- let pfp_rect = pfp_resp.bounding_rect;
- let mut note_action: Option<NoteAction> = pfp_resp.into_action(self.note.pubkey());
+ let (mut note_action, pfp_rect) =
+ if self.flags.contains(NoteOptions::NotificationPreview) {
+ // do not render pfp
+ (None, None)
+ } else {
+ let pfp_resp = self.pfp(note_key, profile, ui);
+ let pfp_rect = pfp_resp.bounding_rect;
+ (pfp_resp.into_action(self.note.pubkey()), Some(pfp_rect))
+ };
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
- NoteView::note_header(ui, self.note_context.i18n, self.note, profile, self.flags);
+ if !self.flags.contains(NoteOptions::NotificationPreview) {
+ NoteView::note_header(
+ ui,
+ self.note_context.i18n,
+ self.note,
+ profile,
+ self.flags,
+ );
- ui.horizontal_wrapped(|ui| 's: {
- ui.spacing_mut().item_spacing.x = 1.0;
+ ui.horizontal_wrapped(|ui| {
+ ui.spacing_mut().item_spacing.x = 1.0;
- let note_reply = self
- .note_context
- .note_cache
- .cached_note_or_insert_mut(note_key, self.note)
- .reply
- .borrow(self.note.tags());
+ let note_reply = self
+ .note_context
+ .note_cache
+ .cached_note_or_insert_mut(note_key, self.note)
+ .reply
+ .borrow(self.note.tags());
- if note_reply.reply().is_none() {
- break 's;
- }
+ if note_reply.reply().is_none() {
+ return;
+ }
- note_action = reply_desc(
- ui,
- txn,
- ¬e_reply,
- self.note_context,
- self.flags,
- self.jobs,
- )
- .or(note_action.take());
- });
+ note_action = reply_desc(
+ ui,
+ txn,
+ ¬e_reply,
+ self.note_context,
+ self.flags,
+ self.jobs,
+ )
+ .or(note_action.take());
+ });
+ }
let mut contents =
NoteContents::new(self.note_context, txn, self.note, self.flags, self.jobs);
@@ -639,9 +654,12 @@ impl<'a, 'd> NoteView<'a, 'd> {
.then_some(NoteAction::note(NoteId::new(*self.note.id())))
.or(note_action);
- NoteResponse::new(response.response)
- .with_action(note_action)
- .with_pfp(note_ui_resp.pfp_rect)
+ let mut resp = NoteResponse::new(response.response).with_action(note_action);
+ if let Some(pfp_rect) = note_ui_resp.pfp_rect {
+ resp = resp.with_pfp(pfp_rect);
+ }
+
+ resp
}
}
@@ -687,7 +705,7 @@ fn get_reposted_note<'a>(ndb: &Ndb, txn: &'a Transaction, note: &Note) -> Option
struct NoteUiResponse {
action: Option<NoteAction>,
- pfp_rect: egui::Rect,
+ pfp_rect: Option<egui::Rect>,
}
struct PfpResponse {
diff --git a/crates/notedeck_ui/src/note/options.rs b/crates/notedeck_ui/src/note/options.rs
@@ -38,6 +38,9 @@ bitflags! {
/// no animation override (accessibility)
const NoAnimations = 1 << 17;
+
+ /// Styled for a notification preview
+ const NotificationPreview = 1 << 18;
}
}