1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-30 03:18:20 +00:00

feat(core/ui): tweak blob first page appearance

This commit adds a margin and footer description to the first page of
the paginated blobs to be confirmed on Mercury. It also extracts the
part of confirm_blob that deals with the first page to a separate
function in order to keep confirm_blob simple.
This commit is contained in:
Ioan Bizău 2024-11-14 17:33:39 +01:00 committed by Martin Milata
parent 929ffa73bd
commit 9c918aaeb8
11 changed files with 130 additions and 60 deletions

View File

@ -0,0 +1 @@
[T3T1] Add margin to the first page of a blob.

View File

@ -194,6 +194,7 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_address;
MP_QSTR_confirm_backup;
MP_QSTR_confirm_blob;
MP_QSTR_confirm_blob_intro;
MP_QSTR_confirm_coinjoin;
MP_QSTR_confirm_emphasized;
MP_QSTR_confirm_fido;
@ -359,7 +360,6 @@ static void _librust_qstrs(void) {
MP_QSTR_notification_level;
MP_QSTR_page_count;
MP_QSTR_page_counter;
MP_QSTR_page_limit;
MP_QSTR_pages;
MP_QSTR_paint;
MP_QSTR_passphrase__access_wallet;

View File

@ -89,6 +89,7 @@ pub struct Frame<T> {
swipe: SwipeConfig,
internal_page_cnt: usize,
horizontal_swipe: HorizontalSwipe,
margin: usize,
}
pub enum FrameMsg<T> {
@ -111,6 +112,7 @@ where
swipe: SwipeConfig::new(),
internal_page_cnt: 1,
horizontal_swipe: HorizontalSwipe::new(),
margin: 0,
}
}
@ -262,12 +264,18 @@ where
..self
}
}
pub fn with_vertical_pages(self) -> Self {
Self {
swipe: self.swipe.with_vertical_pages(),
..self
}
}
pub fn with_margin(mut self, margin: usize) -> Self {
self.margin = margin;
self
}
}
impl<T> Component for Frame<T>
@ -278,7 +286,7 @@ where
fn place(&mut self, bounds: Rect) -> Rect {
self.bounds = bounds;
let content_area = frame_place(&mut self.header, &mut self.footer, bounds);
let content_area = frame_place(&mut self.header, &mut self.footer, bounds, self.margin);
self.content.place(content_area);
@ -347,9 +355,16 @@ fn frame_event(
header.event(ctx, event)
}
fn frame_place(header: &mut Header, footer: &mut Option<Footer>, bounds: Rect) -> Rect {
fn frame_place(
header: &mut Header,
footer: &mut Option<Footer>,
bounds: Rect,
margin: usize,
) -> Rect {
let (mut header_area, mut content_area) = bounds.split_top(TITLE_HEIGHT);
content_area = content_area.inset(Insets::top(theme::SPACING));
content_area = content_area
.inset(Insets::top(theme::SPACING))
.inset(Insets::top(margin as i16));
header_area = header_area.inset(Insets::sides(theme::SPACING));
header.place(header_area);
@ -360,7 +375,7 @@ fn frame_place(header: &mut Header, footer: &mut Option<Footer>, bounds: Rect) -
content_area = content_area.inset(Insets::bottom(theme::SPACING));
let (remaining, footer_area) = content_area.split_bottom(footer.height());
footer.place(footer_area);
content_area = remaining;
content_area = remaining.inset(Insets::bottom(margin as i16));
}
content_area
}

View File

@ -42,6 +42,7 @@ pub struct ConfirmActionStrings {
subtitle: Option<TString<'static>>,
verb: Option<TString<'static>>,
prompt_screen: Option<TString<'static>>,
footer_description: Option<TString<'static>>,
}
impl ConfirmActionStrings {
@ -56,8 +57,14 @@ impl ConfirmActionStrings {
subtitle,
verb,
prompt_screen,
footer_description: None,
}
}
pub fn with_footer_description(mut self, footer_description: Option<TString<'static>>) -> Self {
self.footer_description = footer_description;
self
}
}
#[derive(PartialEq)]
@ -219,6 +226,7 @@ pub fn new_confirm_action(
ConfirmActionStrings::new(title, subtitle, None, prompt_screen.then_some(prompt_title)),
hold,
None,
0,
false,
)
}
@ -229,16 +237,21 @@ fn new_confirm_action_uni<T: Component + Paginate + MaybeTrace + 'static>(
extra: ConfirmActionExtra,
strings: ConfirmActionStrings,
hold: bool,
frame_margin: usize,
page_counter: bool,
) -> Result<SwipeFlow, error::Error> {
let (prompt_screen, prompt_pages, flow, page) =
create_flow(strings.title, strings.prompt_screen, hold, &extra);
let mut content = Frame::left_aligned(strings.title, content)
.with_margin(frame_margin)
.with_swipe(Direction::Up, SwipeSettings::default())
.with_swipe(Direction::Left, SwipeSettings::default())
.with_vertical_pages()
.with_footer(TR::instructions__swipe_up.into(), None);
.with_footer(
TR::instructions__swipe_up.into(),
strings.footer_description,
);
match extra {
ConfirmActionExtra::Menu { .. } => {
@ -401,6 +414,7 @@ pub fn new_confirm_action_simple<T: Component + Paginate + MaybeTrace + 'static>
strings: ConfirmActionStrings,
hold: bool,
page_limit: Option<usize>,
frame_margin: usize,
page_counter: bool,
) -> Result<SwipeFlow, error::Error> {
new_confirm_action_uni(
@ -408,6 +422,7 @@ pub fn new_confirm_action_simple<T: Component + Paginate + MaybeTrace + 'static>
extra,
strings,
hold,
frame_margin,
page_counter,
)
}

View File

@ -52,6 +52,7 @@ pub struct ConfirmBlobParams {
swipe_up: bool,
swipe_down: bool,
swipe_right: bool,
frame_margin: usize,
cancel: bool,
}
@ -80,6 +81,7 @@ impl ConfirmBlobParams {
swipe_up: false,
swipe_down: false,
swipe_right: false,
frame_margin: 0,
cancel: false,
}
}
@ -144,6 +146,19 @@ impl ConfirmBlobParams {
self
}
pub const fn with_frame_margin(mut self, frame_margin: usize) -> Self {
self.frame_margin = frame_margin;
self
}
pub const fn with_footer_description(
mut self,
footer_description: Option<TString<'static>>,
) -> Self {
self.footer_description = footer_description;
self
}
pub const fn with_cancel(mut self, cancel: bool) -> Self {
self.cancel = cancel;
self
@ -277,9 +292,11 @@ impl ConfirmBlobParams {
self.subtitle,
self.verb,
self.prompt.then_some(self.title),
),
)
.with_footer_description(self.footer_description),
self.hold,
self.page_limit,
self.frame_margin,
self.page_counter,
)
}

