1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-12 17:38:13 +00:00

feat(contacts): implement layout for TXOUT

- SwipeFlow for TXOUT confirmation for labeled contact
This commit is contained in:
obrusvit 2024-12-10 17:10:05 +01:00
parent e6b05a7b27
commit 10269d7a26
8 changed files with 240 additions and 26 deletions

View File

@ -212,6 +212,7 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_total__title_sending_from; MP_QSTR_confirm_total__title_sending_from;
MP_QSTR_confirm_value; MP_QSTR_confirm_value;
MP_QSTR_confirm_with_info; MP_QSTR_confirm_with_info;
MP_QSTR_contact_label;
MP_QSTR_count; MP_QSTR_count;
MP_QSTR_current; MP_QSTR_current;
MP_QSTR_danger; MP_QSTR_danger;
@ -248,6 +249,7 @@ static void _librust_qstrs(void) {
MP_QSTR_firmware_update__title_fingerprint; MP_QSTR_firmware_update__title_fingerprint;
MP_QSTR_first_screen; MP_QSTR_first_screen;
MP_QSTR_flow_confirm_output; MP_QSTR_flow_confirm_output;
MP_QSTR_flow_confirm_output_contact;
MP_QSTR_flow_confirm_reset; MP_QSTR_flow_confirm_reset;
MP_QSTR_flow_confirm_set_new_pin; MP_QSTR_flow_confirm_set_new_pin;
MP_QSTR_flow_continue_recovery; MP_QSTR_flow_continue_recovery;

View File

@ -0,0 +1,134 @@
use crate::ui::button_request::{ButtonRequest, ButtonRequestCode};
use crate::ui::component::text::paragraphs::ParagraphSource;
use crate::ui::component::ButtonRequestExt;
use crate::{
error,
strutil::TString,
translations::TR,
ui::{
component::{
swipe_detect::SwipeSettings, text::paragraphs::ParagraphVecShort, ComponentExt,
},
flow::{
base::{Decision, DecisionBuilder},
FlowController, FlowMsg, SwipeFlow,
},
geometry::Direction,
model_mercury::{
component::{
Frame, FrameMsg, PromptMsg, PromptScreen, SwipeContent, VerticalMenu,
VerticalMenuChoiceMsg,
},
theme,
},
},
};
use super::{ConfirmBlobParams, ShowInfoParams};
#[derive(Copy, Clone, PartialEq, Eq)]
enum ConfirmOutputContact {
Contact,
Amount,
Menu,
AddressInfo,
Confirm,
}
impl FlowController for ConfirmOutputContact {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: Direction) -> Decision {
match (self, direction) {
(Self::Contact | Self::Amount, Direction::Left) => Self::Menu.swipe(direction),
(Self::Contact, Direction::Up) => Self::Amount.swipe(direction),
(Self::Menu, Direction::Right) => Self::Contact.swipe(direction),
(Self::AddressInfo, Direction::Right) => Self::Menu.swipe(direction),
(Self::Amount, Direction::Down) => Self::Contact.swipe(direction),
(Self::Amount, Direction::Up) => self.return_msg(FlowMsg::Confirmed),
_ => self.do_nothing(),
}
}
fn handle_event(&'static self, msg: FlowMsg) -> Decision {
match (self, msg) {
(Self::Contact, FlowMsg::Info) => Self::Menu.goto(),
(Self::Amount, FlowMsg::Info) => Self::Menu.goto(),
(Self::Menu, FlowMsg::Choice(0)) => Self::AddressInfo.goto(),
(Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Cancelled),
(Self::Menu, FlowMsg::Cancelled) => Self::Contact.swipe_right(),
(Self::AddressInfo, FlowMsg::Cancelled) => Self::Menu.goto(),
_ => self.do_nothing(),
}
}
}
pub fn new_confirm_output_contact(
paragraphs: ParagraphVecShort<'static>,
title: TString<'static>,
address_params: ShowInfoParams,
amount_params: ConfirmBlobParams,
) -> Result<SwipeFlow, error::Error> {
let paragraphs = paragraphs.into_paragraphs();
let br_code = ButtonRequestCode::ConfirmOutput as u16;
let br_name = "confirm_output".into();
// Contact
let content_contact = Frame::left_aligned(title, SwipeContent::new(paragraphs))
.with_swipe(Direction::Up, SwipeSettings::default())
.with_swipe(Direction::Left, SwipeSettings::default())
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(move |msg| match msg {
FrameMsg::Button(_) => Some(FlowMsg::Info),
FrameMsg::Content(_) => Some(FlowMsg::Confirmed),
})
.one_button_request(ButtonRequest::from_num(br_code, br_name));
// Amount
let content_amount = amount_params
.into_layout()?
.one_button_request(ButtonRequest::from_num(br_code, br_name));
// Address info
let content_address = address_params.into_layout()?;
// Menu
let content_menu = VerticalMenu::empty()
.item(theme::ICON_CHEVRON_RIGHT, TR::words__address.into())
.danger(theme::ICON_CANCEL, TR::send__cancel_sign.into());
let content_menu = Frame::left_aligned(TString::empty(), content_menu)
.with_cancel_button()
.with_swipe(Direction::Right, SwipeSettings::immediate())
.map(move |msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
// Hold to confirm
let content_confirm = Frame::left_aligned(
TR::send__sign_transaction.into(),
SwipeContent::new(PromptScreen::new_hold_to_confirm()),
)
.with_menu_button()
.with_footer(TR::instructions__hold_to_sign.into(), None)
.with_swipe(Direction::Down, SwipeSettings::default())
.with_swipe(Direction::Left, SwipeSettings::default())
.map(|msg| match msg {
FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed),
FrameMsg::Button(_) => Some(FlowMsg::Info),
_ => None,
});
let res = SwipeFlow::new(&ConfirmOutputContact::Contact)?
.with_page(&ConfirmOutputContact::Contact, content_contact)?
.with_page(&ConfirmOutputContact::Amount, content_amount)?
.with_page(&ConfirmOutputContact::Menu, content_menu)?
.with_page(&ConfirmOutputContact::AddressInfo, content_address)?
.with_page(&ConfirmOutputContact::Confirm, content_confirm)?;
Ok(res)
}

