1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 05:28:40 +00:00

refactor(core): convert most of apps.monero to layouts

Progress popups are not ported yet as they're unlike anything else.

Introduces paginate_paragraphs.
This commit is contained in:
Martin Milata 2021-03-01 13:48:59 +01:00 committed by matejcik
parent 6c926ad82e
commit 16094df0c5
25 changed files with 221 additions and 231 deletions

View File

@ -514,10 +514,8 @@ if utils.BITCOIN_ONLY:
import apps.monero.get_watch_only import apps.monero.get_watch_only
apps.monero.key_image_sync apps.monero.key_image_sync
import apps.monero.key_image_sync import apps.monero.key_image_sync
apps.monero.layout.common apps.monero.layout
import apps.monero.layout.common import apps.monero.layout
apps.monero.layout.confirms
import apps.monero.layout.confirms
apps.monero.live_refresh apps.monero.live_refresh
import apps.monero.live_refresh import apps.monero.live_refresh
apps.monero.misc apps.monero.misc

View File

@ -78,6 +78,7 @@ async def get_ownership_proof(
data=hexlify(msg.commitment_data).decode(), data=hexlify(msg.commitment_data).decode(),
icon=ui.ICON_CONFIG, icon=ui.ICON_CONFIG,
icon_color=ui.ORANGE_ICON, icon_color=ui.ORANGE_ICON,
truncate=True, # commitment data, probably should show all
truncate_middle=True, truncate_middle=True,
) )

View File

@ -56,6 +56,7 @@ async def confirm_output(
title="OP_RETURN", title="OP_RETURN",
data=hexlify(data).decode(), data=hexlify(data).decode(),
br_code=ButtonRequestType.ConfirmOutput, br_code=ButtonRequestType.ConfirmOutput,
truncate=True, # 80 bytes - not truncated 2 screens max
) )
else: else:
assert output.address is not None assert output.address is not None

View File

@ -336,20 +336,3 @@ def is_hardened(i: int) -> bool:
def path_is_hardened(address_n: Bip32Path) -> bool: def path_is_hardened(address_n: Bip32Path) -> bool:
return all(is_hardened(n) for n in address_n) 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

View File

@ -30,6 +30,7 @@ async def show_internal_entropy(ctx, entropy: bytes):
icon_color=ui.ORANGE_ICON, icon_color=ui.ORANGE_ICON,
width=16, width=16,
br_code=ButtonRequestType.ResetDevice, br_code=ButtonRequestType.ResetDevice,
truncate=True, # 32 bytes always fits
) )

View File

@ -53,6 +53,7 @@ async def require_confirm_ecdh_session_key(
serialize_identity_without_proto(identity), serialize_identity_without_proto(identity),
icon=ui.ICON_DEFAULT, icon=ui.ICON_DEFAULT,
icon_color=ui.ORANGE_ICON, icon_color=ui.ORANGE_ICON,
truncate=True, # uri without protocol, probably should show entire
) )

View File

@ -1,10 +1,10 @@
from trezor.messages import MoneroAddress from trezor.messages import MoneroAddress
from trezor.ui.layouts import show_address
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain 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 import misc
from apps.monero.layout import confirms
from apps.monero.xmr import addresses, crypto, monero from apps.monero.xmr import addresses, crypto, monero
from apps.monero.xmr.networks import net_version from apps.monero.xmr.networks import net_version
@ -42,10 +42,11 @@ async def get_address(ctx, msg, keychain):
if msg.show_display: if msg.show_display:
desc = address_n_to_str(msg.address_n) desc = address_n_to_str(msg.address_n)
while True: await show_address(
if await confirms.show_address(ctx, addr.decode(), desc=desc): ctx,
break address=addr.decode(),
if await show_qr(ctx, "monero:" + addr.decode(), desc=desc): address_qr="monero:" + addr.decode(),
break desc=desc,
)
return MoneroAddress(address=addr) return MoneroAddress(address=addr)

View File

@ -20,8 +20,7 @@ from trezor.messages import MoneroGetTxKeyAck, MoneroGetTxKeyRequest
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import misc from apps.monero import layout, misc
from apps.monero.layout import confirms
from apps.monero.xmr import crypto from apps.monero.xmr import crypto
from apps.monero.xmr.crypto import chacha_poly 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) await paths.validate_path(ctx, keychain, msg.address_n)
do_deriv = msg.reason == _GET_TX_KEY_REASON_TX_DERIVATION 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) creds = misc.get_creds(keychain, msg.address_n, msg.network_type)

