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:
parent
e6b05a7b27
commit
10269d7a26
@ -212,6 +212,7 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_confirm_total__title_sending_from;
|
||||
MP_QSTR_confirm_value;
|
||||
MP_QSTR_confirm_with_info;
|
||||
MP_QSTR_contact_label;
|
||||
MP_QSTR_count;
|
||||
MP_QSTR_current;
|
||||
MP_QSTR_danger;
|
||||
@ -248,6 +249,7 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_firmware_update__title_fingerprint;
|
||||
MP_QSTR_first_screen;
|
||||
MP_QSTR_flow_confirm_output;
|
||||
MP_QSTR_flow_confirm_output_contact;
|
||||
MP_QSTR_flow_confirm_reset;
|
||||
MP_QSTR_flow_confirm_set_new_pin;
|
||||
MP_QSTR_flow_continue_recovery;
|
||||
|
@ -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)
|
||||
}
|
@ -3,6 +3,7 @@ pub mod confirm_action;
|
||||
pub mod confirm_fido;
|
||||
pub mod confirm_firmware_update;
|
||||
pub mod confirm_output;
|
||||
pub mod confirm_output_contact;
|
||||
pub mod confirm_reset;
|
||||
pub mod confirm_set_new_pin;
|
||||
pub mod confirm_summary;
|
||||
@ -25,6 +26,7 @@ pub use confirm_action::{
|
||||
pub use confirm_fido::new_confirm_fido;
|
||||
pub use confirm_firmware_update::new_confirm_firmware_update;
|
||||
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_set_new_pin::SetNewPin;
|
||||
pub use confirm_summary::new_confirm_summary;
|
||||
|
@ -343,6 +343,11 @@ impl ShowInfoParams {
|
||||
self.items.is_empty()
|
||||
}
|
||||
|
||||
pub const fn with_chunkify(mut self, chunkify: bool) -> Self {
|
||||
self.chunkify = chunkify;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub const fn with_subtitle(mut self, subtitle: Option<TString<'static>>) -> Self {
|
||||
self.subtitle = subtitle;
|
||||
|
@ -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) }
|
||||
}
|
||||
|
||||
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 {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
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."""
|
||||
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(
|
||||
/// *,
|
||||
/// amount: str,
|
||||
|
@ -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."""
|
||||
|
||||
|
||||
# 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
|
||||
def confirm_summary(
|
||||
*,
|
||||
|
@ -96,9 +96,7 @@ async def confirm_output(
|
||||
title = None
|
||||
|
||||
address_label = None
|
||||
if output.label:
|
||||
address_label = output.label
|
||||
elif output.address_n and not output.multisig:
|
||||
if output.address_n and not output.multisig:
|
||||
from trezor import utils
|
||||
|
||||
# Showing the account string only for model_tr layout
|
||||
@ -129,6 +127,7 @@ async def confirm_output(
|
||||
chunkify=chunkify,
|
||||
source_account=account_label(coin, address_n),
|
||||
source_account_path=address_n_to_str(address_n) if address_n else None,
|
||||
contact_label=output.label,
|
||||
)
|
||||
|
||||
await layout
|
||||
|
@ -375,6 +375,7 @@ async def confirm_output(
|
||||
source_account: str | None = None,
|
||||
source_account_path: str | None = None,
|
||||
cancel_text: str | None = None,
|
||||
contact_label: str | None = None,
|
||||
) -> None:
|
||||
if address_label is not None:
|
||||
title = address_label
|
||||
@ -385,29 +386,41 @@ async def confirm_output(
|
||||
else:
|
||||
title = TR.send__title_sending_to
|
||||
|
||||
await raise_if_not_confirmed(
|
||||
trezorui2.flow_confirm_output(
|
||||
title=TR.words__address,
|
||||
subtitle=title,
|
||||
message=address,
|
||||
amount=amount,
|
||||
chunkify=chunkify,
|
||||
text_mono=True,
|
||||
account=source_account,
|
||||
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,
|
||||
)
|
||||
if contact_label:
|
||||
await raise_if_not_confirmed(
|
||||
trezorui2.flow_confirm_output_contact(
|
||||
title = "Send",
|
||||
contact_label=contact_label,
|
||||
address=address,
|
||||
amount=amount,
|
||||
chunkify=chunkify,
|
||||
),
|
||||
br_name=None,
|
||||
)
|
||||
else:
|
||||
await raise_if_not_confirmed(
|
||||
trezorui2.flow_confirm_output(
|
||||
title=TR.words__address,
|
||||
subtitle=title,
|
||||
message=address,
|
||||
amount=amount,
|
||||
chunkify=chunkify,
|
||||
text_mono=True,
|
||||
account=source_account,
|
||||
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(
|
||||
|
Loading…
Reference in New Issue
Block a user