1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-12 06:06:07 +00:00

feat(eckhart): receive address flow

This commit is contained in:
Lukas Bielesch 2025-03-06 13:14:25 +01:00 committed by obrusvit
parent ffc821aa2d
commit 5e8a75619e
9 changed files with 305 additions and 21 deletions

View File

@ -52,6 +52,7 @@ static void _librust_qstrs(void) {
MP_QSTR_address;
MP_QSTR_address__cancel_contact_support;
MP_QSTR_address__cancel_receive;
MP_QSTR_address__check_with_source;
MP_QSTR_address__confirmed;
MP_QSTR_address__public_key;
MP_QSTR_address__public_key_confirmed;
@ -780,6 +781,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__please_check_again;
MP_QSTR_words__please_try_again;
MP_QSTR_words__really_wanna;
MP_QSTR_words__receive;
MP_QSTR_words__recipient;
MP_QSTR_words__settings;
MP_QSTR_words__sign;

View File

@ -1255,7 +1255,7 @@ pub enum TranslatedString {
words__not_recommended = 861, // "Not recommended!"
address_details__account_info = 862, // "Account info"
address__cancel_contact_support = 863, // "If receive address doesn't match, contact Trezor Support at trezor.io/support."
address__cancel_receive = 864, // "Cancel receive"
address__cancel_receive = 864, // {"Bolt": "Cancel receive", "Caesar": "Cancel receive", "Delizia": "Cancel receive", "Eckhart": "Cancel receive?"}
address__qr_code = 865, // "QR code"
address_details__derivation_path = 866, // "Derivation path"
instructions__continue_in_app = 867, // "Continue in the app"
@ -1407,6 +1407,8 @@ pub enum TranslatedString {
reset__share_words_first = 993, // "Write down the first word from the backup."
backup__not_recommend = 994, // "We don't recommend to skip wallet backup creation."
words__pay_attention = 995, // "Pay attention"
address__check_with_source = 996, // "Check the address with source."
words__receive = 997, // "Receive"
}
impl TranslatedString {
@ -2693,7 +2695,14 @@ impl TranslatedString {
Self::words__not_recommended => "Not recommended!",
Self::address_details__account_info => "Account info",
Self::address__cancel_contact_support => "If receive address doesn't match, contact Trezor Support at trezor.io/support.",
#[cfg(feature = "layout_bolt")]
Self::address__cancel_receive => "Cancel receive",
#[cfg(feature = "layout_caesar")]
Self::address__cancel_receive => "Cancel receive",
#[cfg(feature = "layout_delizia")]
Self::address__cancel_receive => "Cancel receive",
#[cfg(feature = "layout_eckhart")]
Self::address__cancel_receive => "Cancel receive?",
Self::address__qr_code => "QR code",
Self::address_details__derivation_path => "Derivation path",
Self::instructions__continue_in_app => "Continue in the app",
@ -2880,6 +2889,8 @@ impl TranslatedString {
Self::reset__share_words_first => "Write down the first word from the backup.",
Self::backup__not_recommend => "We don't recommend to skip wallet backup creation.",
Self::words__pay_attention => "Pay attention",
Self::address__check_with_source => "Check the address with source.",
Self::words__receive => "Receive",
}
}
@ -4282,6 +4293,8 @@ impl TranslatedString {
Qstr::MP_QSTR_reset__share_words_first => Some(Self::reset__share_words_first),
Qstr::MP_QSTR_backup__not_recommend => Some(Self::backup__not_recommend),
Qstr::MP_QSTR_words__pay_attention => Some(Self::words__pay_attention),
Qstr::MP_QSTR_address__check_with_source => Some(Self::address__check_with_source),
Qstr::MP_QSTR_words__receive => Some(Self::words__receive),
_ => None,
}
}

View File

@ -0,0 +1,236 @@
use crate::{
error,
micropython::obj::Obj,
strutil::TString,
translations::TR,
ui::{
component::{
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt},
ComponentExt, Qr,
},
flow::{
base::{Decision, DecisionBuilder as _},
FlowController, FlowMsg, SwipeFlow,
},
geometry::{Alignment, Direction, LinearPlacement, Offset},
layout::util::ConfirmValueParams,
},
};
use super::super::{
component::Button,
firmware::{
ActionBar, Header, HeaderMsg, Hint, QrScreen, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme,
};
const TIMEOUT_MS: u32 = 2000;
const ITEM_PADDING: i16 = 16;
const GROUP_PADDING: i16 = 20;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum GetAddress {
Address,
Confirmed,
Menu,
QrCode,
AccountInfo,
Cancel,
}
impl FlowController for GetAddress {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: Direction) -> Decision {
match (self, direction) {
_ => self.do_nothing(),
}
}
fn handle_event(&'static self, msg: FlowMsg) -> Decision {
match (self, msg) {
(Self::Address, FlowMsg::Info) => Self::Menu.goto(),
(Self::Address, FlowMsg::Confirmed) => Self::Confirmed.goto(),
(Self::Confirmed, _) => self.return_msg(FlowMsg::Confirmed),
(Self::Menu, FlowMsg::Choice(0)) => Self::QrCode.swipe_left(),
(Self::Menu, FlowMsg::Choice(1)) => Self::AccountInfo.swipe_left(),
(Self::Menu, FlowMsg::Choice(2)) => Self::Cancel.swipe_left(),
(Self::Menu, FlowMsg::Cancelled) => Self::Address.swipe_right(),
(Self::QrCode, FlowMsg::Cancelled) => Self::Menu.goto(),
(Self::AccountInfo, FlowMsg::Cancelled) => Self::Menu.goto(),
(Self::Cancel, FlowMsg::Cancelled) => Self::Address.goto(),
(Self::Cancel, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
_ => self.do_nothing(),
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn new_get_address(
title: TString<'static>,
_description: Option<TString<'static>>,
extra: Option<TString<'static>>,
address: Obj, // TODO: get rid of Obj
chunkify: bool,
address_qr: TString<'static>,
case_sensitive: bool,
account: Option<TString<'static>>,
path: Option<TString<'static>>,
_xpubs: Obj, // TODO: get rid of Obj
title_success: TString<'static>,
_br_code: u16,
_br_name: TString<'static>,
) -> Result<SwipeFlow, error::Error> {
// Address
let flow_title: TString = TR::words__receive.into();
let paragraphs = ConfirmValueParams {
description: title,
extra: extra.unwrap_or_else(|| "".into()),
value: address.try_into()?,
font: if chunkify {
let address: TString = address.try_into()?;
theme::get_chunkified_text_style(address.len())
} else {
&theme::TEXT_MONO_ADDRESS
},
description_font: &theme::TEXT_MEDIUM_EXTRA_LIGHT,
extra_font: &theme::TEXT_SMALL,
}
.into_paragraphs()
.with_placement(LinearPlacement::vertical().with_spacing(14));
let content_address = TextScreen::new(paragraphs)
.with_header(Header::new(flow_title).with_menu_button())
.with_action_bar(ActionBar::new_single(
Button::with_text(TR::buttons__confirm.into()).styled(theme::button_confirm()),
))
.with_hint(Hint::new_instruction(
TR::address__check_with_source,
Some(theme::ICON_INFO),
))
.map(|msg| match msg {
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
TextScreenMsg::Menu => Some(FlowMsg::Info),
});
let content_confirmed =
TextScreen::new(Paragraph::new(&theme::TEXT_REGULAR, title_success).into_paragraphs())
.with_header(
Header::new(TR::words__title_done.into())
.with_icon(theme::ICON_DONE, theme::GREEN_LIGHT)
.with_text_style(theme::label_title_confirm()),
)
.with_action_bar(ActionBar::new_timeout(
Button::with_text(TR::instructions__continue_in_app.into()),
TIMEOUT_MS,
))
.map(|_| Some(FlowMsg::Confirmed));
// Menu
let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty()
.item(
Button::with_text(TR::address__qr_code.into())
.styled(theme::menu_item_title())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
)
.item(
Button::with_text(TR::address_details__account_info.into())
.styled(theme::menu_item_title())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
)
.item(
Button::with_text(TR::buttons__cancel.into())
.styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
),
)
.with_header(
Header::new(flow_title)
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
_ => None,
});
// QrCode
let content_qr = QrScreen::new(address_qr.map(|s| Qr::new(s, case_sensitive))?)
.with_header(
Header::new(TR::address_details__title_receive_address.into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.map(|_| Some(FlowMsg::Cancelled));
// AccountInfo
let mut para = ParagraphVecShort::new();
if let Some(a) = account {
para.add(Paragraph::new::<TString>(
&theme::TEXT_SMALL_LIGHT,
TR::words__account.into(),
));
para.add(Paragraph::new(&theme::TEXT_MONO_EXTRA_LIGHT, a).with_top_padding(ITEM_PADDING));
}
if let Some(p) = path {
para.add(
Paragraph::new::<TString>(
&theme::TEXT_SMALL_LIGHT,
TR::address_details__derivation_path.into(),
)
.with_top_padding(GROUP_PADDING),
);
para.add(Paragraph::new(&theme::TEXT_MONO_EXTRA_LIGHT, p).with_top_padding(ITEM_PADDING));
}
let content_account = TextScreen::new(
para.into_paragraphs()
.with_placement(LinearPlacement::vertical()),
)
.with_header(
Header::new(TR::address_details__account_info.into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.map(|_| Some(FlowMsg::Cancelled));
// Cancel
let content_cancel_info = TextScreen::new(
Paragraph::new(&theme::TEXT_REGULAR, TR::address__cancel_receive)
.into_paragraphs()
.with_placement(LinearPlacement::vertical()),
)
.with_header(Header::new(flow_title))
.with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CHEVRON_LEFT),
Button::with_text(TR::buttons__cancel.into()).styled(theme::button_cancel()),
))
.with_hint(Hint::new_instruction(
TR::address__cancel_contact_support,
Some(theme::ICON_INFO),
))
.map(|msg| match msg {
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
_ => None,
});
let mut res = SwipeFlow::new(&GetAddress::Address)?;
res.add_page(&GetAddress::Address, content_address)?
.add_page(&GetAddress::Confirmed, content_confirmed)?
.add_page(&GetAddress::Menu, content_menu)?
.add_page(&GetAddress::QrCode, content_qr)?
.add_page(&GetAddress::AccountInfo, content_account)?
.add_page(&GetAddress::Cancel, content_cancel_info)?;
Ok(res)
}

View File

@ -1,9 +1,11 @@
pub mod confirm_reset;
pub mod get_address;
pub mod prompt_backup;
pub mod request_passphrase;
pub mod show_share_words;
pub use confirm_reset::new_confirm_reset;
pub use get_address::GetAddress;
pub use prompt_backup::PromptBackup;
pub use request_passphrase::RequestPassphrase;
pub use show_share_words::new_show_share_words_flow;

View File

@ -335,21 +335,36 @@ impl FirmwareUI for UIEckhart {
}
fn flow_get_address(
_address: Obj,
_title: TString<'static>,
_description: Option<TString<'static>>,
_extra: Option<TString<'static>>,
_chunkify: bool,
_address_qr: TString<'static>,
_case_sensitive: bool,
_account: Option<TString<'static>>,
_path: Option<TString<'static>>,
_xpubs: Obj,
_title_success: TString<'static>,
_br_code: u16,
_br_name: TString<'static>,
address: Obj,
title: TString<'static>,
description: Option<TString<'static>>,
extra: Option<TString<'static>>,
chunkify: bool,
address_qr: TString<'static>,
case_sensitive: bool,
account: Option<TString<'static>>,
path: Option<TString<'static>>,
xpubs: Obj,
title_success: TString<'static>,
br_code: u16,
br_name: TString<'static>,
) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
let flow = flow::get_address::new_get_address(
title,
description,
extra,
address,
chunkify,
address_qr,
case_sensitive,
account,
path,
xpubs,
title_success,
br_code,
br_name,
)?;
Ok(flow)
}
fn multiple_pages_texts(

View File

@ -10,6 +10,7 @@ class TR:
addr_mismatch__xpub_mismatch: str = "XPUB mismatch?"
address__cancel_contact_support: str = "If receive address doesn't match, contact Trezor Support at trezor.io/support."
address__cancel_receive: str = "Cancel receive"
address__check_with_source: str = "Check the address with source."
address__confirmed: str = "Receive address confirmed"
address__public_key: str = "Public key"
address__public_key_confirmed: str = "Public key confirmed"
@ -968,6 +969,7 @@ class TR:
words__please_check_again: str = "Please check again"
words__please_try_again: str = "Please try again"
words__really_wanna: str = "Do you really want to"
words__receive: str = "Receive"
words__recipient: str = "Recipient"
words__settings: str = "Settings"
words__sign: str = "Sign"

View File

@ -11,7 +11,12 @@
"addr_mismatch__wrong_derivation_path": "Wrong derivation path for selected account.",
"addr_mismatch__xpub_mismatch": "XPUB mismatch?",
"address__cancel_contact_support": "If receive address doesn't match, contact Trezor Support at trezor.io/support.",
"address__cancel_receive": "Cancel receive",
"address__cancel_receive": {
"Bolt": "Cancel receive",
"Caesar": "Cancel receive",
"Delizia": "Cancel receive",
"Eckhart": "Cancel receive?"
},
"address__confirmed": "Receive address confirmed",
"address__public_key": "Public key",
"address__public_key_confirmed": "Public key confirmed",
@ -19,6 +24,7 @@
"address__title_cosigner": "Cosigner",
"address__title_receive_address": "Receive address",
"address__title_yours": "Yours",
"address__check_with_source": "Check the address with source.",
"address_details__account_info": "Account info",
"address_details__derivation_path": "Derivation path",
"address_details__derivation_path_colon": "Derivation path:",
@ -404,7 +410,12 @@
"inputs__return": "RETURN",
"inputs__show": "SHOW",
"inputs__space": "SPACE",
"instructions__continue_holding": {"Bolt": "", "Caesar": "", "Delizia": "Continue\nholding", "Eckhart": "Keep holding"},
"instructions__continue_holding": {
"Bolt": "",
"Caesar": "",
"Delizia": "Continue\nholding",
"Eckhart": "Keep holding"
},
"instructions__continue_in_app": "Continue in the app",
"instructions__enter_next_share": "Enter next share",
"instructions__hold_to_confirm": "Hold to confirm",
@ -1010,6 +1021,7 @@
"words__please_check_again": "Please check again",
"words__please_try_again": "Please try again",
"words__really_wanna": "Do you really want to",
"words__receive": "Receive",
"words__recipient": "Recipient",
"words__settings": "Settings",
"words__sign": "Sign",

View File

@ -994,5 +994,7 @@
"992": "nostr__event_kind_template",
"993": "reset__share_words_first",
"994": "backup__not_recommend",
"995": "words__pay_attention"
"995": "words__pay_attention",
"996": "address__check_with_source",
"997": "words__receive"
}

View File

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "f00c1c603fb911b9b65dbe30261310c4ed90ac1ff90f9c5e52e0ce34acce2745",
"datetime": "2025-03-06T16:38:48.918299",
"commit": "6509d2b3f29d5bd3ee24a3843a6f3e6d9877cf30"
"merkle_root": "e89cf3cbdee6db85be61f81f9efbfa2a7f0e025f1472165cb0e6688f3acfdb7c",
"datetime": "2025-03-08T13:14:44.068121",
"commit": "b624879561ee577733b3d954b4d242a6e4f21d0d"
},
"history": [
{