View File

@ -2,8 +2,7 @@ from trezor.messages import MoneroGetWatchKey, MoneroWatchKey
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import misc from apps.monero import layout, misc
from apps.monero.layout import confirms
from apps.monero.xmr import crypto 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): async def get_watch_only(ctx, msg: MoneroGetWatchKey, keychain):
await paths.validate_path(ctx, keychain, msg.address_n) 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) creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
address = creds.address address = creds.address

View File

@ -12,8 +12,7 @@ from trezor.messages import (
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import misc from apps.monero import layout, misc
from apps.monero.layout import confirms
from apps.monero.xmr import crypto, key_image, monero from apps.monero.xmr import crypto, key_image, monero
from apps.monero.xmr.crypto import chacha_poly 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) 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.num_outputs = msg.num
s.expected_hash = msg.hash s.expected_hash = msg.hash
@ -71,7 +70,7 @@ async def _sync_step(s, ctx, tds):
buff = bytearray(32 * 3) buff = bytearray(32 * 3)
buff_mv = memoryview(buff) 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: for td in tds.tdis:
s.current_output += 1 s.current_output += 1

View File

@ -1,14 +1,14 @@
from ubinascii import hexlify from ubinascii import hexlify
from trezor import ui, wire from trezor import strings, ui
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.ui.components.tt.text import Text from trezor.ui.layouts import (
from trezor.ui.layouts import confirm_action confirm_action,
confirm_hex,
confirm_metadata,
confirm_output,
)
from trezor.ui.popup import Popup 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" 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): async def require_confirm_watchkey(ctx):
await confirm_action( await confirm_action(
ctx, ctx,
@ -123,43 +127,46 @@ async def _require_confirm_output(
version, dst.addr.spend_public_key, dst.addr.view_public_key, payment_id version, dst.addr.spend_public_key, dst.addr.view_public_key, payment_id
) )
text_addr = common.split_address(addr.decode()) await confirm_output(
text_amount = common.format_amount(dst.amount)
if not await common.naive_pagination(
ctx, ctx,
[ui.BOLD, text_amount, ui.MONO] + list(text_addr), address=addr.decode(),
"Confirm send", amount=_format_amount(dst.amount),
ui.ICON_SEND, font_amount=ui.BOLD,
ui.GREEN, br_code=ButtonRequestType.SignTx,
4, )
):
raise wire.ActionCancelled
async def _require_confirm_payment_id(ctx, payment_id: bytes): async def _require_confirm_payment_id(ctx, payment_id: bytes):
if not await common.naive_pagination( await confirm_hex(
ctx, ctx,
[ui.MONO] + list(chunks(hexlify(payment_id).decode(), 16)), "confirm_payment_id",
"Payment ID", title="Payment ID",
ui.ICON_SEND, data=hexlify(payment_id).decode(),
ui.GREEN, br_code=ButtonRequestType.SignTx,
): )
raise wire.ActionCancelled
async def _require_confirm_fee(ctx, fee): async def _require_confirm_fee(ctx, fee):
content = Text("Confirm fee", ui.ICON_SEND, ui.GREEN) await confirm_metadata(
content.bold(common.format_amount(fee)) ctx,
await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput) "confirm_final",
title="Confirm fee",
content="{}",
param=_format_amount(fee),
hide_continue=True,
hold=True,
)
async def _require_confirm_unlock_time(ctx, unlock_time): async def _require_confirm_unlock_time(ctx, unlock_time):
content = Text("Confirm unlock time", ui.ICON_SEND, ui.GREEN) await confirm_metadata(
content.normal("Unlock time for this transaction is set to") ctx,
content.bold(str(unlock_time)) "confirm_locktime",
content.normal("Continue?") "Confirm unlock time",
await require_confirm(ctx, content, ButtonRequestType.SignTx) "Unlock time for this transaction is set to {}",
str(unlock_time),
br_code=ButtonRequestType.SignTx,
)
class TransactionStep(ui.Component): class TransactionStep(ui.Component):
@ -242,28 +249,3 @@ async def live_refresh_step(ctx, current):
if current is None: if current is None:
return return
await Popup(LiveRefreshStep(current)) 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,
)

View File

@ -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)

View File

