1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-31 19:08:28 +00:00

WIP - design of Send flow

This commit is contained in:
grdddj 2023-01-01 17:35:18 +01:00
parent 1349c5c5ee
commit f812230eed
22 changed files with 197 additions and 129 deletions

View File

@ -130,5 +130,6 @@ static void _librust_qstrs(void) {
MP_QSTR_fee_rate_amount;
MP_QSTR_total_label;
MP_QSTR_fee_label;
MP_QSTR_truncated_address;
MP_QSTR_address_title;
MP_QSTR_amount_title;
}

View File

@ -20,7 +20,7 @@ use super::ffi;
/// The `off` field represents offset from the `ptr` and allows us to do
/// substring slices while keeping the head pointer as required by GC.
#[repr(C)]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct StrBuffer {
ptr: *const u8,
len: u16,

View File

@ -106,7 +106,7 @@ impl Font {
#[cfg(feature = "model_tr")]
impl ButtonDetails {
pub fn print(&self) {
let text: String<20> = if let Some(text) = self.text.clone() {
let text: String<20> = if let Some(text) = self.text {
text.as_ref().into()
} else {
"None".into()

View File

@ -644,7 +644,7 @@ impl crate::trace::Trace for ButtonDetails {
/// What happens when a button is triggered.
/// Theoretically any action can be connected
/// with any button.
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq, Copy)]
pub enum ButtonAction {
/// Go to the next page of this flow
NextPage,
@ -699,7 +699,7 @@ impl ButtonAction {
}
/// Storing actions for all three possible buttons.
#[derive(Clone)]
#[derive(Clone, Copy)]
pub struct ButtonActions {
pub left: Option<ButtonAction>,
pub middle: Option<ButtonAction>,
@ -799,9 +799,9 @@ impl ButtonActions {
/// Having access to appropriate action based on the `ButtonPos`
pub fn get_action(&self, pos: ButtonPos) -> Option<ButtonAction> {
match pos {
ButtonPos::Left => self.left.clone(),
ButtonPos::Middle => self.middle.clone(),
ButtonPos::Right => self.right.clone(),
ButtonPos::Left => self.left,
ButtonPos::Middle => self.middle,
ButtonPos::Right => self.right,
}
}
}

View File

@ -68,7 +68,7 @@ impl ButtonType {
/// Create `Button` component from `btn_details`.
fn get_button(pos: ButtonPos, btn_details: ButtonDetails) -> Button {
// Deciding between text and icon
if let Some(text) = btn_details.clone().text {
if let Some(text) = btn_details.text {
Button::with_text(pos, text, btn_details.style())
} else if let Some(icon) = btn_details.icon {
Button::with_icon(pos, icon, btn_details.style())

View File

@ -1,14 +1,14 @@
use crate::{
micropython::buffer::StrBuffer,
ui::{
component::{Child, Component, Event, EventCtx, Pad},
component::{Child, Component, ComponentExt, Event, EventCtx, Pad},
geometry::Rect,
},
};
use super::{
common, theme, ButtonAction, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos,
FlowPages, Page,
FlowPages, Page, ScrollBar,
};
/// To be returned directly from Flow.
@ -18,9 +18,13 @@ pub enum FlowMsg {
}
pub struct Flow<F, const M: usize> {
/// Function to get pages from
pages: FlowPages<F, M>,
/// Instance of the current Page
current_page: Page<M>,
/// Title being shown at the top in bold
common_title: Option<StrBuffer>,
scrollbar: Child<ScrollBar>,
content_area: Rect,
title_area: Rect,
pad: Pad,
@ -40,6 +44,7 @@ where
common_title: None,
content_area: Rect::zero(),
title_area: Rect::zero(),
scrollbar: Child::new(ScrollBar::to_be_filled_later()),
pad: Pad::with_background(theme::BG),
// Setting empty layout for now, we do not yet know how many sub-pages the first page
// has. Initial button layout will be set in `place()` after we can call
@ -56,13 +61,30 @@ where
self
}
/// Getting new current page according to page counter.
/// Also updating the possible title and moving the scrollbar to correct
/// position.
fn change_current_page(&mut self) {
self.current_page = self.pages.get(self.page_counter);
if self.common_title.is_some() && let Some(title) = self.current_page.title() {
self.common_title = Some(title);
}
let scrollbar_active_index = self
.pages
.scrollbar_page_index(self.content_area, self.page_counter);
self.scrollbar
.inner_mut()
.set_active_page(scrollbar_active_index);
}
/// Placing current page, setting current buttons and clearing.
fn update(&mut self, ctx: &mut EventCtx, get_new_page: bool) {
if get_new_page {
self.current_page = self.pages.get(self.page_counter);
self.change_current_page();
}
self.current_page.place(self.content_area);
self.set_buttons(ctx);
self.scrollbar.request_complete_repaint(ctx);
self.clear_and_repaint(ctx);
}
@ -117,10 +139,12 @@ where
fn event_consumed_by_current_choice(&mut self, ctx: &mut EventCtx, pos: ButtonPos) -> bool {
if matches!(pos, ButtonPos::Left) && self.current_page.has_prev_page() {
self.current_page.go_to_prev_page();
self.scrollbar.inner_mut().go_to_previous_page();
self.update(ctx, false);
true
} else if matches!(pos, ButtonPos::Right) && self.current_page.has_next_page() {
self.current_page.go_to_next_page();
self.scrollbar.inner_mut().go_to_next_page();
self.update(ctx, false);
true
} else {
@ -146,6 +170,16 @@ where
self.title_area = title_area;
self.content_area = content_area;
// Placing a scrollbar in case the title is there
if self.common_title.is_some() {
// Finding out the total amount of pages in this flow
let complete_page_count = self.pages.scrollbar_page_count(content_area);
self.scrollbar
.inner_mut()
.set_page_count(complete_page_count);
self.scrollbar.place(title_area);
}
// We finally found how long is the first page, and can set its button layout.
self.current_page.place(content_area);
self.buttons = Child::new(ButtonController::new(self.current_page.btn_layout()));
@ -198,10 +232,10 @@ where
}
fn paint(&mut self) {
// TODO: might put horizontal scrollbar at the top right
// (not compatible with longer/centered titles)
self.pad.paint();
// Scrollbars are painted only with a title
if let Some(title) = &self.common_title {
self.scrollbar.paint();
common::paint_header_left(title, self.title_area);
}
self.current_page.paint();

View File

@ -45,13 +45,33 @@ where
}
}
/// Returns a page on demand on a specified index.
pub fn get(&self, page_index: u8) -> Page<M> {
(self.get_page)(page_index)
}
/// Total amount of pages.
pub fn count(&self) -> u8 {
self.page_count
}
/// How many scrollable pages are there in the flow
/// (each page can have arbitrary number of "sub-pages").
pub fn scrollbar_page_count(&self, bounds: Rect) -> usize {
self.scrollbar_page_index(bounds, self.page_count)
}
/// Active scrollbar position connected with the beginning of a specific
/// page index
pub fn scrollbar_page_index(&self, bounds: Rect, page_index: u8) -> usize {
let mut page_count = 0;
for i in 0..page_index {
let mut current_page = self.get(i);
current_page.place(bounds);
page_count += current_page.page_count;
}
page_count
}
}
#[derive(Clone)]
@ -63,6 +83,7 @@ pub struct Page<const M: usize> {
current_page: usize,
page_count: usize,
char_offset: usize,
title: Option<StrBuffer>,
}
// For `layout.rs`
@ -88,17 +109,30 @@ impl<const M: usize> Page<M> {
current_page: 0,
page_count: 1,
char_offset: 0,
title: None,
}
}
}
// For `flow.rs`
impl<const M: usize> Page<M> {
/// Adding title.
pub fn with_title(mut self, title: StrBuffer) -> Self {
self.title = Some(title);
self
}
pub fn paint(&mut self) {
self.change_page(self.current_page);
self.layout_content(&mut TextRenderer);
}
pub fn place(&mut self, bounds: Rect) -> Rect {
self.text_layout.bounds = bounds;
self.page_count = self.page_count();
bounds
}
pub fn btn_layout(&self) -> ButtonLayout {
// When we are in pagination inside this flow,
// show the up and down arrows on appropriate sides.
@ -123,14 +157,12 @@ impl<const M: usize> Page<M> {
ButtonLayout::new(btn_left, current.btn_middle, btn_right)
}
pub fn place(&mut self, bounds: Rect) -> Rect {
self.text_layout.bounds = bounds;
self.page_count = self.page_count();
bounds
pub fn btn_actions(&self) -> ButtonActions {
self.btn_actions
}
pub fn btn_actions(&self) -> ButtonActions {
self.btn_actions.clone()
pub fn title(&self) -> Option<StrBuffer> {
self.title
}
pub fn has_prev_page(&self) -> bool {
@ -194,17 +226,6 @@ impl<const M: usize> Page<M> {
// For `layout.rs` - aggregating operations
impl<const M: usize> Page<M> {
pub fn icon_label_text(self, icon: IconAndName, label: StrBuffer, text: StrBuffer) -> Self {
self.icon_with_offset(icon, 3)
.text_mono(label)
.newline()
.text_bold(text)
}
pub fn icon_with_offset(self, icon: IconAndName, x_offset: i16) -> Self {
self.icon(icon).offset(Offset::x(x_offset))
}
pub fn text_normal(self, text: StrBuffer) -> Self {
self.font(Font::NORMAL).text(text)
}

View File

@ -22,7 +22,7 @@ pub struct ToDisplay {
impl ToDisplay {
pub fn new(text: StrBuffer) -> Self {
Self {
text: text.clone(),
text,
length_from_end: text.len(),
}
}

View File

@ -39,7 +39,7 @@ impl HoldToConfirm {
/// Updating the text of the component and re-placing it.
pub fn set_text(&mut self, text: StrBuffer, button_area: Rect) {
self.text_width = self.loader.get_text_width(text.clone()) as i16;
self.text_width = self.loader.get_text_width(text) as i16;
self.loader.set_text(text);
self.place(button_area);
}

View File

@ -1,7 +1,7 @@
use crate::ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate},
display::Color,
geometry::{Insets, Offset, Rect},
geometry::{Insets, Rect},
};
use super::{
@ -159,21 +159,8 @@ where
// Put it into its dedicated area when parent component already chose it,
// otherwise place it into the right top of the content.
if self.show_scrollbar {
let max_scrollbar_area = if let Some(scrollbar_area) = self.parent_scrollbar_area {
scrollbar_area
} else {
content_area
};
// Occupying as little space as possible (according to the number of pages),
// aligning to the right.
let min_scrollbar_area = Rect::from_top_right_and_size(
max_scrollbar_area.top_right(),
Offset::new(
self.scrollbar.inner().overall_width(),
ScrollBar::MAX_DOT_SIZE,
),
);
self.scrollbar.place(min_scrollbar_area);
let scrollbar_area = self.parent_scrollbar_area.unwrap_or(content_area);
self.scrollbar.place(scrollbar_area);
}
self.buttons.place(button_area);

View File

@ -17,10 +17,10 @@ pub struct ScrollBar {
/// Carrying the appearance of the scrollbar dot.
#[derive(Debug)]
enum DotType {
BigFull,
Big,
Middle,
Small,
BigFull, // *
Big, // O
Middle, // o
Small, // .
}
/// How many dots at most will there be
@ -132,6 +132,9 @@ impl ScrollBar {
match self.page_count {
0..=3 => {
// *OO
// O*O
// OO*
for i in 0..self.page_count {
if i == self.active_page {
unwrap!(dots.push(DotType::BigFull));
@ -141,6 +144,10 @@ impl ScrollBar {
}
}
4 => {
// *OOo
// O*Oo
// oO*O
// oOO*
match self.active_page {
0 => unwrap!(dots.push(DotType::BigFull)),
1 => unwrap!(dots.push(DotType::Big)),
@ -161,6 +168,13 @@ impl ScrollBar {
};
}
_ => {
// *OOo.
// O*Oo.
// oO*Oo
// ...
// oO*Oo
// .oO*O
// .oOO*
let full_dot_index = match self.active_page {
0 => 0,
1 => 1,
@ -214,8 +228,14 @@ impl Component for ScrollBar {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.pad.place(bounds);
bounds
// Occupying as little space as possible (according to the number of pages),
// aligning to the right.
let scrollbar_area = Rect::from_top_right_and_size(
bounds.top_right(),
Offset::new(self.overall_width(), Self::MAX_DOT_SIZE),
);
self.pad.place(scrollbar_area);
scrollbar_area
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {

View File

@ -100,7 +100,7 @@ impl<const N: usize> ShareWords<N> {
for i in 0..WORDS_PER_PAGE {
y_offset += NUMBER_FONT.line_height() + EXTRA_LINE_HEIGHT;
let index = self.word_index() + i;
let word = self.share_words[index].clone();
let word = self.share_words[index];
let baseline = self.area.top_left() + Offset::new(NUMBER_X_OFFSET, y_offset);
display(baseline, &inttostr!(index as u8 + 1), NUMBER_FONT);
display(baseline + Offset::x(NUMBER_WORD_OFFSET), &word, WORD_FONT);
@ -154,7 +154,7 @@ impl<const N: usize> crate::trace::Trace for ShareWords<N> {
} else {
for i in 0..WORDS_PER_PAGE {
let index = self.word_index() + i;
let word = self.share_words[index].clone();
let word = self.share_words[index];
let content = build_string!(20, inttostr!(index as u8 + 1), " ", &word, "\n");
t.string(&content);
}

View File

@ -285,51 +285,44 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
let truncated_address: StrBuffer =
kwargs.get(Qstr::MP_QSTR_truncated_address)?.try_into()?;
let amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?;
let title: StrBuffer = "SEND".into();
let address_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_title)?.try_into()?;
let amount_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_title)?.try_into()?;
let get_page = move |page_index| {
// Showing two screens - the recipient address and summary confirmation
match page_index {
0 => {
// `icon + label + address`
// RECIPIENT + address
let btn_layout = ButtonLayout::new(
Some(ButtonDetails::cancel_icon()),
None,
Some(ButtonDetails::text("CONTINUE".into())),
Some(ButtonDetails::text("CONFIRM".into())),
);
let btn_actions = ButtonActions::cancel_next();
Page::<20>::new(btn_layout, btn_actions, Font::MONO).icon_label_text(
theme::ICON_USER,
"Recipient".into(),
address.clone(),
)
Page::<10>::new(btn_layout, btn_actions, Font::MONO)
.with_title(address_title)
.text_mono(address)
}
1 => {
// 2 pairs `icon + label + text`
// AMOUNT + amount
let btn_layout = ButtonLayout::new(
Some(ButtonDetails::cancel_icon()),
Some(ButtonDetails::up_arrow_icon()),
None,
Some(ButtonDetails::text("HOLD TO CONFIRM".into()).with_default_duration()),
Some(ButtonDetails::text("CONFIRM".into())),
);
let btn_actions = ButtonActions::cancel_confirm();
Page::<20>::new(btn_layout, btn_actions, Font::MONO)
.icon_label_text(
theme::ICON_USER,
"Recipient".into(),
truncated_address.clone(),
)
Page::<10>::new(btn_layout, btn_actions, Font::MONO)
.with_title(amount_title)
.newline()
.icon_label_text(theme::ICON_AMOUNT, "Amount".into(), amount.clone())
.text_mono(amount)
}
_ => unreachable!(),
}
};
let pages = FlowPages::new(get_page, 2);
let obj = LayoutObj::new(Flow::new(pages).with_common_title(title))?;
let obj = LayoutObj::new(Flow::new(pages).with_common_title(address_title))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -337,7 +330,6 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let total_amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_amount)?.try_into()?;
let fee_amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_fee_amount)?.try_into()?;
let fee_rate_amount: Option<StrBuffer> = kwargs
@ -347,33 +339,35 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
let fee_label: StrBuffer = kwargs.get(Qstr::MP_QSTR_fee_label)?.try_into()?;
let get_page = move |page_index| {
// One page with 2 or 3 pairs `icon + label + text`
// Total amount + fee
assert!(page_index == 0);
let btn_layout = ButtonLayout::new(
Some(ButtonDetails::cancel_icon()),
None,
Some(ButtonDetails::text("HOLD TO SEND".into()).with_default_duration()),
Some(ButtonDetails::text("HOLD TO CONFIRM".into()).with_default_duration()),
);
let btn_actions = ButtonActions::cancel_confirm();
let mut flow_page = Page::<25>::new(btn_layout, btn_actions, Font::MONO)
.icon_label_text(theme::ICON_PARAM, total_label.clone(), total_amount.clone())
let mut flow_page = Page::<15>::new(btn_layout, btn_actions, Font::MONO)
.text_bold(total_label)
.newline()
.icon_label_text(theme::ICON_PARAM, fee_label.clone(), fee_amount.clone());
.text_mono(total_amount)
.newline()
.text_bold(fee_label)
.newline()
.text_mono(fee_amount);
if let Some(fee_rate_amount) = &fee_rate_amount {
flow_page = flow_page.newline().icon_label_text(
theme::ICON_PARAM,
"Fee rate".into(),
fee_rate_amount.clone(),
)
// Fee rate amount might not be there
if let Some(fee_rate_amount) = fee_rate_amount {
flow_page = flow_page.newline().text_mono(fee_rate_amount)
}
flow_page
};
let pages = FlowPages::new(get_page, 1);
let obj = LayoutObj::new(Flow::new(pages).with_common_title(title))?;
let obj = LayoutObj::new(Flow::new(pages))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -748,15 +742,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def confirm_output_r(
/// *,
/// address: str,
/// truncated_address: str,
/// amount: str,
/// address_title: str,
/// amount_title: str,
/// ) -> object:
/// """Confirm output. Specific for model R."""
Qstr::MP_QSTR_confirm_output_r => obj_fn_kw!(0, new_confirm_output).as_obj(),
/// def confirm_total_r(
/// *,
/// title: str,
/// total_amount: str,
/// fee_amount: str,
/// fee_rate_amount: str | None = None,

View File

@ -40,8 +40,9 @@ def confirm_properties(
def confirm_output_r(
*,
address: str,
truncated_address: str,
amount: str,
address_title: str,
amount_title: str,
) -> object:
"""Confirm output. Specific for model R."""
@ -49,7 +50,6 @@ def confirm_output_r(
# rust/src/ui/model_tr/layout.rs
def confirm_total_r(
*,
title: str,
total_amount: str,
fee_amount: str,
fee_rate_amount: str | None = None,

View File

@ -115,6 +115,7 @@ class Approver:
self,
txo: TxOutput,
script_pubkey: bytes,
index: int | None,
orig_txo: TxOutput | None = None,
) -> None:
self._add_output(txo, script_pubkey)
@ -168,11 +169,12 @@ class BasicApprover(Approver):
self,
txo: TxOutput,
script_pubkey: bytes,
index: int | None,
orig_txo: TxOutput | None = None,
) -> None:
from trezor.enums import OutputScriptType
await super().add_external_output(txo, script_pubkey, orig_txo)
await super().add_external_output(txo, script_pubkey, index, orig_txo)
if orig_txo:
if txo.amount < orig_txo.amount:
@ -207,7 +209,7 @@ class BasicApprover(Approver):
elif txo.payment_req_index is None or self.show_payment_req_details:
# Ask user to confirm output, unless it is part of a payment
# request, which gets confirmed separately.
await helpers.confirm_output(txo, self.coin, self.amount_unit)
await helpers.confirm_output(txo, self.coin, self.amount_unit, index)
async def add_payment_request(
self, msg: TxAckPaymentRequest, keychain: Keychain

View File

@ -215,7 +215,7 @@ class Bitcoin:
orig_txo: TxOutput | None = None
if txo.orig_hash:
orig_txo = await self.get_original_output(txo, script_pubkey)
await self.approve_output(txo, script_pubkey, orig_txo)
await self.approve_output(txo, script_pubkey, orig_txo, i)
# Finalize original outputs.
for orig in self.orig_txs:
@ -499,6 +499,7 @@ class Bitcoin:
txo: TxOutput,
script_pubkey: bytes,
orig_txo: TxOutput | None,
index: int | None,
) -> None:
payment_req_index = txo.payment_req_index # local_cache_attribute
approver = self.approver # local_cache_attribute
@ -517,7 +518,7 @@ class Bitcoin:
# Output is change and does not need approval.
approver.add_change_output(txo, script_pubkey)
else:
await approver.add_external_output(txo, script_pubkey, orig_txo)
await approver.add_external_output(txo, script_pubkey, index, orig_txo)
self.tx_info.add_output(txo, script_pubkey)

View File

@ -99,7 +99,9 @@ class DecredApprover(BasicApprover):
) -> None:
# NOTE: The following calls Approver.add_external_output(), not BasicApprover.add_external_output().
# This is needed to skip calling helpers.confirm_output(), which is what BasicApprover would do.
await super(BasicApprover, self).add_external_output(txo, script_pubkey, None)
await super(BasicApprover, self).add_external_output(
txo, script_pubkey, None, None
)
await helpers.confirm_decred_sstx_submission(txo, self.coin, self.amount_unit)
@ -206,8 +208,9 @@ class Decred(Bitcoin):
txo: TxOutput,
script_pubkey: bytes,
orig_txo: TxOutput | None,
index: int | None,
) -> None:
await super().approve_output(txo, script_pubkey, orig_txo)
await super().approve_output(txo, script_pubkey, orig_txo, index)
if self.serialize:
self.write_tx_output(self.serialized_tx, txo, script_pubkey)

View File

@ -37,13 +37,22 @@ class UiConfirm:
class UiConfirmOutput(UiConfirm):
def __init__(self, output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit):
def __init__(
self,
output: TxOutput,
coin: CoinInfo,
amount_unit: AmountUnit,
index: int | None,
):
self.output = output
self.coin = coin
self.amount_unit = amount_unit
self.index = index
def confirm_dialog(self, ctx: Context) -> Awaitable[Any]:
return layout.confirm_output(ctx, self.output, self.coin, self.amount_unit)
return layout.confirm_output(
ctx, self.output, self.coin, self.amount_unit, self.index
)
class UiConfirmDecredSSTXSubmission(UiConfirm):
@ -213,8 +222,8 @@ class UiConfirmNonDefaultLocktime(UiConfirm):
)
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
return (yield UiConfirmOutput(output, coin, amount_unit))
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, index: int | None) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
return (yield UiConfirmOutput(output, coin, amount_unit, index))
def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-is-generator]

View File

@ -44,7 +44,11 @@ def format_coin_amount(amount: int, coin: CoinInfo, amount_unit: AmountUnit) ->
async def confirm_output(
ctx: Context, output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit
ctx: Context,
output: TxOutput,
coin: CoinInfo,
amount_unit: AmountUnit,
index: int | None,
) -> None:
from . import omni
from trezor.enums import OutputScriptType
@ -83,6 +87,7 @@ async def confirm_output(
address_short,
format_coin_amount(output.amount, coin, amount_unit),
title=title,
index=index,
)
await layout

View File

@ -232,7 +232,7 @@ async def confirm_sending(
to,
format_coin_amount(ada_amount, network_id),
title,
ButtonRequestType.Other,
br_code=ButtonRequestType.Other,
)

View File

@ -827,30 +827,20 @@ async def confirm_output(
ctx: GenericContext,
address: str,
amount: str,
subtitle: str = "",
title: str = "Confirm sending",
index: int | None = None,
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
) -> None:
# Creating the truncated address here, not having to do it in Rust
chars_to_take = 4
ellipsis = " ... "
truncated_address = address[:chars_to_take] + ellipsis + address[-chars_to_take:]
# Also splitting the address into chunks delimited by whitespace
chunk_length = 4
delimiter = " "
address_chunks: list[str] = []
for i in range(0, len(address), chunk_length):
address_chunks.append(address[i : i + chunk_length])
address_str = delimiter.join(address_chunks)
address_title = "RECIPIENT" if index is None else f"RECIPIENT #{index + 1}"
amount_title = "AMOUNT" if index is None else f"AMOUNT #{index + 1}"
await raise_if_cancelled(
interact(
ctx,
RustLayout(
trezorui2.confirm_output_r(
address=address_str,
truncated_address=truncated_address,
address=address,
address_title=address_title,
amount_title=amount_title,
amount=amount,
)
),
@ -1045,9 +1035,8 @@ async def confirm_total(
total_amount: str,
fee_amount: str,
fee_rate_amount: str | None = None,
title: str = "Send transaction?",
total_label: str = "Total amount:",
fee_label: str = "Including fee:",
total_label: str = "Total amount",
fee_label: str = "Including fee",
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
@ -1056,12 +1045,11 @@ async def confirm_total(
ctx,
RustLayout(
trezorui2.confirm_total_r(
title=title.upper(),
total_amount=total_amount,
fee_amount=fee_amount,
fee_rate_amount=fee_rate_amount,
total_label=total_label,
fee_label=fee_label,
total_label=total_label.upper(),
fee_label=fee_label.upper(),
)
),
br_type,

View File

@ -528,12 +528,15 @@ async def confirm_output(
address: str,
amount: str,
title: str = "SENDING",
index: int | None = None,
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
) -> None:
title = title.upper()
if title.startswith("CONFIRM "):
title = title[len("CONFIRM ") :]
# TODO: include index to be consistent with TR?
await confirm_value(
ctx,
title,