mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-12 09:48:55 +00:00
Build 51: fit the balance, center the send flow, fix the success logo
On-device review surfaced UI overflow/centering issues at phone width: - Balance hero shrinks to fit: amount_text_centered_ink measures the text and scales the font down when it would run off the edge (e.g. 0.47520721ツ). - Send flow sits closer to the screen edges (smaller side gutters) and is properly centered: the Review/Confirm hero now uses a centered, wrapping layout instead of a fragile manual offset that clipped a long npub and threw off the centering. info_row values truncate instead of overflowing. - Recipient picker: the suggested row shows the full npub in the grey subtitle (truncated to fit) instead of repeating the same shortened npub as the title. - Success screen uses the goblin head (goblin-logo2), not the mascot mask. Verified live at 394px across balance, recipient, review, confirm and the success screen.
This commit is contained in:
@@ -236,6 +236,16 @@ pub fn short_npub(hex: &str) -> String {
|
||||
format!("{}…", &hex[..8.min(hex.len())])
|
||||
}
|
||||
|
||||
/// Full bech32 npub (no truncation), for the recipient picker's grey subtitle
|
||||
/// where showing the complete key is more useful than repeating the truncation.
|
||||
pub fn full_npub(hex: &str) -> String {
|
||||
use nostr_sdk::{PublicKey, ToBech32};
|
||||
PublicKey::from_hex(hex)
|
||||
.ok()
|
||||
.and_then(|pk| pk.to_bech32().ok())
|
||||
.unwrap_or_else(|| hex.to_string())
|
||||
}
|
||||
|
||||
/// Build the activity feed for a wallet, newest first.
|
||||
pub fn activity_items(wallet: &Wallet) -> Vec<ActivityItem> {
|
||||
let data = match wallet.get_data() {
|
||||
|
||||
@@ -206,8 +206,8 @@ impl SendFlow {
|
||||
t.bg
|
||||
},
|
||||
inner_margin: egui::Margin {
|
||||
left: (View::far_left_inset_margin(ui) + 20.0) as i8,
|
||||
right: (View::get_right_inset() + 20.0) as i8,
|
||||
left: (View::far_left_inset_margin(ui) + 12.0) as i8,
|
||||
right: (View::get_right_inset() + 12.0) as i8,
|
||||
top: (View::get_top_inset() + 12.0) as i8,
|
||||
bottom: (View::get_bottom_inset() + 12.0) as i8,
|
||||
},
|
||||
@@ -383,7 +383,7 @@ impl SendFlow {
|
||||
if w::activity_row(
|
||||
ui,
|
||||
&name,
|
||||
&short_npub(&npub),
|
||||
&data::full_npub(&npub),
|
||||
hue,
|
||||
"",
|
||||
false,
|
||||
@@ -936,16 +936,11 @@ impl SendFlow {
|
||||
} else {
|
||||
format!("You're sending {}", recipient.name)
|
||||
};
|
||||
let galley = ui.painter().layout_no_wrap(
|
||||
label.clone(),
|
||||
FontId::new(14.0, fonts::regular()),
|
||||
t.text_dim,
|
||||
);
|
||||
let row_w = 36.0 + 8.0 + galley.size().x;
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(((ui.available_width() - row_w) / 2.0).max(0.0));
|
||||
w::avatar_any(ui, &recipient.name, 36.0, recipient.hue, hero_tex.as_ref());
|
||||
ui.add_space(8.0);
|
||||
// Centered avatar + caption. A long counterparty (a bare npub) wraps
|
||||
// and stays centered instead of overflowing the card.
|
||||
ui.vertical_centered(|ui| {
|
||||
w::avatar_any(ui, &recipient.name, 40.0, recipient.hue, hero_tex.as_ref());
|
||||
ui.add_space(6.0);
|
||||
ui.label(
|
||||
RichText::new(label)
|
||||
.font(FontId::new(14.0, fonts::regular()))
|
||||
@@ -1157,7 +1152,7 @@ impl SendFlow {
|
||||
let (rect, _) = ui.allocate_exact_size(Vec2::splat(120.0), Sense::hover());
|
||||
ui.painter()
|
||||
.circle_filled(rect.center(), 60.0, t.accent_ink);
|
||||
let img = egui::Image::new(egui::include_image!("../../../../img/goblin-mask-128.png"))
|
||||
let img = egui::Image::new(egui::include_image!("../../../../img/goblin-logo2.svg"))
|
||||
.tint(t.accent)
|
||||
.fit_to_exact_size(Vec2::splat(72.0));
|
||||
img.paint_at(
|
||||
|
||||
@@ -107,15 +107,27 @@ pub fn amount_text_centered_ink(
|
||||
num_ink: Color32,
|
||||
mark_ink: Color32,
|
||||
) {
|
||||
let num =
|
||||
ui.painter()
|
||||
.layout_no_wrap(value.to_string(), FontId::new(size, fonts::bold()), num_ink);
|
||||
let mark = ui.painter().layout_no_wrap(
|
||||
TSU.to_string(),
|
||||
FontId::new(size * 0.4, fonts::medium()),
|
||||
mark_ink,
|
||||
);
|
||||
let total = num.size().x + 1.0 + mark.size().x;
|
||||
let avail = ui.available_width();
|
||||
let measure = |ui: &Ui, sz: f32| -> f32 {
|
||||
let num =
|
||||
ui.painter()
|
||||
.layout_no_wrap(value.to_string(), FontId::new(sz, fonts::bold()), num_ink);
|
||||
let mark = ui.painter().layout_no_wrap(
|
||||
TSU.to_string(),
|
||||
FontId::new(sz * 0.4, fonts::medium()),
|
||||
mark_ink,
|
||||
);
|
||||
num.size().x + 1.0 + mark.size().x
|
||||
};
|
||||
// Shrink to fit: a long balance (e.g. 0.46520721ツ) must not run off the
|
||||
// edge. Glyph width is ~linear in font size, so scale down to the available
|
||||
// width with a small margin and a sane floor.
|
||||
let mut size = size;
|
||||
let total0 = measure(ui, size);
|
||||
if total0 > avail && total0 > 1.0 {
|
||||
size = (size * (avail / total0) * 0.97).clamp(14.0, size);
|
||||
}
|
||||
let total = measure(ui, size);
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.add_space(((ui.available_width() - total) / 2.0).max(0.0));
|
||||
@@ -426,15 +438,23 @@ pub fn activity_row(
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(2.0);
|
||||
ui.label(
|
||||
RichText::new(title)
|
||||
.font(FontId::new(15.0, fonts::semibold()))
|
||||
.color(t.text),
|
||||
ui.add(
|
||||
egui::Label::new(
|
||||
RichText::new(title)
|
||||
.font(FontId::new(15.0, fonts::semibold()))
|
||||
.color(t.text),
|
||||
)
|
||||
.truncate(),
|
||||
);
|
||||
ui.label(
|
||||
RichText::new(subtitle)
|
||||
.font(FontId::new(13.0, fonts::regular()))
|
||||
.color(t.text_dim),
|
||||
// Single-line, truncated: keeps the fixed-height row tidy even when
|
||||
// the subtitle is a long value (e.g. a full npub in the picker).
|
||||
ui.add(
|
||||
egui::Label::new(
|
||||
RichText::new(subtitle)
|
||||
.font(FontId::new(13.0, fonts::regular()))
|
||||
.color(t.text_dim),
|
||||
)
|
||||
.truncate(),
|
||||
);
|
||||
});
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
@@ -481,10 +501,15 @@ pub fn info_row(ui: &mut Ui, label: &str, value: &str) {
|
||||
.color(t.text_dim),
|
||||
);
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
ui.label(
|
||||
RichText::new(value)
|
||||
.font(FontId::new(15.0, fonts::semibold()))
|
||||
.color(t.text),
|
||||
// Truncate so a long value (e.g. "Encrypted nostr DM over Nym") never
|
||||
// runs past the edge or collides with the label on a narrow screen.
|
||||
ui.add(
|
||||
egui::Label::new(
|
||||
RichText::new(value)
|
||||
.font(FontId::new(15.0, fonts::semibold()))
|
||||
.color(t.text),
|
||||
)
|
||||
.truncate(),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -677,9 +702,9 @@ pub fn fill_bg(ui: &Ui, color: Color32) {
|
||||
/// would start the row a single line tall, so a `ScrollArea` inside would
|
||||
/// clip everything below the first widget.
|
||||
pub fn centered_column<R>(ui: &mut Ui, width: f32, add: impl FnOnce(&mut Ui) -> R) -> R {
|
||||
// Always keep a side gutter so content never runs flush to the screen
|
||||
// edge on phones (where `width` exceeds the available width).
|
||||
const MIN_SIDE_PAD: f32 = 18.0;
|
||||
// Keep a small side gutter so content sits close to the screen edges on
|
||||
// phones (where `width` exceeds the available width) without running flush.
|
||||
const MIN_SIDE_PAD: f32 = 8.0;
|
||||
let avail = ui.available_width();
|
||||
let w = width.min(avail - MIN_SIDE_PAD * 2.0).max(0.0);
|
||||
let margin = ((avail - w) / 2.0).max(MIN_SIDE_PAD);
|
||||
|
||||
Reference in New Issue
Block a user