@ -13,8 +13,7 @@ from trezor.messages import (
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import misc from apps.monero import layout, misc
from apps.monero.layout import confirms
from apps.monero.xmr import crypto, key_image, monero from apps.monero.xmr import crypto, key_image, monero
from apps.monero.xmr.crypto import chacha_poly 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) await paths.validate_path(ctx, keychain, msg.address_n)
if not storage.cache.get(storage.cache.APP_MONERO_LIVE_REFRESH): 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") storage.cache.set(storage.cache.APP_MONERO_LIVE_REFRESH, b"\x01")
s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type) 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 = bytearray(32 * 3)
buff_mv = memoryview(buff) 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 s.current_output += 1
if __debug__: if __debug__:

View File

@ -4,8 +4,7 @@ Initializes a new transaction.
import gc import gc
from apps.monero import misc, signing from apps.monero import layout, misc, signing
from apps.monero.layout import confirms
from apps.monero.signing.state import State from apps.monero.signing.state import State
from apps.monero.xmr import crypto, monero from apps.monero.xmr import crypto, monero
@ -48,7 +47,7 @@ async def init_transaction(
state.progress_cur = 0 state.progress_cur = 0
# Ask for confirmation # Ask for confirmation
await confirms.require_confirm_transaction( await layout.require_confirm_transaction(
state.ctx, state, tsx_data, state.creds.network_type state.ctx, state, tsx_data, state.creds.network_type
) )
state.creds.address = None state.creds.address = None

View File

@ -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 Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under chacha_poly with
key derived for exactly this purpose. 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 apps.monero.xmr import crypto, monero, serialize
from .state import State from .state import State
@ -32,7 +32,7 @@ async def set_input(
state.current_input_index += 1 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: if state.last_step > state.STEP_INP:
raise ValueError("Invalid state transition") raise ValueError("Invalid state transition")

View File

@ -16,7 +16,7 @@ HMAC correctness (host sends original sort idx) and ordering check
on the key images. This step is skipped. 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 from .state import State

View File

@ -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. 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.signing import offloading_keys
from apps.monero.xmr import crypto from apps.monero.xmr import crypto
@ -25,9 +25,7 @@ async def input_vini(
) -> MoneroTransactionInputViniAck: ) -> MoneroTransactionInputViniAck:
from trezor.messages import MoneroTransactionInputViniAck from trezor.messages import MoneroTransactionInputViniAck
await confirms.transaction_step( await layout.transaction_step(state, state.STEP_VINI, state.current_input_index + 1)
state, state.STEP_VINI, state.current_input_index + 1
)
if state.last_step not in (state.STEP_INP, state.STEP_PERM, state.STEP_VINI): if state.last_step not in (state.STEP_INP, state.STEP_PERM, state.STEP_VINI):
raise ValueError("Invalid state transition") raise ValueError("Invalid state transition")
if state.current_input_index >= state.input_count: if state.current_input_index >= state.input_count:

View File

@ -3,7 +3,7 @@ All inputs set. Defining range signature parameters.
If in the applicable offloading mode, generate commitment masks. 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 apps.monero.xmr import crypto
from .state import State from .state import State
@ -15,7 +15,7 @@ if False:
async def all_inputs_set(state: State) -> MoneroTransactionAllInputsSetAck: async def all_inputs_set(state: State) -> MoneroTransactionAllInputsSetAck:
state.mem_trace(0) 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 from trezor.messages import MoneroTransactionAllInputsSetAck

View File

@ -6,8 +6,7 @@ import gc
from trezor import utils from trezor import utils
from apps.monero import signing from apps.monero import layout, signing
from apps.monero.layout import confirms
from apps.monero.signing import offloading_keys from apps.monero.signing import offloading_keys
from apps.monero.xmr import crypto, serialize 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) # Progress update only for master message (skip for offloaded BP msg)
if not is_offloaded_bp: if not is_offloaded_bp:
await confirms.transaction_step( await layout.transaction_step(
state, state.STEP_OUT, state.current_output_index + 1 state, state.STEP_OUT, state.current_output_index + 1
) )

View File

@ -8,7 +8,7 @@ import gc
from trezor import utils from trezor import utils
from apps.monero.layout import confirms from apps.monero import layout
from apps.monero.xmr import crypto from apps.monero.xmr import crypto
from .state import State from .state import State
@ -20,7 +20,7 @@ if False:
async def all_outputs_set(state: State) -> MoneroTransactionAllOutSetAck: async def all_outputs_set(state: State) -> MoneroTransactionAllOutSetAck:
state.mem_trace(0) 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) state.mem_trace(1)
_validate(state) _validate(state)

