mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 06:48:16 +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:
parent
6c926ad82e
commit
16094df0c5
@ -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
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
)
|
@ -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)
|
@ -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__:
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
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,
|
||||
)
|
||||
)
|
||||
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?")
|
||||
|
||||
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(
|
||||
|
@ -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"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user