View File

@ -62,6 +62,8 @@ use crate::{
},
};
const CONFIRM_BLOB_INTRO_MARGIN: usize = 24;
impl TryFrom<SelectWordCountMsg> for Obj {
type Error = Error;
@ -250,6 +252,7 @@ extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *m
ConfirmActionStrings::new(title, None, None, Some(title)),
false,
None,
0,
false,
)
.and_then(LayoutObj::new_root)
@ -292,23 +295,10 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let page_counter: bool = kwargs.get_or(Qstr::MP_QSTR_page_counter, false)?;
let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, true)?;
let page_limit: Option<usize> = kwargs
.get(Qstr::MP_QSTR_page_limit)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let cancel: bool = kwargs.get_or(Qstr::MP_QSTR_cancel, false)?;
let (description, description_font) = if page_limit == Some(1) {
(
Some(TR::instructions__view_all_data.into()),
&theme::TEXT_SUB_GREEN_LIME,
)
} else {
(description, &theme::TEXT_SUB_GREY)
};
ConfirmBlobParams::new(title, data, description)
.with_description_font(description_font)
.with_description_font(&theme::TEXT_SUB_GREY)
.with_text_mono(text_mono)
.with_subtitle(subtitle)
.with_verb(verb)
@ -321,7 +311,6 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
.with_extra(extra)
.with_chunkify(chunkify)
.with_page_counter(page_counter)
.with_page_limit(page_limit)
.with_cancel(cancel)
.with_prompt(prompt_screen)
.with_hold(hold)
@ -332,6 +321,43 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_blob_intro(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
let subtitle: Option<TString> = kwargs
.get(Qstr::MP_QSTR_subtitle)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb_cancel: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
ConfirmBlobParams::new(title, data, Some(TR::instructions__view_all_data.into()))
.with_verb(verb)
.with_verb_info(Some(TR::buttons__view_all_data.into()))
.with_description_font(&theme::TEXT_SUB_GREEN_LIME)
.with_subtitle(subtitle)
.with_verb_cancel(verb_cancel)
.with_footer_description(Some(
TR::buttons__confirm.into(), /* or words__confirm?? */
))
.with_chunkify(chunkify)
.with_page_limit(Some(1))
.with_frame_margin(CONFIRM_BLOB_INTRO_MARGIN)
.into_flow()
.and_then(LayoutObj::new_root)
.map(Into::into)
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -435,6 +461,7 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
ConfirmActionStrings::new(title, None, None, hold.then_some(title)),
hold,
None,
0,
false,
)
.and_then(LayoutObj::new_root)
@ -470,6 +497,7 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
),
false,
None,
0,
false,
)
.and_then(LayoutObj::new_root)
@ -781,6 +809,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
ConfirmActionStrings::new(title, None, None, Some(title)),
true,
None,
0,
false,
)
.and_then(LayoutObj::new_root)
@ -1102,6 +1131,7 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut
),
true,
None,
0,
false,
)
.and_then(LayoutObj::new_root)
@ -1588,12 +1618,25 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// chunkify: bool = False,
/// page_counter: bool = False,
/// prompt_screen: bool = False,
/// page_limit: int | None = None,
/// cancel: bool = False,
/// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data."""
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),
/// def confirm_blob_intro(
/// *,
/// title: str,
/// data: str | bytes,
/// subtitle: str | None = None,
/// verb: str | None = None,
/// verb_cancel: str | None = None,
/// chunkify: bool = False,
/// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data by showing only the first page of the data
/// and instructing the user to access the menu in order to view all the data,
/// which can then be confirmed using confirm_blob."""
Qstr::MP_QSTR_confirm_blob_intro => obj_fn_kw!(0, new_confirm_blob_intro).as_obj(),
/// def confirm_address(
/// *,
/// title: str,