View File

@ -14,7 +14,7 @@ import gc
from trezor import utils from trezor import utils
from apps.monero.layout import confirms from apps.monero import layout
from apps.monero.xmr import crypto from apps.monero.xmr import crypto
from .state import State 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) :param orig_idx: original index of the src_entr before sorting (HMAC check)
:return: Generated signature MGs[i] :return: Generated signature MGs[i]
""" """
await confirms.transaction_step( await layout.transaction_step(state, state.STEP_SIGN, state.current_input_index + 1)
state, state.STEP_SIGN, state.current_input_index + 1
)
state.current_input_index += 1 state.current_input_index += 1
if state.last_step not in (state.STEP_ALL_OUT, state.STEP_SIGN): if state.last_step not in (state.STEP_ALL_OUT, state.STEP_SIGN):

View File

@ -9,6 +9,12 @@ from .confirm import CANCELLED, CONFIRMED, Confirm
from .swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe from .swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe
from .text import TEXT_MAX_LINES, Span, Text 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) _PAGINATED_LINE_WIDTH = const(204)
WAS_PAGED = object() WAS_PAGED = object()
@ -301,3 +307,71 @@ def paginate_text(
pages[-1] = Confirm(pages[-1]) pages[-1] = Confirm(pages[-1])
return Paginated(pages) 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)

View File

@ -6,7 +6,7 @@ TEXT_LINE_HEIGHT_HALF = const(13)
TEXT_MARGIN_LEFT = const(14) TEXT_MARGIN_LEFT = const(14)
TEXT_MAX_LINES = const(5) TEXT_MAX_LINES = const(5)
MONO_CHARS_PER_LINE = const(17) MONO_ADDR_PER_LINE = const(17)
MONO_HEX_PER_LINE = const(18) MONO_HEX_PER_LINE = const(18)
QR_X = const(120) QR_X = const(120)

View File

