mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-12 14:16:06 +00:00
feat(core): support optional chunkification of addresses in receive and send flows
This commit is contained in:
parent
da3cab22fd
commit
bcb353a4a1
@ -36,6 +36,7 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_button_event;
|
||||
MP_QSTR_cancel_arrow;
|
||||
MP_QSTR_case_sensitive;
|
||||
MP_QSTR_chunkify;
|
||||
MP_QSTR_confirm_action;
|
||||
MP_QSTR_confirm_address;
|
||||
MP_QSTR_confirm_backup;
|
||||
|
@ -52,6 +52,40 @@ pub struct TextLayout {
|
||||
pub continues_from_prev_page: bool,
|
||||
}
|
||||
|
||||
/// Configuration for chunkifying the text into smaller parts.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Chunks {
|
||||
/// How many characters will be grouped in one chunk.
|
||||
pub chunk_size: usize,
|
||||
/// How big will be the space between chunks (in pixels).
|
||||
pub x_offset: i16,
|
||||
/// Optional characters that are wider and should be accounted for
|
||||
pub wider_chars: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Chunks {
|
||||
pub const fn new(chunk_size: usize, x_offset: i16) -> Self {
|
||||
Chunks {
|
||||
chunk_size,
|
||||
x_offset,
|
||||
wider_chars: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn with_wider_chars(mut self, wider_chars: &'static str) -> Self {
|
||||
self.wider_chars = Some(wider_chars);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_char_wider(self, ch: char) -> bool {
|
||||
if let Some(wider_chars) = self.wider_chars {
|
||||
wider_chars.contains(ch)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TextStyle {
|
||||
/// Text font ID.
|
||||
@ -76,6 +110,14 @@ pub struct TextStyle {
|
||||
pub line_breaking: LineBreaking,
|
||||
/// Specifies what to do at the end of the page.
|
||||
pub page_breaking: PageBreaking,
|
||||
|
||||
/// Optionally chunkify all the text with a specified chunk
|
||||
/// size and pixel offset for the next chunk.
|
||||
pub chunks: Option<Chunks>,
|
||||
|
||||
/// Optionally increase the vertical space between text lines
|
||||
/// (can be even negative, in which case it will decrease it).
|
||||
pub line_spacing: i16,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
@ -96,6 +138,8 @@ impl TextStyle {
|
||||
prev_page_ellipsis_icon: None,
|
||||
line_breaking: LineBreaking::BreakAtWhitespace,
|
||||
page_breaking: PageBreaking::CutAndInsertEllipsis,
|
||||
chunks: None,
|
||||
line_spacing: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +165,18 @@ impl TextStyle {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adding optional chunkification to the text.
|
||||
pub const fn with_chunks(mut self, chunks: Chunks) -> Self {
|
||||
self.chunks = Some(chunks);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adding optional change of vertical line spacing.
|
||||
pub const fn with_line_spacing(mut self, line_spacing: i16) -> Self {
|
||||
self.line_spacing = line_spacing;
|
||||
self
|
||||
}
|
||||
|
||||
fn ellipsis_width(&self) -> i16 {
|
||||
if let Some((icon, margin)) = self.ellipsis_icon {
|
||||
icon.toif.width() + margin
|
||||
@ -220,12 +276,13 @@ impl TextLayout {
|
||||
};
|
||||
|
||||
let remaining_width = self.bounds.x1 - cursor.x;
|
||||
let span = Span::fit_horizontally(
|
||||
let mut span = Span::fit_horizontally(
|
||||
remaining_text,
|
||||
remaining_width,
|
||||
self.style.text_font,
|
||||
self.style.line_breaking,
|
||||
line_ending_space,
|
||||
self.style.chunks,
|
||||
);
|
||||
|
||||
cursor.x += match self.align {
|
||||
@ -251,6 +308,9 @@ impl TextLayout {
|
||||
if span.advance.y > 0 {
|
||||
// We're advancing to the next line.
|
||||
|
||||
// Possibly making a bigger/smaller vertical jump
|
||||
span.advance.y += self.style.line_spacing;
|
||||
|
||||
// Check if we should be appending a hyphen at this point.
|
||||
if span.insert_hyphen_before_line_break {
|
||||
sink.hyphen(*cursor, self);
|
||||
@ -488,6 +548,7 @@ impl Span {
|
||||
text_font: impl GlyphMetrics,
|
||||
breaking: LineBreaking,
|
||||
line_ending_space: i16,
|
||||
chunks: Option<Chunks>,
|
||||
) -> Self {
|
||||
const ASCII_LF: char = '\n';
|
||||
const ASCII_CR: char = '\r';
|
||||
@ -537,6 +598,7 @@ impl Span {
|
||||
|
||||
let mut span_width = 0;
|
||||
let mut found_any_whitespace = false;
|
||||
let mut chunks_wider_chars = 0;
|
||||
|
||||
let mut char_indices_iter = text.char_indices().peekable();
|
||||
// Iterating manually because we need a reference to the iterator inside the
|
||||
@ -544,6 +606,22 @@ impl Span {
|
||||
while let Some((i, ch)) = char_indices_iter.next() {
|
||||
let char_width = text_font.char_width(ch);
|
||||
|
||||
// When there is a set chunk size and we reach it,
|
||||
// adjust the line advances and return the line.
|
||||
if let Some(chunkify_config) = chunks {
|
||||
if i == chunkify_config.chunk_size {
|
||||
line.advance.y = 0;
|
||||
// Decreasing the offset for each wider character in the chunk
|
||||
line.advance.x += chunkify_config.x_offset - chunks_wider_chars;
|
||||
return line;
|
||||
} else {
|
||||
// Counting all the wider characters in the chunk
|
||||
if chunkify_config.is_char_wider(ch) {
|
||||
chunks_wider_chars += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consider if we could be breaking the line at this position.
|
||||
if is_whitespace(ch) && span_width + complete_word_end_width <= max_width {
|
||||
// Break before the whitespace, without hyphen.
|
||||
@ -679,6 +757,7 @@ mod tests {
|
||||
FIXED_FONT,
|
||||
LineBreaking::BreakAtWhitespace,
|
||||
0,
|
||||
None,
|
||||
);
|
||||
spans.push((
|
||||
&remaining_text[..span.length],
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
layout::{LayoutFit, LayoutSink, TextLayout},
|
||||
layout::{Chunks, LayoutFit, LayoutSink, TextLayout},
|
||||
LineBreaking, TextStyle,
|
||||
};
|
||||
|
||||
@ -90,6 +90,12 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
|
||||
cursor.x += offset.x;
|
||||
cursor.y += offset.y;
|
||||
}
|
||||
Op::Chunkify(chunks) => {
|
||||
self.layout.style.chunks = chunks;
|
||||
}
|
||||
Op::LineSpacing(line_spacing) => {
|
||||
self.layout.style.line_spacing = line_spacing;
|
||||
}
|
||||
// Moving to the next page
|
||||
Op::NextPage => {
|
||||
// Pretending that nothing more fits on current page to force
|
||||
@ -219,6 +225,14 @@ impl<T: StringType + Clone> OpTextLayout<T> {
|
||||
pub fn line_breaking(self, line_breaking: LineBreaking) -> Self {
|
||||
self.with_new_item(Op::LineBreaking(line_breaking))
|
||||
}
|
||||
|
||||
pub fn chunks(self, chunks: Option<Chunks>) -> Self {
|
||||
self.with_new_item(Op::Chunkify(chunks))
|
||||
}
|
||||
|
||||
pub fn line_spacing(self, spacing: i16) -> Self {
|
||||
self.with_new_item(Op::LineSpacing(spacing))
|
||||
}
|
||||
}
|
||||
|
||||
// Op-adding aggregation operations
|
||||
@ -238,6 +252,14 @@ impl<T: StringType + Clone> OpTextLayout<T> {
|
||||
pub fn text_demibold(self, text: T) -> Self {
|
||||
self.font(Font::DEMIBOLD).text(text)
|
||||
}
|
||||
|
||||
pub fn chunkify_text(self, chunks: Option<(Chunks, i16)>) -> Self {
|
||||
if let Some(chunks) = chunks {
|
||||
self.chunks(Some(chunks.0)).line_spacing(chunks.1)
|
||||
} else {
|
||||
self.chunks(None).line_spacing(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -258,4 +280,8 @@ pub enum Op<T: StringType> {
|
||||
CursorOffset(Offset),
|
||||
/// Force continuing on the next page.
|
||||
NextPage,
|
||||
/// Render the following text in a chunkified way. None will disable that.
|
||||
Chunkify(Option<Chunks>),
|
||||
/// Change the line vertical line spacing.
|
||||
LineSpacing(i16),
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
||||
},
|
||||
TextStyle,
|
||||
},
|
||||
ComponentExt, FormattedText, LineBreaking, Timeout,
|
||||
ComponentExt, FormattedText, Timeout,
|
||||
},
|
||||
display, geometry,
|
||||
layout::{
|
||||
@ -557,6 +557,7 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
let amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?;
|
||||
let address_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_title)?.try_into()?;
|
||||
let amount_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_title)?.try_into()?;
|
||||
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
|
||||
|
||||
let get_page = move |page_index| {
|
||||
// Showing two screens - the recipient address and summary confirmation
|
||||
@ -567,11 +568,20 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
let btn_actions = ButtonActions::cancel_none_next();
|
||||
// Not putting hyphens in the address.
|
||||
// Potentially adding address label in different font.
|
||||
let mut ops = OpTextLayout::new(theme::TEXT_MONO)
|
||||
.line_breaking(LineBreaking::BreakWordsNoHyphen);
|
||||
let mut ops = OpTextLayout::new(theme::TEXT_MONO_DATA);
|
||||
if !address_label.is_empty() {
|
||||
// NOTE: need to explicitly turn off the chunkification before rendering the
|
||||
// address label (for some reason it does not help to turn it off after
|
||||
// rendering the chunks)
|
||||
if chunkify {
|
||||
ops = ops.chunkify_text(None);
|
||||
}
|
||||
ops = ops.text_normal(address_label.clone()).newline();
|
||||
}
|
||||
if chunkify {
|
||||
// Chunkifying the address into smaller pieces when requested
|
||||
ops = ops.chunkify_text(Some((theme::MONO_CHUNKS, 2)));
|
||||
}
|
||||
ops = ops.text_mono(address.clone());
|
||||
let formatted = FormattedText::new(ops).vertically_centered();
|
||||
Page::new(btn_layout, btn_actions, formatted).with_title(address_title.clone())
|
||||
@ -752,15 +762,20 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?;
|
||||
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
|
||||
|
||||
let get_page = move |page_index| {
|
||||
assert!(page_index == 0);
|
||||
|
||||
let btn_layout = ButtonLayout::cancel_armed_info("CONFIRM".into());
|
||||
let btn_actions = ButtonActions::cancel_confirm_info();
|
||||
let ops = OpTextLayout::new(theme::TEXT_MONO)
|
||||
.line_breaking(LineBreaking::BreakWordsNoHyphen)
|
||||
.text_mono(address.clone());
|
||||
let style = if chunkify {
|
||||
// Chunkifying the address into smaller pieces when requested
|
||||
theme::TEXT_MONO_ADDRESS_CHUNKS
|
||||
} else {
|
||||
theme::TEXT_MONO_DATA
|
||||
};
|
||||
let ops = OpTextLayout::new(style).text_mono(address.clone());
|
||||
let formatted = FormattedText::new(ops).vertically_centered();
|
||||
Page::new(btn_layout, btn_actions, formatted).with_title(title.clone())
|
||||
};
|
||||
@ -1584,6 +1599,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// data: str,
|
||||
/// description: str | None, # unused on TR
|
||||
/// extra: str | None, # unused on TR
|
||||
/// chunkify: bool = False,
|
||||
/// ) -> object:
|
||||
/// """Confirm address."""
|
||||
Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(),
|
||||
@ -1659,6 +1675,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// amount: str,
|
||||
/// address_title: str,
|
||||
/// amount_title: str,
|
||||
/// chunkify: bool = False,
|
||||
/// ) -> object:
|
||||
/// """Confirm output."""
|
||||
Qstr::MP_QSTR_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(),
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::ui::{
|
||||
component::{text::TextStyle, LineBreaking, PageBreaking},
|
||||
component::{
|
||||
text::{layout::Chunks, TextStyle},
|
||||
LineBreaking, PageBreaking,
|
||||
},
|
||||
display::{toif::Icon, Color, Font},
|
||||
geometry::Offset,
|
||||
};
|
||||
@ -35,6 +38,13 @@ pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, FG, BG, FG, FG)
|
||||
/// Mono data text does not have hyphens
|
||||
pub const TEXT_MONO_DATA: TextStyle =
|
||||
TEXT_MONO.with_line_breaking(LineBreaking::BreakWordsNoHyphen);
|
||||
pub const TEXT_MONO_ADDRESS_CHUNKS: TextStyle = TEXT_MONO_DATA
|
||||
.with_chunks(MONO_CHUNKS)
|
||||
.with_line_spacing(2)
|
||||
.with_ellipsis_icon(ICON_NEXT_PAGE, -2);
|
||||
|
||||
// Chunks for this model, with accounting for some wider characters in MONO font
|
||||
pub const MONO_CHUNKS: Chunks = Chunks::new(4, 4).with_wider_chars("mMwW");
|
||||
|
||||
/// Convert Python-side numeric id to a `TextStyle`.
|
||||
pub fn textstyle_number(num: i32) -> &'static TextStyle {
|
||||
|
@ -496,6 +496,7 @@ struct ConfirmBlobParams {
|
||||
verb_cancel: Option<StrBuffer>,
|
||||
info_button: bool,
|
||||
hold: bool,
|
||||
chunkify: bool,
|
||||
}
|
||||
|
||||
impl ConfirmBlobParams {
|
||||
@ -517,6 +518,7 @@ impl ConfirmBlobParams {
|
||||
verb_cancel,
|
||||
info_button: false,
|
||||
hold,
|
||||
chunkify: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -535,6 +537,11 @@ impl ConfirmBlobParams {
|
||||
self
|
||||
}
|
||||
|
||||
fn with_chunkify(mut self, chunkify: bool) -> Self {
|
||||
self.chunkify = chunkify;
|
||||
self
|
||||
}
|
||||
|
||||
fn into_layout(self) -> Result<Obj, Error> {
|
||||
let paragraphs = ConfirmBlob {
|
||||
description: self.description.unwrap_or_else(StrBuffer::empty),
|
||||
@ -542,7 +549,11 @@ impl ConfirmBlobParams {
|
||||
data: self.data.try_into()?,
|
||||
description_font: &theme::TEXT_NORMAL,
|
||||
extra_font: &theme::TEXT_DEMIBOLD,
|
||||
data_font: &theme::TEXT_MONO,
|
||||
data_font: if self.chunkify {
|
||||
&theme::TEXT_MONO_ADDRESS_CHUNKS
|
||||
} else {
|
||||
&theme::TEXT_MONO
|
||||
},
|
||||
}
|
||||
.into_paragraphs();
|
||||
|
||||
@ -611,6 +622,21 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
|
||||
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
|
||||
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
|
||||
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
|
||||
|
||||
let data_style = if chunkify {
|
||||
// Longer addresses have smaller x_offset so they fit even with scrollbar
|
||||
// (as they will be shown on more than one page)
|
||||
const FITS_ON_ONE_PAGE: usize = 16 * 4;
|
||||
let address: StrBuffer = data.try_into()?;
|
||||
if address.len() <= FITS_ON_ONE_PAGE {
|
||||
&theme::TEXT_MONO_ADDRESS_CHUNKS
|
||||
} else {
|
||||
&theme::TEXT_MONO_ADDRESS_CHUNKS_SMALLER_X_OFFSET
|
||||
}
|
||||
} else {
|
||||
&theme::TEXT_MONO
|
||||
};
|
||||
|
||||
let paragraphs = ConfirmBlob {
|
||||
description: description.unwrap_or_else(StrBuffer::empty),
|
||||
@ -618,7 +644,7 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
|
||||
data: data.try_into()?,
|
||||
description_font: &theme::TEXT_NORMAL,
|
||||
extra_font: &theme::TEXT_DEMIBOLD,
|
||||
data_font: &theme::TEXT_MONO,
|
||||
data_font: data_style,
|
||||
}
|
||||
.into_paragraphs();
|
||||
|
||||
@ -805,10 +831,12 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
|
||||
.unwrap_or_else(|_| Obj::const_none())
|
||||
.try_into_option()?;
|
||||
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
|
||||
|
||||
ConfirmBlobParams::new(title, value, description, verb, verb_cancel, hold)
|
||||
.with_subtitle(subtitle)
|
||||
.with_info_button(info_button)
|
||||
.with_chunkify(chunkify)
|
||||
.into_layout()
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -1680,6 +1708,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// data: str | bytes,
|
||||
/// description: str | None,
|
||||
/// extra: str | None,
|
||||
/// chunkify: bool = False,
|
||||
/// ) -> object:
|
||||
/// """Confirm address. Similar to `confirm_blob` but has corner info button
|
||||
/// and allows left swipe which does the same thing as the button."""
|
||||
@ -1734,6 +1763,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// verb_cancel: str | None = None,
|
||||
/// info_button: bool = False,
|
||||
/// hold: bool = False,
|
||||
/// chunkify: bool = False,
|
||||
/// ) -> object:
|
||||
/// """Confirm value. Merge of confirm_total and confirm_output."""
|
||||
Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(),
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
time::Duration,
|
||||
ui::{
|
||||
component::{
|
||||
text::{LineBreaking, PageBreaking, TextStyle},
|
||||
text::{layout::Chunks, LineBreaking, PageBreaking, TextStyle},
|
||||
FixedHeightBar,
|
||||
},
|
||||
display::{Color, Font, Icon},
|
||||
@ -517,6 +517,18 @@ pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, FG, BG, GREY_LIGHT,
|
||||
.with_page_breaking(PageBreaking::CutAndInsertEllipsisBoth)
|
||||
.with_ellipsis_icon(ICON_PAGE_NEXT, 0)
|
||||
.with_prev_page_icon(ICON_PAGE_PREV, 0);
|
||||
/// Makes sure that the displayed text (usually address) will get divided into
|
||||
/// smaller chunks.
|
||||
pub const TEXT_MONO_ADDRESS_CHUNKS: TextStyle = TEXT_MONO
|
||||
.with_chunks(Chunks::new(4, 9))
|
||||
.with_line_spacing(5);
|
||||
/// Smaller horizontal chunk offset, used e.g. for long Cardano addresses.
|
||||
/// Also moving the next page ellipsis to the left (as there is a space on the
|
||||
/// left).
|
||||
pub const TEXT_MONO_ADDRESS_CHUNKS_SMALLER_X_OFFSET: TextStyle = TEXT_MONO
|
||||
.with_chunks(Chunks::new(4, 7))
|
||||
.with_line_spacing(5)
|
||||
.with_ellipsis_icon(ICON_PAGE_NEXT, -12);
|
||||
|
||||
/// Convert Python-side numeric id to a `TextStyle`.
|
||||
pub fn textstyle_number(num: i32) -> &'static TextStyle {
|
||||
|
@ -50,6 +50,7 @@ def confirm_address(
|
||||
data: str,
|
||||
description: str | None, # unused on TR
|
||||
extra: str | None, # unused on TR
|
||||
chunkify: bool = False,
|
||||
) -> object:
|
||||
"""Confirm address."""
|
||||
|
||||
@ -132,6 +133,7 @@ def confirm_output(
|
||||
amount: str,
|
||||
address_title: str,
|
||||
amount_title: str,
|
||||
chunkify: bool = False,
|
||||
) -> object:
|
||||
"""Confirm output."""
|
||||
|
||||
@ -496,6 +498,7 @@ def confirm_address(
|
||||
data: str | bytes,
|
||||
description: str | None,
|
||||
extra: str | None,
|
||||
chunkify: bool = False,
|
||||
) -> object:
|
||||
"""Confirm address. Similar to `confirm_blob` but has corner info button
|
||||
and allows left swipe which does the same thing as the button."""
|
||||
@ -555,6 +558,7 @@ def confirm_value(
|
||||
verb_cancel: str | None = None,
|
||||
info_button: bool = False,
|
||||
hold: bool = False,
|
||||
chunkify: bool = False,
|
||||
) -> object:
|
||||
"""Confirm value. Merge of confirm_total and confirm_output."""
|
||||
|
||||
|
@ -26,6 +26,8 @@ async def get_address(msg: BinanceGetAddress, keychain: Keychain) -> BinanceAddr
|
||||
pubkey = node.public_key()
|
||||
address = address_from_public_key(pubkey, HRP)
|
||||
if msg.show_display:
|
||||
await show_address(address, path=paths.address_n_to_str(address_n))
|
||||
await show_address(
|
||||
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
|
||||
)
|
||||
|
||||
return BinanceAddress(address=address)
|
||||
|
@ -34,21 +34,18 @@ async def require_confirm_transfer(msg: BinanceTransferMsg) -> None:
|
||||
for txoutput in msg.outputs:
|
||||
make_input_output_pages(txoutput, "Confirm output")
|
||||
|
||||
await _confirm_transfer(items)
|
||||
await _confirm_transfer(items, chunkify=bool(msg.chunkify))
|
||||
|
||||
|
||||
async def _confirm_transfer(inputs_outputs: Sequence[tuple[str, str, str]]) -> None:
|
||||
async def _confirm_transfer(
|
||||
inputs_outputs: Sequence[tuple[str, str, str]], chunkify: bool
|
||||
) -> None:
|
||||
from trezor.ui.layouts import confirm_output
|
||||
|
||||
for index, (title, amount, address) in enumerate(inputs_outputs):
|
||||
# Having hold=True on the last item
|
||||
hold = index == len(inputs_outputs) - 1
|
||||
await confirm_output(
|
||||
address,
|
||||
amount,
|
||||
title,
|
||||
hold=hold,
|
||||
)
|
||||
await confirm_output(address, amount, title, hold=hold, chunkify=chunkify)
|
||||
|
||||
|
||||
async def require_confirm_cancel(msg: BinanceCancelMsg) -> None:
|
||||
|
@ -113,6 +113,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
|
||||
multisig_index=multisig_index,
|
||||
xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes),
|
||||
account=f"Multisig {multisig.m} of {len(pubnodes)}",
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
else:
|
||||
account_name = address_n_to_name(coin, address_n, script_type)
|
||||
@ -128,6 +129,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
|
||||
case_sensitive=address_case_sensitive,
|
||||
path=path,
|
||||
account=account,
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return Address(address=address, mac=mac)
|
||||
|
@ -356,6 +356,7 @@ class AccountType:
|
||||
coin: coininfo.CoinInfo,
|
||||
address_n: Bip32Path,
|
||||
script_type: InputScriptType | None,
|
||||
show_account_str: bool,
|
||||
) -> str | None:
|
||||
pattern = self.pattern
|
||||
if self.account_level:
|
||||
@ -373,6 +374,8 @@ class AccountType:
|
||||
return None
|
||||
|
||||
name = self.account_name
|
||||
if show_account_str:
|
||||
name = f"{self.account_name} account"
|
||||
account_pos = pattern.find("/account'")
|
||||
if account_pos >= 0:
|
||||
i = pattern.count("/", 0, account_pos)
|
||||
@ -387,6 +390,7 @@ def address_n_to_name(
|
||||
address_n: Bip32Path,
|
||||
script_type: InputScriptType | None = None,
|
||||
account_level: bool = False,
|
||||
show_account_str: bool = False,
|
||||
) -> str | None:
|
||||
ACCOUNT_TYPES = (
|
||||
AccountType(
|
||||
@ -446,7 +450,7 @@ def address_n_to_name(
|
||||
)
|
||||
|
||||
for account in ACCOUNT_TYPES:
|
||||
name = account.get_name(coin, address_n, script_type)
|
||||
name = account.get_name(coin, address_n, script_type, show_account_str)
|
||||
if name:
|
||||
return name
|
||||
|
||||
|
@ -144,6 +144,7 @@ class BasicApprover(Approver):
|
||||
super().__init__(tx, coin)
|
||||
self.change_count = 0 # the number of change-outputs
|
||||
self.foreign_address_confirmed = False
|
||||
self.chunkify = bool(tx.chunkify)
|
||||
|
||||
async def add_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None:
|
||||
if not validate_path_against_script_type(self.coin, txi):
|
||||
@ -224,7 +225,11 @@ class BasicApprover(Approver):
|
||||
# Ask user to confirm output, unless it is part of a payment
|
||||
# request, which gets confirmed separately.
|
||||
await helpers.confirm_output(
|
||||
txo, self.coin, self.amount_unit, self.external_output_index
|
||||
txo,
|
||||
self.coin,
|
||||
self.amount_unit,
|
||||
self.external_output_index,
|
||||
self.chunkify,
|
||||
)
|
||||
self.external_output_index += 1
|
||||
|
||||
|
@ -44,11 +44,13 @@ class UiConfirmOutput(UiConfirm):
|
||||
coin: CoinInfo,
|
||||
amount_unit: AmountUnit,
|
||||
output_index: int,
|
||||
chunkify: bool,
|
||||
):
|
||||
self.output = output
|
||||
self.coin = coin
|
||||
self.amount_unit = amount_unit
|
||||
self.output_index = output_index
|
||||
self.chunkify = chunkify
|
||||
|
||||
def confirm_dialog(self) -> Awaitable[Any]:
|
||||
return layout.confirm_output(
|
||||
@ -56,6 +58,7 @@ class UiConfirmOutput(UiConfirm):
|
||||
self.coin,
|
||||
self.amount_unit,
|
||||
self.output_index,
|
||||
self.chunkify,
|
||||
)
|
||||
|
||||
|
||||
@ -238,8 +241,8 @@ class UiConfirmMultipleAccounts(UiConfirm):
|
||||
return layout.confirm_multiple_accounts()
|
||||
|
||||
|
||||
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
|
||||
return (yield UiConfirmOutput(output, coin, amount_unit, output_index))
|
||||
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
|
||||
return (yield UiConfirmOutput(output, coin, amount_unit, output_index, chunkify))
|
||||
|
||||
|
||||
def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
|
||||
|
@ -62,6 +62,7 @@ async def confirm_output(
|
||||
coin: CoinInfo,
|
||||
amount_unit: AmountUnit,
|
||||
output_index: int,
|
||||
chunkify: bool,
|
||||
) -> None:
|
||||
from trezor.enums import OutputScriptType
|
||||
|
||||
@ -97,9 +98,18 @@ async def confirm_output(
|
||||
|
||||
address_label = None
|
||||
if output.address_n and not output.multisig:
|
||||
from trezor import utils
|
||||
|
||||
# Showing the account string only for T2B1 model
|
||||
show_account_str = utils.INTERNAL_MODEL == "T2B1"
|
||||
script_type = CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[output.script_type]
|
||||
address_label = (
|
||||
address_n_to_name(coin, output.address_n, script_type)
|
||||
address_n_to_name(
|
||||
coin,
|
||||
output.address_n,
|
||||
script_type,
|
||||
show_account_str=show_account_str,
|
||||
)
|
||||
or f"address path {address_n_to_str(output.address_n)}"
|
||||
)
|
||||
|
||||
@ -109,6 +119,7 @@ async def confirm_output(
|
||||
title=title,
|
||||
address_label=address_label,
|
||||
output_index=output_index,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
|
||||
await layout
|
||||
@ -147,7 +158,7 @@ async def confirm_payment_request(
|
||||
) -> Any:
|
||||
from trezor import wire
|
||||
|
||||
memo_texts = []
|
||||
memo_texts: list[str] = []
|
||||
for m in msg.memos:
|
||||
if m.text_memo is not None:
|
||||
memo_texts.append(m.text_memo.text)
|
||||
|
@ -39,6 +39,8 @@ async def get_address(
|
||||
Credential.payment_credential(address_parameters),
|
||||
Credential.stake_credential(address_parameters),
|
||||
)
|
||||
await show_cardano_address(address_parameters, address, msg.protocol_magic)
|
||||
await show_cardano_address(
|
||||
address_parameters, address, msg.protocol_magic, chunkify=bool(msg.chunkify)
|
||||
)
|
||||
|
||||
return CardanoAddress(address=address)
|
||||
|
@ -210,6 +210,7 @@ async def confirm_sending(
|
||||
to: str,
|
||||
output_type: Literal["address", "change", "collateral-return"],
|
||||
network_id: int,
|
||||
chunkify: bool,
|
||||
) -> None:
|
||||
if output_type == "address":
|
||||
title = "Sending"
|
||||
@ -225,6 +226,7 @@ async def confirm_sending(
|
||||
format_coin_amount(ada_amount, network_id),
|
||||
title,
|
||||
br_code=ButtonRequestType.Other,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
|
||||
|
||||
@ -898,6 +900,7 @@ async def show_cardano_address(
|
||||
address_parameters: messages.CardanoAddressParametersType,
|
||||
address: str,
|
||||
protocol_magic: int,
|
||||
chunkify: bool,
|
||||
) -> None:
|
||||
CAT = CardanoAddressType # local_cache_global
|
||||
|
||||
@ -925,4 +928,5 @@ async def show_cardano_address(
|
||||
path=path,
|
||||
account=account,
|
||||
network=network_name,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
|
@ -366,6 +366,7 @@ class Signer:
|
||||
address,
|
||||
"change" if self._is_change_output(output) else "address",
|
||||
self.msg.network_id,
|
||||
chunkify=bool(self.msg.chunkify),
|
||||
)
|
||||
|
||||
async def _show_output_credentials(
|
||||
@ -1043,6 +1044,7 @@ class Signer:
|
||||
address,
|
||||
"collateral-return",
|
||||
self.msg.network_id,
|
||||
chunkify=bool(self.msg.chunkify),
|
||||
)
|
||||
|
||||
def _should_show_collateral_return_init(self, output: CardanoTxOutput) -> bool:
|
||||
|
@ -32,6 +32,8 @@ async def get_address(
|
||||
address = address_from_bytes(node.ethereum_pubkeyhash(), defs.network)
|
||||
|
||||
if msg.show_display:
|
||||
await show_address(address, path=paths.address_n_to_str(address_n))
|
||||
await show_address(
|
||||
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
|
||||
)
|
||||
|
||||
return EthereumAddress(address=address)
|
||||
|
@ -69,6 +69,7 @@ async def get_address(msg: MoneroGetAddress, keychain: Keychain) -> MoneroAddres
|
||||
addr,
|
||||
address_qr="monero:" + addr,
|
||||
path=paths.address_n_to_str(msg.address_n),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return MoneroAddress(address=addr.encode())
|
||||
|
@ -126,7 +126,9 @@ async def require_confirm_transaction(
|
||||
cur_payment = payment_id
|
||||
else:
|
||||
cur_payment = None
|
||||
await _require_confirm_output(dst, network_type, cur_payment)
|
||||
await _require_confirm_output(
|
||||
dst, network_type, cur_payment, chunkify=bool(tsx_data.chunkify)
|
||||
)
|
||||
|
||||
if (
|
||||
payment_id
|
||||
@ -143,6 +145,7 @@ async def _require_confirm_output(
|
||||
dst: MoneroTransactionDestinationEntry,
|
||||
network_type: MoneroNetworkType,
|
||||
payment_id: bytes | None,
|
||||
chunkify: bool,
|
||||
) -> None:
|
||||
"""
|
||||
Single transaction destination confirmation
|
||||
@ -161,6 +164,7 @@ async def _require_confirm_output(
|
||||
addr,
|
||||
_format_amount(dst.amount),
|
||||
br_code=BRT_SignTx,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
|
||||
|
||||
|
@ -35,6 +35,7 @@ async def get_address(msg: NEMGetAddress, keychain: Keychain) -> NEMAddress:
|
||||
case_sensitive=False,
|
||||
path=address_n_to_str(address_n),
|
||||
network=get_network_str(network),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return NEMAddress(address=address)
|
||||
|
@ -46,7 +46,9 @@ async def sign_tx(msg: NEMSignTx, keychain: Keychain) -> NEMSignedTx:
|
||||
common = transaction
|
||||
|
||||
if msg.transfer:
|
||||
tx = await transfer.transfer(public_key, common, msg.transfer, node)
|
||||
tx = await transfer.transfer(
|
||||
public_key, common, msg.transfer, node, chunkify=bool(msg.chunkify)
|
||||
)
|
||||
elif msg.provision_namespace:
|
||||
tx = await namespace.namespace(public_key, common, msg.provision_namespace)
|
||||
elif msg.mosaic_creation:
|
||||
|
@ -12,11 +12,12 @@ async def transfer(
|
||||
common: NEMTransactionCommon,
|
||||
transfer: NEMTransfer,
|
||||
node: bip32.HDNode,
|
||||
chunkify: bool,
|
||||
) -> bytes:
|
||||
transfer.mosaics = serialize.canonicalize_mosaics(transfer.mosaics)
|
||||
payload, encrypted = serialize.get_transfer_payload(transfer, node)
|
||||
|
||||
await layout.ask_transfer(common, transfer, encrypted)
|
||||
await layout.ask_transfer(common, transfer, encrypted, chunkify)
|
||||
|
||||
w = serialize.serialize_transfer(common, transfer, public_key, payload, encrypted)
|
||||
for mosaic in transfer.mosaics:
|
||||
|
@ -20,6 +20,7 @@ async def ask_transfer(
|
||||
common: NEMTransactionCommon,
|
||||
transfer: NEMTransfer,
|
||||
encrypted: bool,
|
||||
chunkify: bool,
|
||||
) -> None:
|
||||
from trezor.ui.layouts import confirm_output, confirm_text
|
||||
|
||||
@ -42,6 +43,7 @@ async def ask_transfer(
|
||||
await confirm_output(
|
||||
transfer.recipient,
|
||||
f"{format_amount(_get_xem_amount(transfer), NEM_MAX_DIVISIBILITY)} XEM",
|
||||
chunkify=chunkify,
|
||||
)
|
||||
|
||||
await require_confirm_final(common.fee)
|
||||
|
@ -25,6 +25,10 @@ async def get_address(msg: RippleGetAddress, keychain: Keychain) -> RippleAddres
|
||||
address = address_from_public_key(pubkey)
|
||||
|
||||
if msg.show_display:
|
||||
await show_address(address, path=paths.address_n_to_str(msg.address_n))
|
||||
await show_address(
|
||||
address,
|
||||
path=paths.address_n_to_str(msg.address_n),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return RippleAddress(address=address)
|
||||
|
@ -22,7 +22,7 @@ async def require_confirm_destination_tag(tag: int) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def require_confirm_tx(to: str, value: int) -> None:
|
||||
async def require_confirm_tx(to: str, value: int, chunkify: bool = False) -> None:
|
||||
from trezor.ui.layouts import confirm_output
|
||||
|
||||
await confirm_output(to, format_amount(value, DECIMALS) + " XRP")
|
||||
await confirm_output(to, format_amount(value, DECIMALS) + " XRP", chunkify=chunkify)
|
||||
|
@ -47,7 +47,9 @@ async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx:
|
||||
|
||||
if payment.destination_tag is not None:
|
||||
await layout.require_confirm_destination_tag(payment.destination_tag)
|
||||
await layout.require_confirm_tx(payment.destination, payment.amount)
|
||||
await layout.require_confirm_tx(
|
||||
payment.destination, payment.amount, chunkify=bool(msg.chunkify)
|
||||
)
|
||||
await layout.require_confirm_total(payment.amount + msg.fee, msg.fee)
|
||||
|
||||
# Signs and encodes signature into DER format
|
||||
|
@ -25,6 +25,8 @@ async def get_address(msg: StellarGetAddress, keychain: Keychain) -> StellarAddr
|
||||
|
||||
if msg.show_display:
|
||||
path = paths.address_n_to_str(msg.address_n)
|
||||
await show_address(address, case_sensitive=False, path=path)
|
||||
await show_address(
|
||||
address, case_sensitive=False, path=path, chunkify=bool(msg.chunkify)
|
||||
)
|
||||
|
||||
return StellarAddress(address=address)
|
||||
|
@ -29,6 +29,10 @@ async def get_address(msg: TezosGetAddress, keychain: Keychain) -> TezosAddress:
|
||||
address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX)
|
||||
|
||||
if msg.show_display:
|
||||
await show_address(address, path=paths.address_n_to_str(msg.address_n))
|
||||
await show_address(
|
||||
address,
|
||||
path=paths.address_n_to_str(msg.address_n),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return TezosAddress(address=address)
|
||||
|
@ -4,13 +4,14 @@ from trezor.ui.layouts import confirm_address, confirm_metadata, confirm_propert
|
||||
BR_SIGN_TX = ButtonRequestType.SignTx # global_import_cache
|
||||
|
||||
|
||||
async def require_confirm_tx(to: str, value: int) -> None:
|
||||
async def require_confirm_tx(to: str, value: int, chunkify: bool = False) -> None:
|
||||
from trezor.ui.layouts import confirm_output
|
||||
|
||||
await confirm_output(
|
||||
to,
|
||||
format_tezos_amount(value),
|
||||
br_code=BR_SIGN_TX,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
|
||||
|
||||
|
@ -71,12 +71,16 @@ async def sign_tx(msg: TezosSignTx, keychain: Keychain) -> TezosSignedTx:
|
||||
# operation to transfer tokens from a smart contract to an implicit account or a smart contract
|
||||
elif transfer is not None:
|
||||
to = _get_address_from_contract(transfer.destination)
|
||||
await layout.require_confirm_tx(to, transfer.amount)
|
||||
await layout.require_confirm_tx(
|
||||
to, transfer.amount, chunkify=bool(msg.chunkify)
|
||||
)
|
||||
await layout.require_confirm_fee(transfer.amount, fee)
|
||||
else:
|
||||
# transactions from an implicit account
|
||||
to = _get_address_from_contract(transaction.destination)
|
||||
await layout.require_confirm_tx(to, transaction.amount)
|
||||
await layout.require_confirm_tx(
|
||||
to, transaction.amount, chunkify=bool(msg.chunkify)
|
||||
)
|
||||
await layout.require_confirm_fee(transaction.amount, fee)
|
||||
|
||||
elif origination is not None:
|
||||
|
@ -466,6 +466,7 @@ async def show_address(
|
||||
mismatch_title: str = "ADDRESS MISMATCH?",
|
||||
br_type: str = "show_address",
|
||||
br_code: ButtonRequestType = ButtonRequestType.Address,
|
||||
chunkify: bool = False,
|
||||
) -> None:
|
||||
send_button_request = True
|
||||
if title is None:
|
||||
@ -482,6 +483,7 @@ async def show_address(
|
||||
data=address,
|
||||
description="", # unused on TR
|
||||
extra=None, # unused on TR
|
||||
chunkify=chunkify,
|
||||
)
|
||||
)
|
||||
if send_button_request:
|
||||
@ -550,6 +552,7 @@ def show_pubkey(
|
||||
br_type=br_type,
|
||||
br_code=ButtonRequestType.PublicKey,
|
||||
mismatch_title=mismatch_title,
|
||||
chunkify=False,
|
||||
)
|
||||
|
||||
|
||||
@ -652,6 +655,7 @@ async def confirm_output(
|
||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||
address_label: str | None = None,
|
||||
output_index: int | None = None,
|
||||
chunkify: bool = False,
|
||||
) -> None:
|
||||
address_title = (
|
||||
"RECIPIENT" if output_index is None else f"RECIPIENT #{output_index + 1}"
|
||||
@ -667,6 +671,7 @@ async def confirm_output(
|
||||
address_title=address_title,
|
||||
amount_title=amount_title,
|
||||
amount=amount,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
),
|
||||
"confirm_output",
|
||||
|
@ -417,6 +417,7 @@ async def show_address(
|
||||
mismatch_title: str = "Address mismatch?",
|
||||
br_type: str = "show_address",
|
||||
br_code: ButtonRequestType = ButtonRequestType.Address,
|
||||
chunkify: bool = False,
|
||||
) -> None:
|
||||
send_button_request = True
|
||||
if title is None:
|
||||
@ -435,6 +436,7 @@ async def show_address(
|
||||
data=address,
|
||||
description=network or "",
|
||||
extra=None,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
)
|
||||
if send_button_request:
|
||||
@ -500,6 +502,7 @@ def show_pubkey(
|
||||
br_type=br_type,
|
||||
br_code=ButtonRequestType.PublicKey,
|
||||
mismatch_title=mismatch_title,
|
||||
chunkify=False,
|
||||
)
|
||||
|
||||
|
||||
@ -577,6 +580,7 @@ async def confirm_output(
|
||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||
address_label: str | None = None,
|
||||
output_index: int | None = None,
|
||||
chunkify: bool = False,
|
||||
) -> None:
|
||||
if title is not None:
|
||||
if title.upper().startswith("CONFIRM "):
|
||||
@ -601,6 +605,7 @@ async def confirm_output(
|
||||
verb="CONTINUE",
|
||||
hold=False,
|
||||
info_button=False,
|
||||
chunkify=chunkify,
|
||||
)
|
||||
),
|
||||
"confirm_output",
|
||||
|
Loading…
Reference in New Issue
Block a user