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_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;
|
||||||
|
@ -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_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;
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
*,
|
*,
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user