@ -11,10 +11,10 @@ from ..components.common import break_path_to_lines
from ..components.common.confirm import is_confirmed, raise_if_cancelled from ..components.common.confirm import is_confirmed, raise_if_cancelled
from ..components.tt.button import ButtonCancel, ButtonDefault from ..components.tt.button import ButtonCancel, ButtonDefault
from ..components.tt.confirm import Confirm, HoldToConfirm 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 ..components.tt.text import Span, Text
from ..constants.tt import ( from ..constants.tt import (
MONO_CHARS_PER_LINE, MONO_ADDR_PER_LINE,
MONO_HEX_PER_LINE, MONO_HEX_PER_LINE,
QR_SIZE_THRESHOLD, QR_SIZE_THRESHOLD,
QR_X, 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: async def confirm_path_warning(ctx: wire.GenericContext, path: str) -> None:
text = Text("Confirm path", ui.ICON_WRONG, ui.RED) text = Text("Confirm path", ui.ICON_WRONG, ui.RED)
text.normal("Path") 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?") text.normal("is unknown.", "Are you sure?")
await raise_if_cancelled( await raise_if_cancelled(
interact( interact(
@ -207,7 +207,7 @@ def _show_qr(
def _split_address(address: str) -> Iterator[str]: 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( def _truncate_hex(
@ -232,13 +232,18 @@ def _show_address(
address: str, address: str,
desc: str, desc: str,
network: str | None = None, network: str | None = None,
) -> Confirm: ) -> Confirm | Paginated:
text = Text(desc, ui.ICON_RECEIVE, ui.GREEN, new_lines=False) para = [(ui.NORMAL, "%s network" % network)] if network is not None else []
if network is not None: para.extend(
text.normal("%s network\n" % network) (ui.MONO, address_line) for address_line in chunks(address, MONO_ADDR_PER_LINE)
text.mono(*_split_address(address)) )
return paginate_paragraphs(
return Confirm(text, cancel="QR", cancel_style=ButtonDefault) 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: def _show_xpub(xpub: str, desc: str, cancel: str) -> Paginated:
@ -332,6 +337,7 @@ def show_pubkey(
data=pubkey, data=pubkey,
br_code=ButtonRequestType.PublicKey, br_code=ButtonRequestType.PublicKey,
icon=ui.ICON_RECEIVE, icon=ui.ICON_RECEIVE,
truncate=True, # should fit?
) )
@ -441,14 +447,22 @@ async def confirm_output(
amount: str, amount: str,
font_amount: int = ui.NORMAL, # TODO cleanup @ redesign font_amount: int = ui.NORMAL, # TODO cleanup @ redesign
color_to: int = ui.FG, # 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, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
) -> None: ) -> None:
text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN, new_lines=False) title = "Confirm sending"
text.content = [font_amount, amount, ui.NORMAL, color_to, " to\n", ui.FG] if len(address) > (TEXT_MAX_LINES - 1) * width:
text.mono(*_split_address(address)) para = [(font_amount, amount)]
await raise_if_cancelled( para.extend((ui.MONO, line) for line in chunks(address, width_paginated))
interact(ctx, Confirm(text), "confirm_output", br_code) 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( async def confirm_decred_sstx_submission(
@ -482,25 +496,34 @@ async def confirm_hex(
font_description: int = ui.NORMAL, # TODO cleanup @ redesign font_description: int = ui.NORMAL, # TODO cleanup @ redesign
color_description: int = ui.FG, # TODO cleanup @ redesign color_description: int = ui.FG, # TODO cleanup @ redesign
width: int = MONO_HEX_PER_LINE, width: int = MONO_HEX_PER_LINE,
width_paginated: int = MONO_HEX_PER_LINE - 2,
truncate: bool = False,
truncate_middle: bool = False, truncate_middle: bool = False,
) -> None: ) -> None:
text = Text(title, icon, icon_color, new_lines=False) if truncate:
description_lines = 0 text = Text(title, icon, icon_color, new_lines=False)
if description is not None: description_lines = 0
description_lines = Span(description, 0, font_description).count_lines() if description is not None:
text.content.extend( description_lines = Span(description, 0, font_description).count_lines()
(font_description, color_description, description, ui.FG) 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,
)
) )
text.br() content: ui.Layout = Confirm(text)
text.mono( else:
*_truncate_hex( width_paginated = min(width, MONO_HEX_PER_LINE - 2)
data, assert color_description == ui.FG # only ethereum uses this and it truncates
lines=TEXT_MAX_LINES - description_lines, para = [(font_description, description)] if description is not None else []
width=width, para.extend((ui.MONO, line) for line in chunks(data, width_paginated))
middle=truncate_middle, content = paginate_paragraphs(para, title, icon, icon_color)
)
)
content: ui.Layout = Confirm(text)
await raise_if_cancelled(interact(ctx, content, br_type, br_code)) await raise_if_cancelled(interact(ctx, content, br_type, br_code))
@ -554,14 +577,19 @@ async def confirm_metadata(
content: str, content: str,
param: str | None = None, param: str | None = None,
br_code: ButtonRequestType = ButtonRequestType.SignTx, br_code: ButtonRequestType = ButtonRequestType.SignTx,
hide_continue: bool = False,
hold: bool = False,
) -> None: ) -> None:
text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False)
text.format_parametrized(content, param if param is not None else "") 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?")
await raise_if_cancelled(interact(ctx, Confirm(text), br_type, br_code)) cls = HoldToConfirm if hold else Confirm
await raise_if_cancelled(interact(ctx, cls(text), br_type, br_code))
async def confirm_replacement( async def confirm_replacement(

View File

@ -29,14 +29,14 @@ class TestMsgMoneroGetaddress:
@pytest.mark.setup_client(mnemonic=MNEMONIC12) @pytest.mark.setup_client(mnemonic=MNEMONIC12)
def test_monero_getaddress(self, client): def test_monero_getaddress(self, client):
assert ( 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" == b"4Ahp23WfMrMFK3wYL2hLWQFGt87ZTeRkufS6JoQZu6MEFDokAQeGWmu9MA3GFq1yVLSJQbKJqVAn9F9DLYGpRzRAEXqAXKM"
) )
assert ( 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" == b"44iAazhoAkv5a5RqLNVyh82a1n3ceNggmN4Ho7bUBJ14WkEVR8uFTe9f7v5rNnJ2kEbVXxfXiRzsD5Jtc6NvBi4D6WNHPie"
) )
assert ( 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" == b"47ejhmbZ4wHUhXaqA4b7PN667oPMkokf4ZkNdWrMSPy9TNaLVr7vLqVUQHh2MnmaAEiyrvLsX8xUf99q3j1iAeMV8YvSFcH"
) )