View File

@ -3,6 +3,7 @@ pub mod confirm_action;
pub mod confirm_fido; pub mod confirm_fido;
pub mod confirm_firmware_update; pub mod confirm_firmware_update;
pub mod confirm_output; pub mod confirm_output;
pub mod confirm_output_contact;
pub mod confirm_reset; pub mod confirm_reset;
pub mod confirm_set_new_pin; pub mod confirm_set_new_pin;
pub mod confirm_summary; pub mod confirm_summary;
@ -25,6 +26,7 @@ pub use confirm_action::{
pub use confirm_fido::new_confirm_fido; pub use confirm_fido::new_confirm_fido;
pub use confirm_firmware_update::new_confirm_firmware_update; pub use confirm_firmware_update::new_confirm_firmware_update;
pub use confirm_output::new_confirm_output; pub use confirm_output::new_confirm_output;
pub use confirm_output_contact::new_confirm_output_contact;
pub use confirm_reset::new_confirm_reset; pub use confirm_reset::new_confirm_reset;
pub use confirm_set_new_pin::SetNewPin; pub use confirm_set_new_pin::SetNewPin;
pub use confirm_summary::new_confirm_summary; pub use confirm_summary::new_confirm_summary;

View File

@ -343,6 +343,11 @@ impl ShowInfoParams {
self.items.is_empty() self.items.is_empty()
} }
pub const fn with_chunkify(mut self, chunkify: bool) -> Self {
self.chunkify = chunkify;
self
}
#[inline(never)] #[inline(never)]
pub const fn with_subtitle(mut self, subtitle: Option<TString<'static>>) -> Self { pub const fn with_subtitle(mut self, subtitle: Option<TString<'static>>) -> Self {
self.subtitle = subtitle; self.subtitle = subtitle;

View File

@ -622,6 +622,42 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
extern "C" fn new_confirm_output_contact(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let contact_label: TString = kwargs.get(Qstr::MP_QSTR_contact_label)?.try_into()?;
let address: TString = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
let amount: Obj = kwargs.get(Qstr::MP_QSTR_amount)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, true)?;
let paragraphs = ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_SUB_GREY, "Contact"),
Paragraph::new(&theme::TEXT_SUPER, contact_label),
]);
let amount_params = ConfirmBlobParams::new(TR::words__amount.into(), amount, None)
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.with_text_mono(true)
.with_swipe_up()
.with_swipe_down();
let mut address_params = ShowInfoParams::new(TR::words__address.into())
.with_cancel_button()
.with_chunkify(chunkify);
address_params = unwrap!(address_params.add(TR::words__address.into(), address));
let flow = flow::new_confirm_output_contact(
paragraphs,
title,
address_params,
amount_params,
)?;
Ok(LayoutObj::new_root(flow)?.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let amount: TString = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?; let amount: TString = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?;
@ -1984,6 +2020,17 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page.""" /// """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page."""
Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(), Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(),
/// def flow_confirm_output_contact(
/// *,
/// title: str,
/// contact_label: str,
/// address: str,
/// amount: str,
/// chunkify: bool = True,
/// ) -> LayoutObj[UiResult]:
/// """Confirm the transaction output for labeled contact."""
Qstr::MP_QSTR_flow_confirm_output_contact => obj_fn_kw!(0, new_confirm_output_contact).as_obj(),
/// def confirm_summary( /// def confirm_summary(
/// *, /// *,
/// amount: str, /// amount: str,

View File

@ -522,6 +522,18 @@ def flow_confirm_output(
"""Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page.""" """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page."""
# rust/src/ui/model_mercury/layout.rs
def flow_confirm_output_contact(
*,
title: str,
contact_label: str,
address: str,
amount: str,
chunkify: bool = True,
) -> LayoutObj[UiResult]:
"""Confirm the transaction output for labeled contact."""
# rust/src/ui/model_mercury/layout.rs # rust/src/ui/model_mercury/layout.rs
def confirm_summary( def confirm_summary(
*, *,

View File

@ -96,9 +96,7 @@ async def confirm_output(
title = None title = None
address_label = None address_label = None
if output.label: if output.address_n and not output.multisig:
address_label = output.label
elif output.address_n and not output.multisig:
from trezor import utils from trezor import utils
# Showing the account string only for model_tr layout # Showing the account string only for model_tr layout
@ -129,6 +127,7 @@ async def confirm_output(
chunkify=chunkify, chunkify=chunkify,
source_account=account_label(coin, address_n), source_account=account_label(coin, address_n),
source_account_path=address_n_to_str(address_n) if address_n else None, source_account_path=address_n_to_str(address_n) if address_n else None,
contact_label=output.label,
) )
await layout await layout

View File

@ -375,6 +375,7 @@ async def confirm_output(
source_account: str | None = None, source_account: str | None = None,
source_account_path: str | None = None, source_account_path: str | None = None,
cancel_text: str | None = None, cancel_text: str | None = None,
contact_label: str | None = None,
) -> None: ) -> None:
if address_label is not None: if address_label is not None:
title = address_label title = address_label
@ -385,29 +386,41 @@ async def confirm_output(
else: else:
title = TR.send__title_sending_to title = TR.send__title_sending_to
await raise_if_not_confirmed( if contact_label:
trezorui2.flow_confirm_output( await raise_if_not_confirmed(
title=TR.words__address, trezorui2.flow_confirm_output_contact(
subtitle=title, title = "Send",
message=address, contact_label=contact_label,
amount=amount, address=address,
chunkify=chunkify, amount=amount,
text_mono=True, chunkify=chunkify,
account=source_account, ),
account_path=source_account_path, br_name=None,
address=None, )
address_title=None, else:
br_code=br_code, await raise_if_not_confirmed(
br_name="confirm_output", trezorui2.flow_confirm_output(
summary_items=None, title=TR.words__address,
fee_items=None, subtitle=title,
summary_title=None, message=address,
summary_br_name=None, amount=amount,
summary_br_code=None, chunkify=chunkify,
cancel_text=cancel_text, text_mono=True,
), account=source_account,
br_name=None, account_path=source_account_path,
) address=None,
address_title=None,
br_code=br_code,
br_name="confirm_output",
summary_items=None,
fee_items=None,
summary_title=None,
summary_br_name=None,
summary_br_code=None,
cancel_text=cancel_text,
),
br_name=None,
)
async def should_show_payment_request_details( async def should_show_payment_request_details(