diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index e22b7ee2ec..6605cccd6c 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -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; diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs index 34c9d37c63..4c7ee343ed 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs +++ b/core/embed/rust/src/translations/generated/translated_string.rs @@ -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, } } diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/get_address.rs b/core/embed/rust/src/ui/layout_eckhart/flow/get_address.rs new file mode 100644 index 0000000000..2761ff5df3 --- /dev/null +++ b/core/embed/rust/src/ui/layout_eckhart/flow/get_address.rs @@ -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>, + extra: Option>, + address: Obj, // TODO: get rid of Obj + chunkify: bool, + address_qr: TString<'static>, + case_sensitive: bool, + account: Option>, + path: Option>, + _xpubs: Obj, // TODO: get rid of Obj + title_success: TString<'static>, + _br_code: u16, + _br_name: TString<'static>, +) -> Result { + // 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::( + &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::( + &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) +} diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs b/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs index 5835ae7e81..47c7a2f4b4 100644 --- a/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs @@ -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; diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs index 2367f98bb1..346b80b128 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs @@ -335,21 +335,36 @@ impl FirmwareUI for UIEckhart { } fn flow_get_address( - _address: Obj, - _title: TString<'static>, - _description: Option>, - _extra: Option>, - _chunkify: bool, - _address_qr: TString<'static>, - _case_sensitive: bool, - _account: Option>, - _path: Option>, - _xpubs: Obj, - _title_success: TString<'static>, - _br_code: u16, - _br_name: TString<'static>, + address: Obj, + title: TString<'static>, + description: Option>, + extra: Option>, + chunkify: bool, + address_qr: TString<'static>, + case_sensitive: bool, + account: Option>, + path: Option>, + xpubs: Obj, + title_success: TString<'static>, + br_code: u16, + br_name: TString<'static>, ) -> Result { - Err::, 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( diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index 63032020fc..0f0fe57519 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -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" diff --git a/core/translations/en.json b/core/translations/en.json index 3ed684678a..2e5ef31c78 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -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", diff --git a/core/translations/order.json b/core/translations/order.json index 461a7dbb18..e7049cd8ee 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -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" } diff --git a/core/translations/signatures.json b/core/translations/signatures.json index e6183c76bb..bbe61e1b9d 100644 --- a/core/translations/signatures.json +++ b/core/translations/signatures.json @@ -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": [ {