ui: reactions closer approximation of iOS design

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-08-25 20:00:17 -04:00
parent 30af03cfcc
commit 529377a706
4 changed files with 138 additions and 111 deletions

View File

@@ -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,80 +540,86 @@ 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(),
)
.size(24.0)
.sense(Sense::click()),
);
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))
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.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);
}
});
});
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);
if let Some(note_action) = resp.action {
action = Some(note_action);
}
});
});
notedeck_ui::hline(ui);
RenderEntryResponse::Success(action)
}

View File

@@ -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 {

View File

@@ -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);
ui.horizontal_wrapped(|ui| 's: {
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());
if note_reply.reply().is_none() {
break 's;
}
note_action = reply_desc(
if !self.flags.contains(NoteOptions::NotificationPreview) {
NoteView::note_header(
ui,
txn,
&note_reply,
self.note_context,
self.note_context.i18n,
self.note,
profile,
self.flags,
self.jobs,
)
.or(note_action.take());
});
);
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());
if note_reply.reply().is_none() {
return;
}
note_action = reply_desc(
ui,
txn,
&note_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 {

View File

@@ -38,6 +38,9 @@ bitflags! {
/// no animation override (accessibility)
const NoAnimations = 1 << 17;
/// Styled for a notification preview
const NotificationPreview = 1 << 18;
}
}