diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 04129ece3..ee13835c7 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -514,10 +514,8 @@ if utils.BITCOIN_ONLY: import apps.monero.get_watch_only apps.monero.key_image_sync import apps.monero.key_image_sync - apps.monero.layout.common - import apps.monero.layout.common - apps.monero.layout.confirms - import apps.monero.layout.confirms + apps.monero.layout + import apps.monero.layout apps.monero.live_refresh import apps.monero.live_refresh apps.monero.misc diff --git a/core/src/apps/bitcoin/get_ownership_proof.py b/core/src/apps/bitcoin/get_ownership_proof.py index e85311d62..a10e4d5bd 100644 --- a/core/src/apps/bitcoin/get_ownership_proof.py +++ b/core/src/apps/bitcoin/get_ownership_proof.py @@ -78,6 +78,7 @@ async def get_ownership_proof( data=hexlify(msg.commitment_data).decode(), icon=ui.ICON_CONFIG, icon_color=ui.ORANGE_ICON, + truncate=True, # commitment data, probably should show all truncate_middle=True, ) diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index 7eea0e692..af5fc78e2 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -56,6 +56,7 @@ async def confirm_output( title="OP_RETURN", data=hexlify(data).decode(), br_code=ButtonRequestType.ConfirmOutput, + truncate=True, # 80 bytes - not truncated 2 screens max ) else: assert output.address is not None diff --git a/core/src/apps/common/paths.py b/core/src/apps/common/paths.py index e69d429c0..b9bd3a0da 100644 --- a/core/src/apps/common/paths.py +++ b/core/src/apps/common/paths.py @@ -336,20 +336,3 @@ def is_hardened(i: int) -> bool: def path_is_hardened(address_n: Bip32Path) -> bool: return all(is_hardened(n) for n in address_n) - - -def break_address_n_to_lines(address_n: Bip32Path) -> list[str]: - from trezor.ui.constants import MONO_CHARS_PER_LINE - from .layout import address_n_to_str - - lines = [] - path_str = address_n_to_str(address_n) - - per_line = MONO_CHARS_PER_LINE - while len(path_str) > per_line: - i = path_str[:per_line].rfind("/") - lines.append(path_str[:i]) - path_str = path_str[i:] - lines.append(path_str) - - return lines diff --git a/core/src/apps/management/reset_device/layout.py b/core/src/apps/management/reset_device/layout.py index 186c3c49c..e6547ff13 100644 --- a/core/src/apps/management/reset_device/layout.py +++ b/core/src/apps/management/reset_device/layout.py @@ -30,6 +30,7 @@ async def show_internal_entropy(ctx, entropy: bytes): icon_color=ui.ORANGE_ICON, width=16, br_code=ButtonRequestType.ResetDevice, + truncate=True, # 32 bytes always fits ) diff --git a/core/src/apps/misc/get_ecdh_session_key.py b/core/src/apps/misc/get_ecdh_session_key.py index 754371cc3..4626c9b07 100644 --- a/core/src/apps/misc/get_ecdh_session_key.py +++ b/core/src/apps/misc/get_ecdh_session_key.py @@ -53,6 +53,7 @@ async def require_confirm_ecdh_session_key( serialize_identity_without_proto(identity), icon=ui.ICON_DEFAULT, icon_color=ui.ORANGE_ICON, + truncate=True, # uri without protocol, probably should show entire ) diff --git a/core/src/apps/monero/get_address.py b/core/src/apps/monero/get_address.py index e2b3a177b..275077f9f 100644 --- a/core/src/apps/monero/get_address.py +++ b/core/src/apps/monero/get_address.py @@ -1,10 +1,10 @@ from trezor.messages import MoneroAddress +from trezor.ui.layouts import show_address from apps.common import paths from apps.common.keychain import auto_keychain -from apps.common.layout import address_n_to_str, show_qr +from apps.common.layout import address_n_to_str from apps.monero import misc -from apps.monero.layout import confirms from apps.monero.xmr import addresses, crypto, monero from apps.monero.xmr.networks import net_version @@ -42,10 +42,11 @@ async def get_address(ctx, msg, keychain): if msg.show_display: desc = address_n_to_str(msg.address_n) - while True: - if await confirms.show_address(ctx, addr.decode(), desc=desc): - break - if await show_qr(ctx, "monero:" + addr.decode(), desc=desc): - break + await show_address( + ctx, + address=addr.decode(), + address_qr="monero:" + addr.decode(), + desc=desc, + ) return MoneroAddress(address=addr) diff --git a/core/src/apps/monero/get_tx_keys.py b/core/src/apps/monero/get_tx_keys.py index 9c3911f99..c10b06028 100644 --- a/core/src/apps/monero/get_tx_keys.py +++ b/core/src/apps/monero/get_tx_keys.py @@ -20,8 +20,7 @@ from trezor.messages import MoneroGetTxKeyAck, MoneroGetTxKeyRequest from apps.common import paths from apps.common.keychain import auto_keychain -from apps.monero import misc -from apps.monero.layout import confirms +from apps.monero import layout, misc from apps.monero.xmr import crypto from apps.monero.xmr.crypto import chacha_poly @@ -34,7 +33,7 @@ async def get_tx_keys(ctx, msg: MoneroGetTxKeyRequest, keychain): await paths.validate_path(ctx, keychain, msg.address_n) do_deriv = msg.reason == _GET_TX_KEY_REASON_TX_DERIVATION - await confirms.require_confirm_tx_key(ctx, export_key=not do_deriv) + await layout.require_confirm_tx_key(ctx, export_key=not do_deriv) creds = misc.get_creds(keychain, msg.address_n, msg.network_type) diff --git a/core/src/apps/monero/get_watch_only.py b/core/src/apps/monero/get_watch_only.py index 2a9ab0a6d..e36b6cfec 100644 --- a/core/src/apps/monero/get_watch_only.py +++ b/core/src/apps/monero/get_watch_only.py @@ -2,8 +2,7 @@ from trezor.messages import MoneroGetWatchKey, MoneroWatchKey from apps.common import paths from apps.common.keychain import auto_keychain -from apps.monero import misc -from apps.monero.layout import confirms +from apps.monero import layout, misc from apps.monero.xmr import crypto @@ -11,7 +10,7 @@ from apps.monero.xmr import crypto async def get_watch_only(ctx, msg: MoneroGetWatchKey, keychain): await paths.validate_path(ctx, keychain, msg.address_n) - await confirms.require_confirm_watchkey(ctx) + await layout.require_confirm_watchkey(ctx) creds = misc.get_creds(keychain, msg.address_n, msg.network_type) address = creds.address diff --git a/core/src/apps/monero/key_image_sync.py b/core/src/apps/monero/key_image_sync.py index 9ecec2367..983e99618 100644 --- a/core/src/apps/monero/key_image_sync.py +++ b/core/src/apps/monero/key_image_sync.py @@ -12,8 +12,7 @@ from trezor.messages import ( from apps.common import paths from apps.common.keychain import auto_keychain -from apps.monero import misc -from apps.monero.layout import confirms +from apps.monero import layout, misc from apps.monero.xmr import crypto, key_image, monero from apps.monero.xmr.crypto import chacha_poly @@ -49,7 +48,7 @@ async def _init_step(s, ctx, msg, keychain): s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type) - await confirms.require_confirm_keyimage_sync(ctx) + await layout.require_confirm_keyimage_sync(ctx) s.num_outputs = msg.num s.expected_hash = msg.hash @@ -71,7 +70,7 @@ async def _sync_step(s, ctx, tds): buff = bytearray(32 * 3) buff_mv = memoryview(buff) - await confirms.keyimage_sync_step(ctx, s.current_output, s.num_outputs) + await layout.keyimage_sync_step(ctx, s.current_output, s.num_outputs) for td in tds.tdis: s.current_output += 1 diff --git a/core/src/apps/monero/layout/confirms.py b/core/src/apps/monero/layout.py similarity index 76% rename from core/src/apps/monero/layout/confirms.py rename to core/src/apps/monero/layout.py index fd9c21922..f3a80c580 100644 --- a/core/src/apps/monero/layout/confirms.py +++ b/core/src/apps/monero/layout.py @@ -1,14 +1,14 @@ from ubinascii import hexlify -from trezor import ui, wire +from trezor import strings, ui from trezor.enums import ButtonRequestType -from trezor.ui.components.tt.text import Text -from trezor.ui.layouts import confirm_action +from trezor.ui.layouts import ( + confirm_action, + confirm_hex, + confirm_metadata, + confirm_output, +) from trezor.ui.popup import Popup -from trezor.utils import chunks - -from apps.common.confirm import require_confirm, require_hold_to_confirm -from apps.monero.layout import common DUMMY_PAYMENT_ID = b"\x00\x00\x00\x00\x00\x00\x00\x00" @@ -21,6 +21,10 @@ if False: ) +def _format_amount(value): + return "%s XMR" % strings.format_amount(value, 12) + + async def require_confirm_watchkey(ctx): await confirm_action( ctx, @@ -123,43 +127,46 @@ async def _require_confirm_output( version, dst.addr.spend_public_key, dst.addr.view_public_key, payment_id ) - text_addr = common.split_address(addr.decode()) - text_amount = common.format_amount(dst.amount) - - if not await common.naive_pagination( + await confirm_output( ctx, - [ui.BOLD, text_amount, ui.MONO] + list(text_addr), - "Confirm send", - ui.ICON_SEND, - ui.GREEN, - 4, - ): - raise wire.ActionCancelled + address=addr.decode(), + amount=_format_amount(dst.amount), + font_amount=ui.BOLD, + br_code=ButtonRequestType.SignTx, + ) async def _require_confirm_payment_id(ctx, payment_id: bytes): - if not await common.naive_pagination( + await confirm_hex( ctx, - [ui.MONO] + list(chunks(hexlify(payment_id).decode(), 16)), - "Payment ID", - ui.ICON_SEND, - ui.GREEN, - ): - raise wire.ActionCancelled + "confirm_payment_id", + title="Payment ID", + data=hexlify(payment_id).decode(), + br_code=ButtonRequestType.SignTx, + ) async def _require_confirm_fee(ctx, fee): - content = Text("Confirm fee", ui.ICON_SEND, ui.GREEN) - content.bold(common.format_amount(fee)) - await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + await confirm_metadata( + ctx, + "confirm_final", + title="Confirm fee", + content="{}", + param=_format_amount(fee), + hide_continue=True, + hold=True, + ) async def _require_confirm_unlock_time(ctx, unlock_time): - content = Text("Confirm unlock time", ui.ICON_SEND, ui.GREEN) - content.normal("Unlock time for this transaction is set to") - content.bold(str(unlock_time)) - content.normal("Continue?") - await require_confirm(ctx, content, ButtonRequestType.SignTx) + await confirm_metadata( + ctx, + "confirm_locktime", + "Confirm unlock time", + "Unlock time for this transaction is set to {}", + str(unlock_time), + br_code=ButtonRequestType.SignTx, + ) class TransactionStep(ui.Component): @@ -242,28 +249,3 @@ async def live_refresh_step(ctx, current): if current is None: return await Popup(LiveRefreshStep(current)) - - -async def show_address( - ctx, address: str, desc: str = "Confirm address", network: str = None -): - from apps.common.confirm import confirm - from trezor.enums import ButtonRequestType - from trezor.ui.components.tt.button import ButtonDefault - from trezor.ui.components.tt.scroll import Paginated - - pages = [] - for lines in common.paginate_lines(common.split_address(address), 5): - text = Text(desc, ui.ICON_RECEIVE, ui.GREEN) - if network is not None: - text.normal("%s network" % network) - text.mono(*lines) - pages.append(text) - - return await confirm( - ctx, - Paginated(pages), - code=ButtonRequestType.Address, - cancel="QR", - cancel_style=ButtonDefault, - ) diff --git a/core/src/apps/monero/layout/common.py b/core/src/apps/monero/layout/common.py deleted file mode 100644 index e20bff1c8..000000000 --- a/core/src/apps/monero/layout/common.py +++ /dev/null @@ -1,70 +0,0 @@ -from trezor import strings, ui, utils -from trezor.enums import ButtonRequestType -from trezor.ui.components.tt.text import Text - -from apps.common import button_request - - -async def naive_pagination( - ctx, lines, title, icon=ui.ICON_RESET, icon_color=ui.ORANGE, per_page=5 -): - from trezor.ui.components.tt.scroll import ( - CANCELLED, - CONFIRMED, - PaginatedWithButtons, - ) - - pages = [] - page_lines = paginate_lines(lines, per_page) - - for i, lines in enumerate(page_lines): - if len(page_lines) > 1: - paging = "%s/%s" % (i + 1, len(page_lines)) - else: - paging = "" - text = Text("%s %s" % (title, paging), icon, icon_color) - text.normal(*lines) - pages.append(text) - - paginated = PaginatedWithButtons(pages, one_by_one=True) - - while True: - await button_request(ctx, code=ButtonRequestType.SignTx) - result = await ctx.wait(paginated) - if result is CONFIRMED: - return True - if result is CANCELLED: - return False - - -def paginate_lines(lines, lines_per_page=5): - """Paginates lines across pages with preserving formatting modifiers (e.g., mono)""" - pages = [] - cpage = [] - nlines = 0 - last_modifier = None - for line in lines: - cpage.append(line) - if not isinstance(line, int): - nlines += 1 - else: - last_modifier = line - - if nlines >= lines_per_page: - pages.append(cpage) - cpage = [] - nlines = 0 - if last_modifier is not None: - cpage.append(last_modifier) - - if nlines > 0: - pages.append(cpage) - return pages - - -def format_amount(value): - return "%s XMR" % strings.format_amount(value, 12) - - -def split_address(address): - return utils.chunks(address, 16) diff --git a/core/src/apps/monero/live_refresh.py b/core/src/apps/monero/live_refresh.py index a0203dd85..7af7f2403 100644 --- a/core/src/apps/monero/live_refresh.py +++ b/core/src/apps/monero/live_refresh.py @@ -13,8 +13,7 @@ from trezor.messages import ( from apps.common import paths from apps.common.keychain import auto_keychain -from apps.monero import misc -from apps.monero.layout import confirms +from apps.monero import layout, misc from apps.monero.xmr import crypto, key_image, monero from apps.monero.xmr.crypto import chacha_poly @@ -52,7 +51,7 @@ async def _init_step( await paths.validate_path(ctx, keychain, msg.address_n) if not storage.cache.get(storage.cache.APP_MONERO_LIVE_REFRESH): - await confirms.require_confirm_live_refresh(ctx) + await layout.require_confirm_live_refresh(ctx) storage.cache.set(storage.cache.APP_MONERO_LIVE_REFRESH, b"\x01") s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type) @@ -64,7 +63,7 @@ async def _refresh_step(s: LiveRefreshState, ctx, msg: MoneroLiveRefreshStepRequ buff = bytearray(32 * 3) buff_mv = memoryview(buff) - await confirms.live_refresh_step(ctx, s.current_output) + await layout.live_refresh_step(ctx, s.current_output) s.current_output += 1 if __debug__: diff --git a/core/src/apps/monero/signing/step_01_init_transaction.py b/core/src/apps/monero/signing/step_01_init_transaction.py index 4e9fe2dc5..5049cf622 100644 --- a/core/src/apps/monero/signing/step_01_init_transaction.py +++ b/core/src/apps/monero/signing/step_01_init_transaction.py @@ -4,8 +4,7 @@ Initializes a new transaction. import gc -from apps.monero import misc, signing -from apps.monero.layout import confirms +from apps.monero import layout, misc, signing from apps.monero.signing.state import State from apps.monero.xmr import crypto, monero @@ -48,7 +47,7 @@ async def init_transaction( state.progress_cur = 0 # Ask for confirmation - await confirms.require_confirm_transaction( + await layout.require_confirm_transaction( state.ctx, state, tsx_data, state.creds.network_type ) state.creds.address = None diff --git a/core/src/apps/monero/signing/step_02_set_input.py b/core/src/apps/monero/signing/step_02_set_input.py index a7bb84cd7..4b13fa718 100644 --- a/core/src/apps/monero/signing/step_02_set_input.py +++ b/core/src/apps/monero/signing/step_02_set_input.py @@ -11,7 +11,7 @@ If number of inputs is small, in-memory mode is used = alpha, pseudo_outs are ke Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under chacha_poly with key derived for exactly this purpose. """ -from apps.monero.layout import confirms +from apps.monero import layout from apps.monero.xmr import crypto, monero, serialize from .state import State @@ -32,7 +32,7 @@ async def set_input( state.current_input_index += 1 - await confirms.transaction_step(state, state.STEP_INP, state.current_input_index) + await layout.transaction_step(state, state.STEP_INP, state.current_input_index) if state.last_step > state.STEP_INP: raise ValueError("Invalid state transition") diff --git a/core/src/apps/monero/signing/step_03_inputs_permutation.py b/core/src/apps/monero/signing/step_03_inputs_permutation.py index 9974f95c4..6dd0fc78c 100644 --- a/core/src/apps/monero/signing/step_03_inputs_permutation.py +++ b/core/src/apps/monero/signing/step_03_inputs_permutation.py @@ -16,7 +16,7 @@ HMAC correctness (host sends original sort idx) and ordering check on the key images. This step is skipped. """ -from apps.monero.layout.confirms import transaction_step +from apps.monero.layout import transaction_step from .state import State diff --git a/core/src/apps/monero/signing/step_04_input_vini.py b/core/src/apps/monero/signing/step_04_input_vini.py index b049aa038..604c4d471 100644 --- a/core/src/apps/monero/signing/step_04_input_vini.py +++ b/core/src/apps/monero/signing/step_04_input_vini.py @@ -3,7 +3,7 @@ This step serves for an incremental hashing of tx.vin[i] to the tx_prefix_hasher after the sorting on tx.vin[i].ki. The sorting order was received in the previous step. """ -from apps.monero.layout import confirms +from apps.monero import layout from apps.monero.signing import offloading_keys from apps.monero.xmr import crypto @@ -25,9 +25,7 @@ async def input_vini( ) -> MoneroTransactionInputViniAck: from trezor.messages import MoneroTransactionInputViniAck - await confirms.transaction_step( - state, state.STEP_VINI, state.current_input_index + 1 - ) + await layout.transaction_step(state, state.STEP_VINI, state.current_input_index + 1) if state.last_step not in (state.STEP_INP, state.STEP_PERM, state.STEP_VINI): raise ValueError("Invalid state transition") if state.current_input_index >= state.input_count: diff --git a/core/src/apps/monero/signing/step_05_all_inputs_set.py b/core/src/apps/monero/signing/step_05_all_inputs_set.py index 71448c5bd..936b70c44 100644 --- a/core/src/apps/monero/signing/step_05_all_inputs_set.py +++ b/core/src/apps/monero/signing/step_05_all_inputs_set.py @@ -3,7 +3,7 @@ All inputs set. Defining range signature parameters. If in the applicable offloading mode, generate commitment masks. """ -from apps.monero.layout import confirms +from apps.monero import layout from apps.monero.xmr import crypto from .state import State @@ -15,7 +15,7 @@ if False: async def all_inputs_set(state: State) -> MoneroTransactionAllInputsSetAck: state.mem_trace(0) - await confirms.transaction_step(state, state.STEP_ALL_IN) + await layout.transaction_step(state, state.STEP_ALL_IN) from trezor.messages import MoneroTransactionAllInputsSetAck diff --git a/core/src/apps/monero/signing/step_06_set_output.py b/core/src/apps/monero/signing/step_06_set_output.py index f4165c543..0d9f8e7d3 100644 --- a/core/src/apps/monero/signing/step_06_set_output.py +++ b/core/src/apps/monero/signing/step_06_set_output.py @@ -6,8 +6,7 @@ import gc from trezor import utils -from apps.monero import signing -from apps.monero.layout import confirms +from apps.monero import layout, signing from apps.monero.signing import offloading_keys from apps.monero.xmr import crypto, serialize @@ -36,7 +35,7 @@ async def set_output( # Progress update only for master message (skip for offloaded BP msg) if not is_offloaded_bp: - await confirms.transaction_step( + await layout.transaction_step( state, state.STEP_OUT, state.current_output_index + 1 ) diff --git a/core/src/apps/monero/signing/step_07_all_outputs_set.py b/core/src/apps/monero/signing/step_07_all_outputs_set.py index e739f2f57..48871cdcb 100644 --- a/core/src/apps/monero/signing/step_07_all_outputs_set.py +++ b/core/src/apps/monero/signing/step_07_all_outputs_set.py @@ -8,7 +8,7 @@ import gc from trezor import utils -from apps.monero.layout import confirms +from apps.monero import layout from apps.monero.xmr import crypto from .state import State @@ -20,7 +20,7 @@ if False: async def all_outputs_set(state: State) -> MoneroTransactionAllOutSetAck: state.mem_trace(0) - await confirms.transaction_step(state, state.STEP_ALL_OUT) + await layout.transaction_step(state, state.STEP_ALL_OUT) state.mem_trace(1) _validate(state) diff --git a/core/src/apps/monero/signing/step_09_sign_input.py b/core/src/apps/monero/signing/step_09_sign_input.py index 01141e5a8..0a2d6f031 100644 --- a/core/src/apps/monero/signing/step_09_sign_input.py +++ b/core/src/apps/monero/signing/step_09_sign_input.py @@ -14,7 +14,7 @@ import gc from trezor import utils -from apps.monero.layout import confirms +from apps.monero import layout from apps.monero.xmr import crypto from .state import State @@ -48,9 +48,7 @@ async def sign_input( :param orig_idx: original index of the src_entr before sorting (HMAC check) :return: Generated signature MGs[i] """ - await confirms.transaction_step( - state, state.STEP_SIGN, state.current_input_index + 1 - ) + await layout.transaction_step(state, state.STEP_SIGN, state.current_input_index + 1) state.current_input_index += 1 if state.last_step not in (state.STEP_ALL_OUT, state.STEP_SIGN): diff --git a/core/src/trezor/ui/components/tt/scroll.py b/core/src/trezor/ui/components/tt/scroll.py index eaebd072c..b25999097 100644 --- a/core/src/trezor/ui/components/tt/scroll.py +++ b/core/src/trezor/ui/components/tt/scroll.py @@ -9,6 +9,12 @@ from .confirm import CANCELLED, CONFIRMED, Confirm from .swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe from .text import TEXT_MAX_LINES, Span, Text +if False: + from typing import Iterable, Any + + from ..common.text import TextContent + + _PAGINATED_LINE_WIDTH = const(204) WAS_PAGED = object() @@ -301,3 +307,71 @@ def paginate_text( pages[-1] = Confirm(pages[-1]) return Paginated(pages) + + +def paginate_paragraphs( + para: Iterable[tuple[int, str]], + header: str, + header_icon: str = ui.ICON_DEFAULT, + icon_color: int = ui.ORANGE_ICON, + break_words: bool = False, + confirm_kwargs: Dict[str, Any] = {}, +) -> Union[Confirm, Paginated]: + span = Span("", 0, ui.NORMAL, break_words=break_words) + lines = 0 + content: list[TextContent] = [] + for font, text in para: + span.reset(text, 0, font, break_words=break_words) + lines += span.count_lines() + + # we'll need this for multipage too + if content: + content.append("\n") + content.append(font) + content.append(text) + + if lines <= TEXT_MAX_LINES: + result = Text( + header, + header_icon=header_icon, + icon_color=icon_color, + new_lines=False, + break_words=break_words, + ) + for font, text in para: + if len(result.content) != 0: + result.content.append("\n") + result.content.append(font) + result.content.append(text) + return Confirm(result, **confirm_kwargs) + + else: + pages: list[ui.Component] = [] + lines_left = 0 + for i, (font, text) in enumerate(para): + span.reset( + text, 0, font, break_words=break_words, line_width=_PAGINATED_LINE_WIDTH + ) + + while span.has_more_content(): + span.next_line() + if lines_left <= 0: + page = Text( + header, + header_icon=header_icon, + icon_color=icon_color, + new_lines=False, + content_offset=i * 3 + 1, # font, _text_, newline + char_offset=span.start, + line_width=_PAGINATED_LINE_WIDTH, + render_page_overflow=False, + break_words=break_words, + ) + page.content = content + pages.append(page) + lines_left = TEXT_MAX_LINES - 1 + else: + lines_left -= 1 + + pages[-1] = Confirm(pages[-1], **confirm_kwargs) + return Paginated(pages) diff --git a/core/src/trezor/ui/constants/tt.py b/core/src/trezor/ui/constants/tt.py index d8cb3f273..77aa5ca7a 100644 --- a/core/src/trezor/ui/constants/tt.py +++ b/core/src/trezor/ui/constants/tt.py @@ -6,7 +6,7 @@ TEXT_LINE_HEIGHT_HALF = const(13) TEXT_MARGIN_LEFT = const(14) TEXT_MAX_LINES = const(5) -MONO_CHARS_PER_LINE = const(17) +MONO_ADDR_PER_LINE = const(17) MONO_HEX_PER_LINE = const(18) QR_X = const(120) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index f41d84909..da06b9886 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -11,10 +11,10 @@ from ..components.common import break_path_to_lines from ..components.common.confirm import is_confirmed, raise_if_cancelled from ..components.tt.button import ButtonCancel, ButtonDefault from ..components.tt.confirm import Confirm, HoldToConfirm -from ..components.tt.scroll import Paginated, paginate_text +from ..components.tt.scroll import Paginated, paginate_paragraphs, paginate_text from ..components.tt.text import Span, Text from ..constants.tt import ( - MONO_CHARS_PER_LINE, + MONO_ADDR_PER_LINE, MONO_HEX_PER_LINE, QR_SIZE_THRESHOLD, QR_X, @@ -182,7 +182,7 @@ async def confirm_backup(ctx: wire.GenericContext) -> bool: async def confirm_path_warning(ctx: wire.GenericContext, path: str) -> None: text = Text("Confirm path", ui.ICON_WRONG, ui.RED) text.normal("Path") - text.mono(*break_path_to_lines(path, MONO_CHARS_PER_LINE)) + text.mono(*break_path_to_lines(path, MONO_ADDR_PER_LINE)) text.normal("is unknown.", "Are you sure?") await raise_if_cancelled( interact( @@ -207,7 +207,7 @@ def _show_qr( def _split_address(address: str) -> Iterator[str]: - return chunks_intersperse(address, MONO_CHARS_PER_LINE) + return chunks_intersperse(address, MONO_ADDR_PER_LINE) def _truncate_hex( @@ -232,13 +232,18 @@ def _show_address( address: str, desc: str, network: str | None = None, -) -> Confirm: - text = Text(desc, ui.ICON_RECEIVE, ui.GREEN, new_lines=False) - if network is not None: - text.normal("%s network\n" % network) - text.mono(*_split_address(address)) - - return Confirm(text, cancel="QR", cancel_style=ButtonDefault) +) -> Confirm | Paginated: + para = [(ui.NORMAL, "%s network" % network)] if network is not None else [] + para.extend( + (ui.MONO, address_line) for address_line in chunks(address, MONO_ADDR_PER_LINE) + ) + return paginate_paragraphs( + para, + header=desc, + header_icon=ui.ICON_RECEIVE, + icon_color=ui.GREEN, + confirm_kwargs={"cancel": "QR", "cancel_style": ButtonDefault}, + ) def _show_xpub(xpub: str, desc: str, cancel: str) -> Paginated: @@ -332,6 +337,7 @@ def show_pubkey( data=pubkey, br_code=ButtonRequestType.PublicKey, icon=ui.ICON_RECEIVE, + truncate=True, # should fit? ) @@ -441,14 +447,22 @@ async def confirm_output( amount: str, font_amount: int = ui.NORMAL, # TODO cleanup @ redesign color_to: int = ui.FG, # TODO cleanup @ redesign + width: int = MONO_ADDR_PER_LINE, + width_paginated: int = MONO_ADDR_PER_LINE - 1, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, ) -> None: - text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN, new_lines=False) - text.content = [font_amount, amount, ui.NORMAL, color_to, " to\n", ui.FG] - text.mono(*_split_address(address)) - await raise_if_cancelled( - interact(ctx, Confirm(text), "confirm_output", br_code) - ) + title = "Confirm sending" + if len(address) > (TEXT_MAX_LINES - 1) * width: + para = [(font_amount, amount)] + para.extend((ui.MONO, line) for line in chunks(address, width_paginated)) + content: ui.Layout = paginate_paragraphs(para, title, ui.ICON_SEND, ui.GREEN) + else: + text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) + text.content = [font_amount, amount, ui.NORMAL, color_to, " to\n", ui.FG] + text.mono(*chunks_intersperse(address, width)) + content = Confirm(text) + + await raise_if_cancelled(interact(ctx, content, "confirm_output", br_code)) async def confirm_decred_sstx_submission( @@ -482,25 +496,34 @@ async def confirm_hex( font_description: int = ui.NORMAL, # TODO cleanup @ redesign color_description: int = ui.FG, # TODO cleanup @ redesign width: int = MONO_HEX_PER_LINE, + width_paginated: int = MONO_HEX_PER_LINE - 2, + truncate: bool = False, truncate_middle: bool = False, ) -> None: - text = Text(title, icon, icon_color, new_lines=False) - description_lines = 0 - if description is not None: - description_lines = Span(description, 0, font_description).count_lines() - text.content.extend( - (font_description, color_description, description, ui.FG) - ) - text.br() - text.mono( - *_truncate_hex( - data, - lines=TEXT_MAX_LINES - description_lines, - width=width, - middle=truncate_middle, + if truncate: + text = Text(title, icon, icon_color, new_lines=False) + description_lines = 0 + if description is not None: + description_lines = Span(description, 0, font_description).count_lines() + text.content.extend( + (font_description, color_description, description, ui.FG) + ) + text.br() + text.mono( + *_truncate_hex( + data, + lines=TEXT_MAX_LINES - description_lines, + width=width, + middle=truncate_middle, + ) ) - ) - content: ui.Layout = Confirm(text) + content: ui.Layout = Confirm(text) + else: + width_paginated = min(width, MONO_HEX_PER_LINE - 2) + assert color_description == ui.FG # only ethereum uses this and it truncates + para = [(font_description, description)] if description is not None else [] + para.extend((ui.MONO, line) for line in chunks(data, width_paginated)) + content = paginate_paragraphs(para, title, icon, icon_color) await raise_if_cancelled(interact(ctx, content, br_type, br_code)) @@ -554,14 +577,19 @@ async def confirm_metadata( content: str, param: str | None = None, br_code: ButtonRequestType = ButtonRequestType.SignTx, + hide_continue: bool = False, + hold: bool = False, ) -> None: text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) text.format_parametrized(content, param if param is not None else "") - text.br() - text.normal("Continue?") + if not hide_continue: + text.br() + text.normal("Continue?") + + cls = HoldToConfirm if hold else Confirm - await raise_if_cancelled(interact(ctx, Confirm(text), br_type, br_code)) + await raise_if_cancelled(interact(ctx, cls(text), br_type, br_code)) async def confirm_replacement( diff --git a/tests/device_tests/test_msg_monero_getaddress.py b/tests/device_tests/test_msg_monero_getaddress.py index f4c62f6fe..bf120d540 100644 --- a/tests/device_tests/test_msg_monero_getaddress.py +++ b/tests/device_tests/test_msg_monero_getaddress.py @@ -29,14 +29,14 @@ class TestMsgMoneroGetaddress: @pytest.mark.setup_client(mnemonic=MNEMONIC12) def test_monero_getaddress(self, client): assert ( - monero.get_address(client, parse_path("m/44h/128h/0h")) + monero.get_address(client, parse_path("m/44h/128h/0h"), show_display=True) == b"4Ahp23WfMrMFK3wYL2hLWQFGt87ZTeRkufS6JoQZu6MEFDokAQeGWmu9MA3GFq1yVLSJQbKJqVAn9F9DLYGpRzRAEXqAXKM" ) assert ( - monero.get_address(client, parse_path("m/44h/128h/1h")) + monero.get_address(client, parse_path("m/44h/128h/1h"), show_display=True) == b"44iAazhoAkv5a5RqLNVyh82a1n3ceNggmN4Ho7bUBJ14WkEVR8uFTe9f7v5rNnJ2kEbVXxfXiRzsD5Jtc6NvBi4D6WNHPie" ) assert ( - monero.get_address(client, parse_path("m/44h/128h/2h")) + monero.get_address(client, parse_path("m/44h/128h/2h"), show_display=True) == b"47ejhmbZ4wHUhXaqA4b7PN667oPMkokf4ZkNdWrMSPy9TNaLVr7vLqVUQHh2MnmaAEiyrvLsX8xUf99q3j1iAeMV8YvSFcH" )