mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-04-20 17:19:01 +00:00
Merge 8c1bcb47b6
into f89e7670c5
This commit is contained in:
commit
93a5e2bd32
@ -1157,7 +1157,7 @@ pub enum TranslatedString {
|
||||
wipe_code__diff_from_pin = 776, // "The wipe code must be different from your PIN."
|
||||
wipe_code__disabled = 777, // "Wipe code disabled."
|
||||
wipe_code__enabled = 778, // "Wipe code enabled."
|
||||
wipe_code__enter_new = 779, // "Enter new wipe code"
|
||||
wipe_code__enter_new = 779, // "New wipe code"
|
||||
wipe_code__info = 780, // "Wipe code can be used to erase all data from this device."
|
||||
wipe_code__invalid = 781, // "Invalid wipe code"
|
||||
wipe_code__mismatch = 782, // "The wipe codes you entered do not match."
|
||||
@ -2560,7 +2560,7 @@ impl TranslatedString {
|
||||
Self::wipe_code__diff_from_pin => "The wipe code must be different from your PIN.",
|
||||
Self::wipe_code__disabled => "Wipe code disabled.",
|
||||
Self::wipe_code__enabled => "Wipe code enabled.",
|
||||
Self::wipe_code__enter_new => "Enter new wipe code",
|
||||
Self::wipe_code__enter_new => "New wipe code",
|
||||
Self::wipe_code__info => "Wipe code can be used to erase all data from this device.",
|
||||
Self::wipe_code__invalid => "Invalid wipe code",
|
||||
Self::wipe_code__mismatch => "The wipe codes you entered do not match.",
|
||||
|
@ -319,9 +319,13 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
|
||||
let subtitle: Option<TString> = kwargs
|
||||
.get(Qstr::MP_QSTR_subtitle)
|
||||
.and_then(Obj::try_into_option)
|
||||
.unwrap_or(None);
|
||||
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||
|
||||
let layout = ModelUI::confirm_properties(title, items, hold)?;
|
||||
let layout = ModelUI::confirm_properties(title, subtitle, items, hold)?;
|
||||
Ok(LayoutObj::new_root(layout)?.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -1303,6 +1307,7 @@ pub static mp_module_trezorui_api: Module = obj_module! {
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// items: list[tuple[str | None, str | bytes | None, bool]],
|
||||
/// subtitle: str | None = None,
|
||||
/// hold: bool = False,
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm list of key-value pairs. The third component in the tuple should be True if
|
||||
|
@ -111,7 +111,7 @@ impl<'a> Label<'a> {
|
||||
|
||||
pub fn render_with_alpha<'s>(&self, target: &mut impl Renderer<'s>, alpha: u8) {
|
||||
self.text
|
||||
.map(|c| self.layout.render_text_with_alpha(c, target, alpha));
|
||||
.map(|c| self.layout.render_text_with_alpha(c, target, alpha, true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ impl Component for Label<'_> {
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.text.map(|c| self.layout.render_text(c, target));
|
||||
self.text.map(|c| self.layout.render_text(c, target, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,21 +232,35 @@ impl TextLayout {
|
||||
}
|
||||
|
||||
/// Draw as much text as possible on the current screen.
|
||||
pub fn render_text<'s>(&self, text: &str, target: &mut impl Renderer<'s>) -> LayoutFit {
|
||||
self.render_text_with_alpha(text, target, 255)
|
||||
pub fn render_text<'s>(
|
||||
&self,
|
||||
text: &str,
|
||||
target: &mut impl Renderer<'s>,
|
||||
must_fit: bool,
|
||||
) -> LayoutFit {
|
||||
self.render_text_with_alpha(text, target, 255, must_fit)
|
||||
}
|
||||
|
||||
/// Draw as much text as possible on the current screen.
|
||||
pub fn render_text_with_alpha<'s>(
|
||||
&self,
|
||||
text: &str,
|
||||
target: &mut impl Renderer<'s>,
|
||||
alpha: u8,
|
||||
must_fit: bool,
|
||||
) -> LayoutFit {
|
||||
self.layout_text(
|
||||
let fit = self.layout_text(
|
||||
text,
|
||||
&mut self.initial_cursor(),
|
||||
&mut TextRenderer::new(target).with_alpha(alpha),
|
||||
)
|
||||
);
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
if must_fit && matches!(fit, LayoutFit::OutOfBounds { .. }) {
|
||||
target.raise_overflow_exception();
|
||||
}
|
||||
|
||||
fit
|
||||
}
|
||||
|
||||
/// Loop through the `text` and try to fit it on the current screen,
|
||||
|
@ -236,7 +236,7 @@ where
|
||||
&self.visible,
|
||||
self.offset,
|
||||
&mut |layout, content| {
|
||||
layout.render_text(content, target);
|
||||
layout.render_text(content, target, false);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ pub fn text_multiline<'s>(
|
||||
let text_layout = TextLayout::new(text_style)
|
||||
.with_bounds(area)
|
||||
.with_align(alignment);
|
||||
let layout_fit = text.map(|t| text_layout.render_text(t, target));
|
||||
let layout_fit = text.map(|t| text_layout.render_text(t, target, false));
|
||||
match layout_fit {
|
||||
LayoutFit::Fitting { height, .. } => Some(area.split_top(height).1),
|
||||
LayoutFit::OutOfBounds { .. } => None,
|
||||
@ -60,11 +60,11 @@ pub fn text_multiline_bottom<'s>(
|
||||
LayoutFit::Fitting { height, .. } => {
|
||||
let (top, bottom) = area.split_bottom(height);
|
||||
text_layout = text_layout.with_bounds(bottom);
|
||||
text_layout.render_text(t, target);
|
||||
text_layout.render_text(t, target, false);
|
||||
Some(top)
|
||||
}
|
||||
LayoutFit::OutOfBounds { .. } => {
|
||||
text_layout.render_text(t, target);
|
||||
text_layout.render_text(t, target, false);
|
||||
None
|
||||
}
|
||||
})
|
||||
|
@ -313,10 +313,21 @@ impl Layout<Result<Obj, Error>> for SwipeFlow {
|
||||
self.returned_value.as_ref()
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
fn paint(&mut self) -> Result<(), Error> {
|
||||
let mut overflow: bool = false;
|
||||
render_on_display(None, Some(Color::black()), |target| {
|
||||
self.render_state(self.state.index(), target);
|
||||
#[cfg(feature = "ui_debug")]
|
||||
if target.should_raise_overflow_exception() {
|
||||
overflow = true;
|
||||
}
|
||||
});
|
||||
|
||||
if overflow {
|
||||
Err(Error::OutOfRange)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ use crate::ui::{
|
||||
component::{base::AttachType, Event, EventCtx},
|
||||
};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LayoutState {
|
||||
Initial,
|
||||
@ -15,7 +17,7 @@ pub trait Layout<T> {
|
||||
fn place(&mut self);
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<LayoutState>;
|
||||
fn value(&self) -> Option<&T>;
|
||||
fn paint(&mut self);
|
||||
fn paint(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
|
@ -44,7 +44,7 @@ use crate::{
|
||||
},
|
||||
display::{self, Color},
|
||||
event::USBEvent,
|
||||
shape::render_on_display,
|
||||
shape::{render_on_display, Renderer},
|
||||
CommonUI, ModelUI,
|
||||
},
|
||||
};
|
||||
@ -146,10 +146,21 @@ where
|
||||
self.returned_value.as_ref()
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
fn paint(&mut self) -> Result<(), Error> {
|
||||
let mut overflow: bool = false;
|
||||
render_on_display(None, Some(Color::black()), |target| {
|
||||
self.inner.render(target);
|
||||
#[cfg(feature = "ui_debug")]
|
||||
if target.should_raise_overflow_exception() {
|
||||
overflow = true;
|
||||
}
|
||||
});
|
||||
|
||||
if overflow {
|
||||
Err(Error::OutOfRange)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -305,15 +316,14 @@ impl LayoutObjInner {
|
||||
|
||||
/// Run a paint pass over the component tree. Returns true if any component
|
||||
/// actually requested painting since last invocation of the function.
|
||||
fn obj_paint_if_requested(&mut self) -> bool {
|
||||
fn obj_paint_if_requested(&mut self) -> Result<bool, Error> {
|
||||
display::sync();
|
||||
|
||||
if self.repaint != Repaint::None {
|
||||
self.repaint = Repaint::None;
|
||||
self.root_mut().paint();
|
||||
true
|
||||
self.root_mut().paint().map(|_| true)
|
||||
} else {
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -597,8 +607,8 @@ extern "C" fn ui_layout_timer(this: Obj, token: Obj) -> Obj {
|
||||
extern "C" fn ui_layout_paint(this: Obj) -> Obj {
|
||||
let block = || {
|
||||
let this: Gc<LayoutObj> = this.try_into()?;
|
||||
let painted = this.inner_mut().obj_paint_if_requested().into();
|
||||
Ok(painted)
|
||||
let painted = this.inner_mut().obj_paint_if_requested();
|
||||
Ok(painted?.into())
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{image::Image, Child, Component, Event, EventCtx, Label},
|
||||
component::{
|
||||
image::Image,
|
||||
text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
|
||||
Child, Component, Event, EventCtx, Label,
|
||||
},
|
||||
display,
|
||||
geometry::{Insets, Rect},
|
||||
shape,
|
||||
@ -32,7 +36,7 @@ pub enum FidoMsg {
|
||||
pub struct FidoConfirm<F: Fn(usize) -> TString<'static>, U> {
|
||||
page_swipe: Swipe,
|
||||
app_name: Label<'static>,
|
||||
account_name: Label<'static>,
|
||||
account_name: Paragraphs<Paragraph<'static>>,
|
||||
icon: Child<Image>,
|
||||
/// Function/closure that will return appropriate page on demand.
|
||||
get_account: F,
|
||||
@ -65,22 +69,10 @@ where
|
||||
page_swipe.allow_right = scrollbar.has_previous_page();
|
||||
page_swipe.allow_left = scrollbar.has_next_page();
|
||||
|
||||
// NOTE: This is an ugly hotfix for the erroneous behavior of
|
||||
// TextLayout used in the account_name Label. In this
|
||||
// particular case, TextLayout calculates the wrong height of
|
||||
// fitted text that's higher than the TextLayout bound itself.
|
||||
//
|
||||
// The following two lines should be swapped when the problem with
|
||||
// TextLayout is fixed.
|
||||
//
|
||||
// See also, continuation of this hotfix in the place() function.
|
||||
|
||||
// let current_account = get_account(scrollbar.active_page);
|
||||
let current_account = "".into();
|
||||
|
||||
Self {
|
||||
app_name: Label::centered(app_name, theme::TEXT_DEMIBOLD),
|
||||
account_name: Label::centered(current_account, theme::TEXT_DEMIBOLD),
|
||||
account_name: Paragraph::new(&theme::TEXT_MONO, get_account(scrollbar.active_page))
|
||||
.into_paragraphs(),
|
||||
page_swipe,
|
||||
icon: Child::new(Image::new(icon_data)),
|
||||
get_account,
|
||||
@ -107,7 +99,7 @@ where
|
||||
self.page_swipe.allow_left = self.scrollbar.has_next_page();
|
||||
|
||||
let current_account = (self.get_account)(self.active_page());
|
||||
self.account_name.set_text(current_account);
|
||||
self.account_name = Paragraph::new(&theme::TEXT_MONO, current_account).into_paragraphs();
|
||||
|
||||
// Redraw the page.
|
||||
ctx.request_paint();
|
||||
@ -153,19 +145,11 @@ where
|
||||
self.icon.place(image_area);
|
||||
|
||||
// Place the text labels.
|
||||
let (app_name_area, account_name_area) = remaining_area
|
||||
.inset(Insets::top(APP_NAME_PADDING))
|
||||
.split_top(APP_NAME_HEIGHT);
|
||||
let (app_name_area, account_name_area) = remaining_area.split_top(APP_NAME_HEIGHT);
|
||||
|
||||
self.app_name.place(app_name_area);
|
||||
self.account_name.place(account_name_area);
|
||||
|
||||
// NOTE: This is a hotfix used due to the erroneous behavior of TextLayout.
|
||||
// This line should be removed when the problem with TextLayout is fixed.
|
||||
// See also the code for FidoConfirm::new().
|
||||
self.account_name
|
||||
.set_text((self.get_account)(self.scrollbar.active_page));
|
||||
|
||||
bounds
|
||||
}
|
||||
|
||||
@ -194,15 +178,14 @@ where
|
||||
}
|
||||
|
||||
// Erasing the old text content before writing the new one.
|
||||
let account_name_area = self.account_name.area();
|
||||
let real_area = account_name_area
|
||||
.with_height(account_name_area.height() + self.account_name.font().text_baseline() + 1);
|
||||
shape::Bar::new(real_area).with_bg(theme::BG).render(target);
|
||||
shape::Bar::new(self.account_name.area())
|
||||
.with_bg(theme::BG)
|
||||
.render(target);
|
||||
|
||||
// Account name is optional.
|
||||
// Showing it only if it differs from app name.
|
||||
// (Dummy requests usually have some text as both app_name and account_name.)
|
||||
let account_name = self.account_name.text();
|
||||
let account_name = self.account_name.content();
|
||||
let app_name = self.app_name.text();
|
||||
if !account_name.is_empty() && account_name != app_name {
|
||||
self.account_name.render(target);
|
||||
|
@ -376,6 +376,7 @@ impl FirmwareUI for UIBolt {
|
||||
|
||||
fn confirm_properties(
|
||||
title: TString<'static>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
items: Obj,
|
||||
hold: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
|
@ -412,6 +412,7 @@ impl FirmwareUI for UICaesar {
|
||||
|
||||
fn confirm_properties(
|
||||
title: TString<'static>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
items: Obj,
|
||||
hold: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
|
@ -402,13 +402,11 @@ fn frame_place(
|
||||
bounds: Rect,
|
||||
margin: usize,
|
||||
) -> Rect {
|
||||
let (mut header_area, mut content_area) = bounds.split_top(TITLE_HEIGHT);
|
||||
content_area = content_area
|
||||
let header_area = header.place(bounds);
|
||||
let mut content_area = bounds
|
||||
.inset(Insets::top(header_area.height().max(TITLE_HEIGHT)))
|
||||
.inset(Insets::top(theme::SPACING))
|
||||
.inset(Insets::top(margin as i16));
|
||||
header_area = header_area.inset(Insets::sides(theme::SPACING));
|
||||
|
||||
header.place(header_area);
|
||||
|
||||
if let Some(footer) = footer {
|
||||
// FIXME: spacer at the bottom might be applied also for usage without footer
|
||||
|
@ -93,7 +93,7 @@ impl Header {
|
||||
pub const fn new(alignment: Alignment, title: TString<'static>) -> Self {
|
||||
Self {
|
||||
area: Rect::zero(),
|
||||
title: Label::new(title, alignment, theme::label_title_main()).vertically_centered(),
|
||||
title: Label::new(title, alignment, theme::label_title_main()),
|
||||
subtitle: None,
|
||||
button: None,
|
||||
anim: None,
|
||||
@ -185,23 +185,32 @@ impl Component for Header {
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
let header_area = if let Some(b) = &mut self.button {
|
||||
let (rest, button_area) = bounds.split_right(TITLE_HEIGHT);
|
||||
let (rest, button_area) = bounds.split_right(TITLE_HEIGHT + theme::SPACING * 2);
|
||||
let (button_area, _under_button_area) = button_area.split_top(TITLE_HEIGHT);
|
||||
b.place(button_area);
|
||||
rest
|
||||
} else {
|
||||
bounds
|
||||
};
|
||||
}
|
||||
.inset(Insets::sides(theme::SPACING));
|
||||
|
||||
if self.subtitle.is_some() {
|
||||
let title_area = self.title.place(header_area);
|
||||
let remaining = header_area.inset(Insets::top(title_area.height()));
|
||||
let _subtitle_area = self.subtitle.place(remaining);
|
||||
let subtitle_area = self.subtitle.place(remaining);
|
||||
self.area = title_area.outset(Insets::top(subtitle_area.height()));
|
||||
} else {
|
||||
self.title.place(header_area);
|
||||
}
|
||||
let title_area = self.title.place(header_area);
|
||||
if title_area.height() < header_area.height() / 10 {
|
||||
self.title
|
||||
.place(title_area.translate(Offset::y(title_area.height() / 2)));
|
||||
} else {
|
||||
self.title
|
||||
.place(title_area.translate(Offset::y(-theme::SPACING)));
|
||||
}
|
||||
};
|
||||
|
||||
self.area = bounds;
|
||||
bounds
|
||||
self.area
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
@ -238,7 +247,7 @@ impl Component for Header {
|
||||
|
||||
self.button.render(target);
|
||||
|
||||
target.in_clip(self.area.split_left(offset.x).0, &|target| {
|
||||
target.in_clip(self.title.area().split_left(offset.x).0, &|target| {
|
||||
if let Some(icon) = self.icon {
|
||||
let color = self.color.unwrap_or(theme::GREEN);
|
||||
shape::ToifImage::new(self.title.area().left_center(), icon.toif)
|
||||
|
@ -410,12 +410,13 @@ impl FirmwareUI for UIDelizia {
|
||||
|
||||
fn confirm_properties(
|
||||
title: TString<'static>,
|
||||
subtitle: Option<TString<'static>>,
|
||||
items: Obj,
|
||||
hold: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
let paragraphs = PropsList::new(
|
||||
items,
|
||||
&theme::TEXT_NORMAL,
|
||||
&theme::TEXT_SUB_GREY_LIGHT,
|
||||
&theme::TEXT_MONO,
|
||||
&theme::TEXT_MONO,
|
||||
)?;
|
||||
@ -423,7 +424,7 @@ impl FirmwareUI for UIDelizia {
|
||||
let flow = flow::new_confirm_action_simple(
|
||||
paragraphs.into_paragraphs(),
|
||||
ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()),
|
||||
ConfirmActionStrings::new(title, None, None, hold.then_some(title)),
|
||||
ConfirmActionStrings::new(title, subtitle, None, hold.then_some(title)),
|
||||
hold,
|
||||
None,
|
||||
0,
|
||||
|
@ -30,6 +30,9 @@ where
|
||||
bg_color: Option<Color>,
|
||||
/// Drawing cache (decompression context, scratch-pad memory)
|
||||
cache: &'a DrawingCache<'alloc>,
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
overflow: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'alloc, T, C> ProgressiveRenderer<'a, 'alloc, T, C>
|
||||
@ -53,6 +56,8 @@ where
|
||||
viewport,
|
||||
bg_color,
|
||||
cache,
|
||||
#[cfg(feature = "ui_debug")]
|
||||
overflow: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,4 +145,14 @@ where
|
||||
unwrap!(self.shapes.push(holder), "Shape list full");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn raise_overflow_exception(&mut self) {
|
||||
self.overflow = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn should_raise_overflow_exception(&self) -> bool {
|
||||
self.overflow
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,12 @@ pub trait Renderer<'a> {
|
||||
inner(self);
|
||||
self.set_viewport(original);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn raise_overflow_exception(&mut self);
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn should_raise_overflow_exception(&self) -> bool;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
@ -73,6 +79,9 @@ where
|
||||
canvas: &'a mut C,
|
||||
/// Drawing cache (decompression context, scratch-pad memory)
|
||||
cache: &'a DrawingCache<'alloc>,
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
overflow: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'alloc, C> DirectRenderer<'a, 'alloc, C>
|
||||
@ -92,7 +101,12 @@ where
|
||||
// TODO: consider storing original canvas.viewport
|
||||
// and restoring it by drop() function
|
||||
|
||||
Self { canvas, cache }
|
||||
Self {
|
||||
canvas,
|
||||
cache,
|
||||
#[cfg(feature = "ui_debug")]
|
||||
overflow: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,6 +131,16 @@ where
|
||||
shape.cleanup(self.cache);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn raise_overflow_exception(&mut self) {
|
||||
self.overflow = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn should_raise_overflow_exception(&self) -> bool {
|
||||
self.overflow
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScopedRenderer<'alloc, 'env, T>
|
||||
@ -164,4 +188,14 @@ where
|
||||
{
|
||||
self.renderer.render_shape(shape);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn raise_overflow_exception(&mut self) {
|
||||
self.renderer.raise_overflow_exception();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
fn should_raise_overflow_exception(&self) -> bool {
|
||||
self.renderer.should_raise_overflow_exception()
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ pub trait FirmwareUI {
|
||||
|
||||
fn confirm_properties(
|
||||
title: TString<'static>,
|
||||
subtitle: Option<TString<'static>>,
|
||||
items: Obj, // TODO: replace Obj`
|
||||
hold: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error>;
|
||||
|
@ -257,6 +257,7 @@ def confirm_properties(
|
||||
*,
|
||||
title: str,
|
||||
items: list[tuple[str | None, str | bytes | None, bool]],
|
||||
subtitle: str | None = None,
|
||||
hold: bool = False,
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm list of key-value pairs. The third component in the tuple should be True if
|
||||
|
@ -924,7 +924,7 @@ class TR:
|
||||
wipe_code__diff_from_pin: str = "The wipe code must be different from your PIN."
|
||||
wipe_code__disabled: str = "Wipe code disabled."
|
||||
wipe_code__enabled: str = "Wipe code enabled."
|
||||
wipe_code__enter_new: str = "Enter new wipe code"
|
||||
wipe_code__enter_new: str = "New wipe code"
|
||||
wipe_code__info: str = "Wipe code can be used to erase all data from this device."
|
||||
wipe_code__invalid: str = "Invalid wipe code"
|
||||
wipe_code__mismatch: str = "The wipe codes you entered do not match."
|
||||
|
@ -92,13 +92,14 @@ async def confirm_instruction(
|
||||
|
||||
await confirm_properties(
|
||||
"confirm_instruction",
|
||||
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
|
||||
f"{instruction_index}/{instructions_count}",
|
||||
(
|
||||
(
|
||||
ui_property.display_name,
|
||||
property_template.format(instruction, value),
|
||||
),
|
||||
),
|
||||
instruction.ui_name,
|
||||
)
|
||||
elif ui_property.account is not None:
|
||||
account_template = instruction.get_account_template(ui_property.account)
|
||||
@ -134,8 +135,9 @@ async def confirm_instruction(
|
||||
|
||||
await confirm_properties(
|
||||
"confirm_instruction",
|
||||
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
|
||||
f"{instruction_index}/{instructions_count}",
|
||||
account_data,
|
||||
instruction.ui_name,
|
||||
)
|
||||
else:
|
||||
raise ValueError # Invalid ui property
|
||||
@ -158,8 +160,9 @@ async def confirm_instruction(
|
||||
|
||||
await confirm_properties(
|
||||
"confirm_instruction",
|
||||
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
|
||||
f"{instruction_index}/{instructions_count}",
|
||||
signers,
|
||||
instruction.ui_name,
|
||||
)
|
||||
|
||||
|
||||
|
@ -190,7 +190,7 @@ async def confirm_path_payment_strict_receive_op(
|
||||
await confirm_output(
|
||||
op.destination_account,
|
||||
format_amount(op.destination_amount, op.destination_asset),
|
||||
title=TR.stellar__path_pay,
|
||||
TR.stellar__path_pay,
|
||||
)
|
||||
await confirm_asset_issuer(op.destination_asset)
|
||||
# confirm what the sender is using to pay
|
||||
@ -209,7 +209,7 @@ async def confirm_path_payment_strict_send_op(
|
||||
await confirm_output(
|
||||
op.destination_account,
|
||||
format_amount(op.destination_min, op.destination_asset),
|
||||
title=TR.stellar__path_pay_at_least,
|
||||
TR.stellar__path_pay_at_least,
|
||||
)
|
||||
await confirm_asset_issuer(op.destination_asset)
|
||||
# confirm what the sender is using to pay
|
||||
|
@ -736,12 +736,16 @@ def confirm_properties(
|
||||
br_name: str,
|
||||
title: str,
|
||||
props: Iterable[PropertyType],
|
||||
subtitle: str | None = None,
|
||||
hold: bool = False,
|
||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||
) -> Awaitable[None]:
|
||||
# Monospace flag for values that are bytes.
|
||||
items = [(prop[0], prop[1], isinstance(prop[1], bytes)) for prop in props]
|
||||
|
||||
if subtitle:
|
||||
title += ": " + subtitle
|
||||
|
||||
return raise_if_not_confirmed(
|
||||
trezorui_api.confirm_properties(
|
||||
title=title,
|
||||
|
@ -725,6 +725,7 @@ def confirm_properties(
|
||||
br_name: str,
|
||||
title: str,
|
||||
props: Iterable[PropertyType],
|
||||
subtitle: str | None = None,
|
||||
hold: bool = False,
|
||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||
) -> Awaitable[None]:
|
||||
@ -740,6 +741,9 @@ def confirm_properties(
|
||||
is_data = value and " " not in value
|
||||
return (key, value, bool(is_data))
|
||||
|
||||
if subtitle:
|
||||
title += ": " + subtitle
|
||||
|
||||
return raise_if_not_confirmed(
|
||||
trezorui_api.confirm_properties(
|
||||
title=title,
|
||||
|
@ -654,6 +654,7 @@ def confirm_properties(
|
||||
br_name: str,
|
||||
title: str,
|
||||
props: Iterable[PropertyType],
|
||||
subtitle: str | None = None,
|
||||
hold: bool = False,
|
||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||
) -> Awaitable[None]:
|
||||
@ -663,6 +664,7 @@ def confirm_properties(
|
||||
return raise_if_not_confirmed(
|
||||
trezorui_api.confirm_properties(
|
||||
title=title,
|
||||
subtitle=subtitle,
|
||||
items=items,
|
||||
hold=hold,
|
||||
),
|
||||
|
@ -565,7 +565,7 @@
|
||||
"pin__mismatch": "Zadané PIN kódy se neshodují!",
|
||||
"pin__pin_mismatch": "PIN se neshoduje",
|
||||
"pin__please_check_again": "Zkontrolujte to znovu.",
|
||||
"pin__reenter_new": "Znovu zadejte nový PIN",
|
||||
"pin__reenter_new": "Zopakujte PIN",
|
||||
"pin__reenter_to_confirm": "Znovu zadejte PIN pro potvrzení.",
|
||||
"pin__should_be_long": "PIN musí mít 4-50 číslic.",
|
||||
"pin__title_check_pin": "Zkontrolovat PIN",
|
||||
@ -953,11 +953,11 @@
|
||||
"wipe_code__diff_from_pin": "Kód pro vymazání se musí lišit od PIN kódu.",
|
||||
"wipe_code__disabled": "Kód pro vymazání zakázán.",
|
||||
"wipe_code__enabled": "Kód pro vymazání povolen.",
|
||||
"wipe_code__enter_new": "Vložte nový kód vymaz.",
|
||||
"wipe_code__enter_new": "Nový kód vymaz.",
|
||||
"wipe_code__info": "Kód pro vymazání lze použít k vymazání všech dat ze zařízení.",
|
||||
"wipe_code__invalid": "Chybný kód vymazání",
|
||||
"wipe_code__mismatch": "Zadané kódy pro vymazání se neshodují.",
|
||||
"wipe_code__reenter": "Zopakujte kód vymazání",
|
||||
"wipe_code__reenter": "Zopakujte kód vymaz.",
|
||||
"wipe_code__reenter_to_confirm": "Potvrďte dalším zadáním kódu vymazání.",
|
||||
"wipe_code__title_check": "Zkont. kód vymazání",
|
||||
"wipe_code__title_invalid": "Nesprávný kód pro vymazání",
|
||||
|
@ -565,7 +565,7 @@
|
||||
"pin__mismatch": "Die eingegebenen PINs stimmen nicht überein.",
|
||||
"pin__pin_mismatch": "PIN stimmt nicht",
|
||||
"pin__please_check_again": "Bitte noch einmal prüfen.",
|
||||
"pin__reenter_new": "Neue PIN neu eingeben",
|
||||
"pin__reenter_new": "PIN neu eingeben",
|
||||
"pin__reenter_to_confirm": "Gib die PIN zur Bestätigung erneut ein.",
|
||||
"pin__should_be_long": "PIN sollte aus 4-50 Ziffern bestehen.",
|
||||
"pin__title_check_pin": "PIN prüfen",
|
||||
|
@ -926,7 +926,7 @@
|
||||
"wipe_code__diff_from_pin": "The wipe code must be different from your PIN.",
|
||||
"wipe_code__disabled": "Wipe code disabled.",
|
||||
"wipe_code__enabled": "Wipe code enabled.",
|
||||
"wipe_code__enter_new": "Enter new wipe code",
|
||||
"wipe_code__enter_new": "New wipe code",
|
||||
"wipe_code__info": "Wipe code can be used to erase all data from this device.",
|
||||
"wipe_code__invalid": "Invalid wipe code",
|
||||
"wipe_code__mismatch": "The wipe codes you entered do not match.",
|
||||
|
@ -565,7 +565,7 @@
|
||||
"pin__mismatch": "Los PIN introducidos no coinciden.",
|
||||
"pin__pin_mismatch": "El PIN no coincide.",
|
||||
"pin__please_check_again": "Vuelve a comprobarlo.",
|
||||
"pin__reenter_new": "Reintroducir nuevo PIN.",
|
||||
"pin__reenter_new": "Reintroducir PIN.",
|
||||
"pin__reenter_to_confirm": "Vuelve a introducir el PIN para confirmar.",
|
||||
"pin__should_be_long": "El PIN debe tener 4-50 dígitos.",
|
||||
"pin__title_check_pin": "Revisar PIN",
|
||||
@ -953,11 +953,11 @@
|
||||
"wipe_code__diff_from_pin": "El código de borrar debe ser diferente del PIN.",
|
||||
"wipe_code__disabled": "El código de borrar se ha desactivado.",
|
||||
"wipe_code__enabled": "El código de borrar se ha activado.",
|
||||
"wipe_code__enter_new": "Meter nuevo cód.borrar",
|
||||
"wipe_code__enter_new": "Nuevo cód. borrar",
|
||||
"wipe_code__info": "Con el código de borrar eliminarás todos los datos del dispositivo.",
|
||||
"wipe_code__invalid": "Cód. borrar no válido",
|
||||
"wipe_code__mismatch": "Los códigos de borrar no coinciden.",
|
||||
"wipe_code__reenter": "Reingresar cód.borrar.",
|
||||
"wipe_code__reenter": "Reingresar cód. bor.",
|
||||
"wipe_code__reenter_to_confirm": "Reingresar cód.borrar para confirmar.",
|
||||
"wipe_code__title_check": "Revisar cód. borrar",
|
||||
"wipe_code__title_invalid": "Cód. borrar no válido",
|
||||
|
@ -565,7 +565,7 @@
|
||||
"pin__mismatch": "Les codes PIN saisis ne correspondent pas.",
|
||||
"pin__pin_mismatch": "Erreur de PIN",
|
||||
"pin__please_check_again": "Revérifiez.",
|
||||
"pin__reenter_new": "Réentrez nouv. PIN",
|
||||
"pin__reenter_new": "Réentrez PIN",
|
||||
"pin__reenter_to_confirm": "Réentrez le PIN pour conf.",
|
||||
"pin__should_be_long": "Le PIN doit être composé de 4 à 50 chiffres.",
|
||||
"pin__title_check_pin": "Vérifier PIN",
|
||||
@ -953,7 +953,7 @@
|
||||
"wipe_code__diff_from_pin": "Le code d'eff. doit être différent de votre PIN.",
|
||||
"wipe_code__disabled": "Code d'eff. désactivé.",
|
||||
"wipe_code__enabled": "Code d'eff. activé.",
|
||||
"wipe_code__enter_new": "Entrez nouv. code eff.",
|
||||
"wipe_code__enter_new": "Nouv. code d'eff.",
|
||||
"wipe_code__info": "Le code d'eff. peut être utilisé pour eff. les données de ce disp.",
|
||||
"wipe_code__invalid": "Code d'eff. non valide",
|
||||
"wipe_code__mismatch": "Les codes d'eff. ne correspondent pas.",
|
||||
|
@ -565,7 +565,7 @@
|
||||
"pin__mismatch": "I PIN immessi non corrispondono.",
|
||||
"pin__pin_mismatch": "PIN non corrispondente",
|
||||
"pin__please_check_again": "Ricontrolla.",
|
||||
"pin__reenter_new": "Reimmetti nuovo PIN",
|
||||
"pin__reenter_new": "Reimmetti PIN",
|
||||
"pin__reenter_to_confirm": "Reimmettere PIN per confermare.",
|
||||
"pin__should_be_long": "Lunghezza PIN 4-50 cifre.",
|
||||
"pin__title_check_pin": "Controlla PIN",
|
||||
|
@ -564,7 +564,7 @@
|
||||
"pin__mismatch": "Os PINs inseridos não coincidem!",
|
||||
"pin__pin_mismatch": "O PIN não coincide",
|
||||
"pin__please_check_again": "Verifique novamente.",
|
||||
"pin__reenter_new": "Reinsira o novo PIN",
|
||||
"pin__reenter_new": "Reinsira PIN",
|
||||
"pin__reenter_to_confirm": "Reinsira o PIN para confirmar.",
|
||||
"pin__should_be_long": "O PIN deve ter 4-50 dígitos.",
|
||||
"pin__title_check_pin": "Verificar PIN",
|
||||
@ -952,7 +952,7 @@
|
||||
"wipe_code__diff_from_pin": "O código de limpeza deve ser diferente do seu PIN.",
|
||||
"wipe_code__disabled": "Código de limpeza desativado.",
|
||||
"wipe_code__enabled": "Código de limpeza ativado.",
|
||||
"wipe_code__enter_new": "Inserir novo cód. limp.",
|
||||
"wipe_code__enter_new": "Novo cód. limpeza",
|
||||
"wipe_code__info": "O cód. limpeza pode apagar todos os dados deste dispositivo.",
|
||||
"wipe_code__invalid": "Cód. limpeza inválido",
|
||||
"wipe_code__mismatch": "Cód. limpeza não coincidem.",
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"current": {
|
||||
"merkle_root": "136cfad983788597b6218f51a94b93b14535f20111dc91aa953e6e9df462ab16",
|
||||
"datetime": "2025-03-27T18:08:46.572012",
|
||||
"commit": "b340d6c7b21d110b8bb4478c654b293bd3a977f0"
|
||||
"merkle_root": "478ee723935569e2d267a52f12ae0a1e2a0c3876574c47b8a518b30c9a8e724b",
|
||||
"datetime": "2025-04-17T10:14:37.075778",
|
||||
"commit": "a8251b09f977e75f551d136cb34131ca6f1394ea"
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
|
@ -859,11 +859,11 @@
|
||||
"wipe_code__diff_from_pin": "Silme kodu, PIN kodunuzdan farklı olmalıdır.",
|
||||
"wipe_code__disabled": "Silme kodu devre dışı.",
|
||||
"wipe_code__enabled": "Silme kodu etkin.",
|
||||
"wipe_code__enter_new": "Yeni silme kodu girin",
|
||||
"wipe_code__enter_new": "Yeni silme kodu",
|
||||
"wipe_code__info": "Silme kodu, cihazdaki tüm verileri silmek için kullanılabilir.",
|
||||
"wipe_code__invalid": "Geçersiz silme kodu",
|
||||
"wipe_code__mismatch": "Girdiğiniz silme kodları uyumlu değil.",
|
||||
"wipe_code__reenter": "Silme kod. yeniden girin",
|
||||
"wipe_code__reenter": "Silme kod. yeniden",
|
||||
"wipe_code__reenter_to_confirm": "Onaylamak için silme kodunu yeniden girin.",
|
||||
"wipe_code__title_check": "Si̇lme kod. kntrl et",
|
||||
"wipe_code__title_invalid": "Geçersi̇z si̇lme kodu",
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user