View File

@ -1711,7 +1711,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// chunkify: bool = False,
/// page_counter: bool = False,
/// prompt_screen: bool = False,
/// page_limit: int | None = None,
/// cancel: bool = False,
/// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data."""

View File

@ -30,7 +30,6 @@ pub struct ButtonPage<T> {
/// Swipe controller.
swipe: Swipe,
scrollbar: ScrollBar,
page_limit: Option<usize>,
/// Hold-to-confirm mode whenever this is `Some(loader)`.
loader: Option<Loader>,
button_cancel: Option<Button>,
@ -72,7 +71,6 @@ where
pad: Pad::with_background(background),
swipe: Swipe::new(),
scrollbar: ScrollBar::vertical(),
page_limit: None,
loader: None,
button_cancel: Some(Button::with_icon(theme::ICON_CANCEL)),
button_confirm: Button::with_icon(theme::ICON_CONFIRM).styled(theme::button_confirm()),
@ -112,11 +110,6 @@ where
self
}
pub fn with_page_limit(mut self, page_limit: Option<usize>) -> Self {
self.page_limit = page_limit;
self
}
pub fn with_back_button(mut self) -> Self {
self.cancel_from_any_page = true;
self.button_prev = Button::with_icon(theme::ICON_BACK).initially_enabled(false);
@ -335,11 +328,6 @@ where
count // Content fits on a single page.
}
};
let page_count = if let Some(limit) = self.page_limit {
page_count.min(limit)
} else {
page_count
};
if page_count == 1 && self.button_cancel.is_none() {
self.button_confirm.place(layout.button_both);

View File

@ -414,7 +414,6 @@ struct ConfirmBlobParams {
hold: bool,
chunkify: bool,
text_mono: bool,
page_limit: Option<usize>,
}
impl ConfirmBlobParams {
@ -438,7 +437,6 @@ impl ConfirmBlobParams {
hold,
chunkify: false,
text_mono: true,
page_limit: None,
}
}
@ -467,11 +465,6 @@ impl ConfirmBlobParams {
self
}
fn with_page_limit(mut self, page_limit: Option<usize>) -> Self {
self.page_limit = page_limit;
self
}
fn into_layout(self) -> Result<Obj, Error> {
let paragraphs = ConfirmBlob {
description: self.description.unwrap_or("".into()),
@ -497,7 +490,6 @@ impl ConfirmBlobParams {
if self.hold {
page = page.with_hold()?
}
page = page.with_page_limit(self.page_limit);
let mut frame = Frame::left_aligned(theme::label_title(), self.title, page);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(theme::label_subtitle(), subtitle);
@ -533,17 +525,12 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
let info: bool = kwargs.get_or(Qstr::MP_QSTR_info, false)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let page_limit: Option<usize> = kwargs
.get(Qstr::MP_QSTR_page_limit)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
ConfirmBlobParams::new(title, data, description, verb, verb_cancel, hold)
.with_text_mono(text_mono)
.with_extra(extra)
.with_chunkify(chunkify)
.with_info_button(info)
.with_page_limit(page_limit)
.into_layout()
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1805,7 +1792,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// chunkify: bool = False,
/// page_counter: bool = False,
/// prompt_screen: bool = False,
/// page_limit: int | None = None,
/// cancel: bool = False,
/// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data."""

View File

@ -70,12 +70,26 @@ def confirm_blob(
chunkify: bool = False,
page_counter: bool = False,
prompt_screen: bool = False,
page_limit: int | None = None,
cancel: bool = False,
) -> LayoutObj[UiResult]:
"""Confirm byte sequence data."""
# rust/src/ui/model_mercury/layout.rs
def confirm_blob_intro(
*,
title: str,
data: str | bytes,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
chunkify: bool = False,
) -> LayoutObj[UiResult]:
"""Confirm byte sequence data by showing only the first page of the data
and instructing the user to access the menu in order to view all the data,
which can then be confirmed using confirm_blob."""
# rust/src/ui/model_mercury/layout.rs
def confirm_address(
*,
@ -644,7 +658,6 @@ def confirm_blob(
chunkify: bool = False,
page_counter: bool = False,
prompt_screen: bool = False,
page_limit: int | None = None,
cancel: bool = False,
) -> LayoutObj[UiResult]:
"""Confirm byte sequence data."""
@ -1217,7 +1230,6 @@ def confirm_blob(
chunkify: bool = False,
page_counter: bool = False,
prompt_screen: bool = False,
page_limit: int | None = None,
cancel: bool = False,
) -> LayoutObj[UiResult]:
"""Confirm byte sequence data."""

View File

@ -463,19 +463,13 @@ def confirm_blob(
prompt_screen: bool = True,
) -> Awaitable[None]:
if ask_pagination:
main_layout = trezorui2.confirm_blob(
main_layout = trezorui2.confirm_blob_intro(
title=title,
data=data,
description=None,
subtitle=description,
verb=verb,
verb_cancel=verb_cancel,
verb_info=TR.buttons__view_all_data,
info=True,
hold=False,
chunkify=chunkify,
prompt_screen=False,
page_limit=1,
)
info_layout = trezorui2.confirm_blob(
title=title,