1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-29 18:08:19 +00:00

WIP - create confirm_blob with not showing hyphens and line-ends

This commit is contained in:
grdddj 2023-01-11 12:31:32 +01:00
parent fe90b0a5cd
commit 5398849275
5 changed files with 854 additions and 772 deletions

View File

@ -30,7 +30,7 @@ use crate::{
layout::{ layout::{
obj::{ComponentMsgObj, LayoutObj}, obj::{ComponentMsgObj, LayoutObj},
result::{CANCELLED, CONFIRMED, INFO}, result::{CANCELLED, CONFIRMED, INFO},
util::{iter_into_objs, iter_into_vec, upy_disable_animation}, util::{iter_into_objs, iter_into_vec, upy_disable_animation, ConfirmBlob},
}, },
model_tr::component::{ScrollableContent, ScrollableFrame}, model_tr::component::{ScrollableContent, ScrollableFrame},
}, },
@ -185,6 +185,51 @@ impl ComponentMsgObj for Lockscreen {
} }
} }
/// Function to create and call a `ButtonPage` dialog based on `Paragraphs`
/// Has optional title (supply empty `StrBuffer` for that) and hold-to-confirm
/// functionality.
fn paragraphs_in_button_page<T: ParagraphSource + 'static>(
title: StrBuffer,
paragraphs: Paragraphs<T>,
verb: StrBuffer,
verb_cancel: Option<StrBuffer>,
hold: bool,
) -> Result<Obj, Error> {
// Left button - icon, text or nothing.
let cancel_btn = if let Some(verb_cancel) = verb_cancel {
if !verb_cancel.is_empty() {
Some(ButtonDetails::text(verb_cancel))
} else {
Some(ButtonDetails::cancel_icon())
}
} else {
None
};
// Right button - text or nothing.
// Optional HoldToConfirm
let mut confirm_btn = if !verb.is_empty() {
Some(ButtonDetails::text(verb))
} else {
None
};
if hold {
confirm_btn = confirm_btn.map(|btn| btn.with_default_duration());
}
let content = ButtonPage::new(paragraphs, theme::BG)
.with_cancel_btn(cancel_btn)
.with_confirm_btn(confirm_btn);
let mut frame = ScrollableFrame::new(content);
if !title.as_ref().is_empty() {
frame = frame.with_title(title);
}
let obj = LayoutObj::new(frame)?;
Ok(obj.into())
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| { let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -215,40 +260,36 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
paragraphs.into_paragraphs() paragraphs.into_paragraphs()
}; };
// Left button - icon, text or nothing. paragraphs_in_button_page(title, paragraphs, verb, verb_cancel, hold)
let cancel_btn = if let Some(verb_cancel) = verb_cancel { };
if !verb_cancel.is_empty() { unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
Some(ButtonDetails::text(verb_cancel)) }
} else {
Some(ButtonDetails::cancel_icon())
}
} else {
None
};
// Right button - text or nothing. extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
// Optional HoldToConfirm let block = move |_args: &[Obj], kwargs: &Map| {
let mut confirm_btn = if !verb.is_empty() { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
Some(ButtonDetails::text(verb)) let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
} else { let description: Option<StrBuffer> =
None kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
}; let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
if hold { let verb: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_verb, "CONFIRM".into())?;
// TODO: clients might want to set the duration let verb_cancel: Option<StrBuffer> = kwargs
confirm_btn = confirm_btn.map(|btn| btn.with_default_duration()); .get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let paragraphs = ConfirmBlob {
description: description.unwrap_or_else(StrBuffer::empty),
extra: extra.unwrap_or_else(StrBuffer::empty),
data: data.try_into()?,
description_font: &theme::TEXT_BOLD,
extra_font: &theme::TEXT_MONO,
data_font: &theme::TEXT_MONO_DATA,
} }
.into_paragraphs();
let content = ButtonPage::new(paragraphs, theme::BG) paragraphs_in_button_page(title, paragraphs, verb, verb_cancel, hold)
.with_cancel_btn(cancel_btn)
.with_confirm_btn(confirm_btn);
let obj = if title.as_ref().is_empty() {
LayoutObj::new(ScrollableFrame::new(content))?
} else {
LayoutObj::new(ScrollableFrame::new(content).with_title(title))?
};
Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
@ -264,9 +305,10 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
let mut iter_buf = IterBuf::new(); let mut iter_buf = IterBuf::new();
let iter = Iter::try_from_obj_with_buf(items, &mut iter_buf)?; let iter = Iter::try_from_obj_with_buf(items, &mut iter_buf)?;
for para in iter { for para in iter {
let [key, value, _is_mono]: [Obj; 3] = iter_into_objs(para)?; let [key, value, is_data]: [Obj; 3] = iter_into_objs(para)?;
let key = key.try_into_option::<StrBuffer>()?; let key = key.try_into_option::<StrBuffer>()?;
let value = value.try_into_option::<StrBuffer>()?; let value = value.try_into_option::<StrBuffer>()?;
let is_data: bool = is_data.try_into()?;
if let Some(key) = key { if let Some(key) = key {
if value.is_some() { if value.is_some() {
@ -276,17 +318,22 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
} }
} }
if let Some(value) = value { if let Some(value) = value {
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); let style = if is_data {
&theme::TEXT_MONO_DATA
} else {
&theme::TEXT_MONO
};
paragraphs.add(Paragraph::new(style, value));
} }
} }
let mut content = ButtonPage::new(paragraphs.into_paragraphs(), theme::BG); paragraphs_in_button_page(
if hold { title,
let confirm_btn = Some(ButtonDetails::text("CONFIRM".into()).with_default_duration()); paragraphs.into_paragraphs(),
content = content.with_confirm_btn(confirm_btn); "CONFIRM".into(),
} None,
let obj = LayoutObj::new(ScrollableFrame::new(content).with_title(title))?; hold,
Ok(obj.into()) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
@ -389,6 +436,7 @@ extern "C" fn new_show_receive_address(n_args: usize, args: *const Obj, kwargs:
let btn_layout = ButtonLayout::cancel_armed_text("CONFIRM".into(), "i".into()); let btn_layout = ButtonLayout::cancel_armed_text("CONFIRM".into(), "i".into());
let btn_actions = ButtonActions::last_confirm_next(); let btn_actions = ButtonActions::last_confirm_next();
Page::<15>::new(btn_layout, btn_actions, Font::BOLD) Page::<15>::new(btn_layout, btn_actions, Font::BOLD)
.with_line_breaking(LineBreaking::BreakWordsNoHyphen)
.text_bold(title) .text_bold(title)
.newline() .newline()
.newline_half() .newline_half()
@ -446,10 +494,10 @@ extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?; kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?;
let content = Paragraphs::new([ let content = Frame::new(
Paragraph::new(&theme::TEXT_BOLD, title), title,
Paragraph::new(&theme::TEXT_MONO, description), Paragraphs::new([Paragraph::new(&theme::TEXT_MONO, description)]),
]); );
let obj = if time_ms == 0 { let obj = if time_ms == 0 {
// No timer, used when we only want to draw the dialog once and // No timer, used when we only want to draw the dialog once and
// then throw away the layout object. // then throw away the layout object.
@ -922,6 +970,18 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Confirm action.""" /// """Confirm action."""
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(), Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
/// def confirm_blob(
/// *,
/// title: str,
/// data: str | bytes,
/// description: str | None,
/// extra: str | None,
/// verb_cancel: str | None = None,
/// hold: bool = False,
/// ) -> object:
/// """Confirm byte sequence data."""
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),
/// def confirm_properties( /// def confirm_properties(
/// *, /// *,
/// title: str, /// title: str,

View File

@ -1,5 +1,5 @@
use crate::ui::{ use crate::ui::{
component::text::TextStyle, component::{text::TextStyle, LineBreaking},
display::{Color, Font, IconAndName}, display::{Color, Font, IconAndName},
geometry::Offset, geometry::Offset,
}; };
@ -17,9 +17,12 @@ pub const FONT_CHOICE_ITEMS: Font = Font::NORMAL;
pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, FG, BG, FG, FG); pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, FG, BG, FG, FG);
pub const TEXT_DEMIBOLD: TextStyle = TextStyle::new(Font::DEMIBOLD, FG, BG, FG, FG); pub const TEXT_DEMIBOLD: TextStyle = TextStyle::new(Font::DEMIBOLD, FG, BG, FG, FG);
pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::BOLD, FG, BG, FG, FG); pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::BOLD, FG, BG, FG, FG);
/// Mono text has the icon ellipsis
pub const TEXT_MONO: TextStyle = pub const TEXT_MONO: TextStyle =
TextStyle::new(Font::MONO, FG, BG, FG, FG).with_ellipsis_icon(ICON_NEXT_PAGE.0); TextStyle::new(Font::MONO, FG, BG, FG, FG).with_ellipsis_icon(ICON_NEXT_PAGE.0);
// Header does not have the ellipsis /// Mono data text does not have hyphens
pub const TEXT_MONO_DATA: TextStyle =
TEXT_MONO.with_line_breaking(LineBreaking::BreakWordsNoHyphen);
pub const TEXT_HEADER: TextStyle = TextStyle::new(Font::BOLD, FG, BG, FG, FG); pub const TEXT_HEADER: TextStyle = TextStyle::new(Font::BOLD, FG, BG, FG, FG);
// Icons with their names for debugging purposes // Icons with their names for debugging purposes

View File

@ -606,28 +606,33 @@ async def confirm_backup(ctx: GenericContext) -> bool:
async def confirm_path_warning( async def confirm_path_warning(
ctx: GenericContext, path: str, path_type: str = "Path" ctx: GenericContext,
path: str,
path_type: str | None = None,
) -> None: ) -> None:
if path_type:
title = f"Unknown {path_type}"
else:
title = "Unknown path"
return await _placeholder_confirm( return await _placeholder_confirm(
ctx, ctx,
"path_warning", "path_warning",
"CONFIRM PATH", title.upper(),
f"{path_type}\n{path} is unknown.\nAre you sure?", description=path,
br_code=ButtonRequestType.UnknownDerivationPath, br_code=ButtonRequestType.UnknownDerivationPath,
) )
def _show_xpub(xpub: str, title: str, cancel: str | None) -> ui.Layout: def _show_xpub(xpub: str, title: str, cancel: str | None) -> ui.Layout:
content = RustLayout( return RustLayout(
trezorui2.confirm_action( trezorui2.confirm_blob(
title=title.upper(), title=title.upper(),
action="", data=xpub,
description=xpub,
verb="CONFIRM",
verb_cancel=cancel, verb_cancel=cancel,
description=None,
extra=None,
) )
) )
return content
async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None: async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None:
@ -880,14 +885,25 @@ async def confirm_blob(
br_code: ButtonRequestType = BR_TYPE_OTHER, br_code: ButtonRequestType = BR_TYPE_OTHER,
ask_pagination: bool = False, ask_pagination: bool = False,
) -> None: ) -> None:
await confirm_action( title = title.upper()
ctx, description = description or ""
br_type, layout = RustLayout(
title.upper(), trezorui2.confirm_blob(
description, title=title,
str(data), description=description,
hold=hold, data=data,
br_code=br_code, extra=None,
hold=hold,
)
)
await raise_if_cancelled(
interact(
ctx,
layout,
br_type,
br_code,
)
) )
@ -959,7 +975,10 @@ async def confirm_properties(
if isinstance(prop[1], bytes): if isinstance(prop[1], bytes):
return (prop[0], hexlify(prop[1]).decode(), True) return (prop[0], hexlify(prop[1]).decode(), True)
else: else:
return (prop[0], prop[1], False) # When there is not space in the text, taking it as data
# to not include hyphens
is_data = prop[1] and " " not in prop[1]
return (prop[0], prop[1], is_data)
await raise_if_cancelled( await raise_if_cancelled(
interact( interact(
@ -1208,12 +1227,13 @@ async def show_popup(
description_param: str = "", description_param: str = "",
timeout_ms: int = 3000, timeout_ms: int = 3000,
) -> None: ) -> None:
description = description.format(description_param)
if subtitle: if subtitle:
title += f"\n{subtitle}" description = f"{subtitle}\n{description}"
await RustLayout( await RustLayout(
trezorui2.show_info( trezorui2.show_info(
title=title, title=title,
description=description.format(description_param), description=description,
time_ms=timeout_ms, time_ms=timeout_ms,
) )
) )

View File

@ -331,7 +331,7 @@ async def confirm_path_warning(
def _show_xpub(xpub: str, title: str, cancel: str | None) -> ui.Layout: def _show_xpub(xpub: str, title: str, cancel: str | None) -> ui.Layout:
content = RustLayout( return RustLayout(
trezorui2.confirm_blob( trezorui2.confirm_blob(
title=title, title=title,
data=xpub, data=xpub,
@ -340,7 +340,6 @@ def _show_xpub(xpub: str, title: str, cancel: str | None) -> ui.Layout:
description=None, description=None,
) )
) )
return content
async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None: async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None:

File diff suppressed because it is too large Load Diff