From c5ca22dc2fe846780c3a721aa2fb41b88a65904f Mon Sep 17 00:00:00 2001 From: Lukas Bielesch Date: Wed, 26 Feb 2025 12:17:30 +0100 Subject: [PATCH] feat(eckhart): prompt backup flow --- core/embed/rust/librust_qstr.h | 2 + .../generated/translated_string.rs | 24 +++- .../rust/src/ui/layout_eckhart/flow/mod.rs | 2 + .../ui/layout_eckhart/flow/prompt_backup.rs | 123 ++++++++++++++++++ .../rust/src/ui/layout_eckhart/ui_firmware.rs | 5 +- core/mocks/trezortranslate_keys.pyi | 2 + core/translations/en.json | 16 ++- core/translations/order.json | 4 +- 8 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 core/embed/rust/src/ui/layout_eckhart/flow/prompt_backup.rs diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 8370897bc1..1acc03977a 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -104,6 +104,7 @@ static void _librust_qstrs(void) { MP_QSTR_backup__it_should_be_backed_up_now; MP_QSTR_backup__new_wallet_created; MP_QSTR_backup__new_wallet_successfully_created; + MP_QSTR_backup__not_recommend; MP_QSTR_backup__recover_anytime; MP_QSTR_backup__title_backup_completed; MP_QSTR_backup__title_backup_wallet; @@ -806,6 +807,7 @@ static void _librust_qstrs(void) { MP_QSTR_words__not_recommended; MP_QSTR_words__operation_cancelled; MP_QSTR_words__outputs; + MP_QSTR_words__pay_attention; MP_QSTR_words__please_check_again; MP_QSTR_words__please_try_again; MP_QSTR_words__really_wanna; diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs index 1403f962b7..40598855ee 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs +++ b/core/embed/rust/src/translations/generated/translated_string.rs @@ -29,7 +29,7 @@ pub enum TranslatedString { auto_lock__change_template = 15, // "Auto-lock Trezor after {0} of inactivity?" auto_lock__title = 16, // "Auto-lock delay" backup__can_back_up_anytime = 17, // "You can back up your Trezor once, at any time." - backup__it_should_be_backed_up = 18, // "You should back up your new wallet right now." + backup__it_should_be_backed_up = 18, // {"Bolt": "You should back up your new wallet right now.", "Caesar": "You should back up your new wallet right now.", "Delizia": "You should back up your new wallet right now.", "Eckhart": "Back up your new wallet now."} backup__it_should_be_backed_up_now = 19, // "It should be backed up now!" backup__new_wallet_created = 20, // "Wallet created.\n" backup__new_wallet_successfully_created = 21, // "Wallet created successfully." @@ -1249,7 +1249,7 @@ pub enum TranslatedString { instructions__hold_to_confirm = 855, // "Hold to confirm" words__important = 856, // "Important" reset__words_written_down_template = 857, // "I wrote down all {0} words in order." - backup__create_backup_to_prevent_loss = 858, // "Create a backup to avoid losing access to your funds" + backup__create_backup_to_prevent_loss = 858, // {"Bolt": "Create a backup to avoid losing access to your funds", "Caesar": "Create a backup to avoid losing access to your funds", "Delizia": "Create a backup to avoid losing access to your funds", "Eckhart": "Create a wallet backup to avoid losing access to your funds."} reset__check_backup_instructions = 859, // "Let's do a quick check of your backup." words__instructions = 860, // "Instructions" words__not_recommended = 861, // "Not recommended!" @@ -1408,6 +1408,8 @@ pub enum TranslatedString { ble__unpair_current = 994, // "Unpair connected device" ble__unpair_title = 995, // "Unpair" reset__share_words_first = 996, // "Write down the first word from the backup." + backup__not_recommend = 997, // "We don't recommend to skip wallet backup creation." + words__pay_attention = 998, // "Pay attention" } impl TranslatedString { @@ -1433,7 +1435,14 @@ impl TranslatedString { Self::auto_lock__change_template => "Auto-lock Trezor after {0} of inactivity?", Self::auto_lock__title => "Auto-lock delay", Self::backup__can_back_up_anytime => "You can back up your Trezor once, at any time.", + #[cfg(feature = "layout_bolt")] Self::backup__it_should_be_backed_up => "You should back up your new wallet right now.", + #[cfg(feature = "layout_caesar")] + Self::backup__it_should_be_backed_up => "You should back up your new wallet right now.", + #[cfg(feature = "layout_delizia")] + Self::backup__it_should_be_backed_up => "You should back up your new wallet right now.", + #[cfg(feature = "layout_eckhart")] + Self::backup__it_should_be_backed_up => "Back up your new wallet now.", Self::backup__it_should_be_backed_up_now => "It should be backed up now!", Self::backup__new_wallet_created => "Wallet created.\n", Self::backup__new_wallet_successfully_created => "Wallet created successfully.", @@ -2674,7 +2683,14 @@ impl TranslatedString { Self::instructions__hold_to_confirm => "Hold to confirm", Self::words__important => "Important", Self::reset__words_written_down_template => "I wrote down all {0} words in order.", + #[cfg(feature = "layout_bolt")] Self::backup__create_backup_to_prevent_loss => "Create a backup to avoid losing access to your funds", + #[cfg(feature = "layout_caesar")] + Self::backup__create_backup_to_prevent_loss => "Create a backup to avoid losing access to your funds", + #[cfg(feature = "layout_delizia")] + Self::backup__create_backup_to_prevent_loss => "Create a backup to avoid losing access to your funds", + #[cfg(feature = "layout_eckhart")] + Self::backup__create_backup_to_prevent_loss => "Create a wallet backup to avoid losing access to your funds.", Self::reset__check_backup_instructions => "Let's do a quick check of your backup.", Self::words__instructions => "Instructions", Self::words__not_recommended => "Not recommended!", @@ -2847,6 +2863,8 @@ impl TranslatedString { Self::ble__unpair_current => "Unpair connected device", Self::ble__unpair_title => "Unpair", 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", } } @@ -4250,6 +4268,8 @@ impl TranslatedString { Qstr::MP_QSTR_ble__unpair_current => Some(Self::ble__unpair_current), Qstr::MP_QSTR_ble__unpair_title => Some(Self::ble__unpair_title), 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), _ => None, } } 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 a83f738009..5835ae7e81 100644 --- a/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs @@ -1,7 +1,9 @@ pub mod confirm_reset; +pub mod prompt_backup; pub mod request_passphrase; pub mod show_share_words; pub use confirm_reset::new_confirm_reset; +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/flow/prompt_backup.rs b/core/embed/rust/src/ui/layout_eckhart/flow/prompt_backup.rs new file mode 100644 index 0000000000..302b6210ad --- /dev/null +++ b/core/embed/rust/src/ui/layout_eckhart/flow/prompt_backup.rs @@ -0,0 +1,123 @@ +use crate::{ + error, + strutil::TString, + translations::TR, + ui::{ + component::{ + text::paragraphs::{Paragraph, ParagraphSource, Paragraphs}, + ComponentExt, + }, + flow::{ + base::{Decision, DecisionBuilder as _}, + FlowController, FlowMsg, SwipeFlow, + }, + geometry::{Alignment, Direction, LinearPlacement, Offset}, + }, +}; + +use super::super::{ + component::{ + ActionBar, Button, Header, HeaderMsg, Hint, TextScreen, TextScreenMsg, VerticalMenu, + VerticalMenuScreen, VerticalMenuScreenMsg, + }, + theme, +}; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum PromptBackup { + Intro, + Menu, + SkipBackup, +} + +impl FlowController for PromptBackup { + #[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::Intro, FlowMsg::Info) => Self::Menu.goto(), + (Self::Intro, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed), + (Self::Menu, FlowMsg::Choice(0)) => Self::SkipBackup.swipe_left(), + (Self::Menu, FlowMsg::Cancelled) => Self::Intro.goto(), + (Self::SkipBackup, FlowMsg::Cancelled) => Self::Intro.goto(), + (Self::SkipBackup, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled), + _ => self.do_nothing(), + } + } +} + +pub fn new_prompt_backup() -> Result { + let title: TString = TR::backup__title_create_wallet_backup.into(); + let content: TString = TR::backup__it_should_be_backed_up.into(); + + let paragraphs = Paragraphs::new(Paragraph::new(&theme::TEXT_REGULAR, content)) + .with_placement(LinearPlacement::vertical()); + + let content_intro = TextScreen::new(paragraphs) + .with_header(Header::new(title).with_menu_button()) + .with_action_bar(ActionBar::new_single(Button::with_text( + TR::buttons__continue.into(), + ))) + .map(|msg| match msg { + TextScreenMsg::Menu => Some(FlowMsg::Info), + TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed), + _ => None, + }); + + let content_menu = VerticalMenuScreen::new( + VerticalMenu::empty().item( + Button::with_text(TR::backup__title_skip.into()) + .styled(theme::menu_item_title_red()) + .with_text_align(Alignment::Start) + .with_content_offset(Offset::x(12)), + ), + ) + .with_header( + Header::new(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, + }); + + let paragraphs_skip_intro = Paragraph::new( + &theme::TEXT_REGULAR, + TR::backup__create_backup_to_prevent_loss, + ) + .into_paragraphs() + .with_placement(LinearPlacement::vertical()); + + let content_skip_intro = TextScreen::new(paragraphs_skip_intro) + .with_header( + Header::new(TR::words__pay_attention.into()) + .with_icon(theme::ICON_WARNING, theme::ORANGE) + .with_text_style(theme::label_title_danger()), + ) + .with_action_bar(ActionBar::new_double( + Button::with_icon(theme::ICON_CHEVRON_LEFT), + Button::with_text(TR::buttons__skip.into()).styled(theme::button_cancel()), + )) + .with_hint(Hint::new_instruction(TR::backup__not_recommend, None)) + .map(|msg| match msg { + TextScreenMsg::Menu => Some(FlowMsg::Cancelled), + TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed), + TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled), + }); + + let mut res = SwipeFlow::new(&PromptBackup::Intro)?; + res.add_page(&PromptBackup::Intro, content_intro)? + .add_page(&PromptBackup::Menu, content_menu)? + .add_page(&PromptBackup::SkipBackup, content_skip_intro)?; + Ok(res) +} 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 a95e0678a4..06b34e6610 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs @@ -351,7 +351,8 @@ impl FirmwareUI for UIEckhart { } fn prompt_backup() -> Result { - Err::, Error>(Error::ValueError(c"not implemented")) + let flow = flow::prompt_backup::new_prompt_backup()?; + Ok(flow) } fn request_bip39( @@ -575,8 +576,6 @@ impl FirmwareUI for UIEckhart { Ok(layout) } - // TODO: This is a temporary implementation so the UI can be functional. - // TODO: This is a temporary implementation so the UI can be functional. fn show_progress( description: TString<'static>, _indeterminate: bool, diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index d9cb9a6bd8..edf825aacd 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -35,6 +35,7 @@ class TR: backup__it_should_be_backed_up_now: str = "It should be backed up now!" backup__new_wallet_created: str = "Wallet created.\n" backup__new_wallet_successfully_created: str = "Wallet created successfully." + backup__not_recommend: str = "We don't recommend to skip wallet backup creation." backup__recover_anytime: str = "You can use your backup to recover your wallet at any time." backup__title_backup_completed: str = "Wallet backup completed" backup__title_backup_wallet: str = "Back up wallet" @@ -966,6 +967,7 @@ class TR: words__not_recommended: str = "Not recommended!" words__operation_cancelled: str = "Operation cancelled" words__outputs: str = "outputs" + words__pay_attention: str = "Pay attention" 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" diff --git a/core/translations/en.json b/core/translations/en.json index dc1847716a..f6724c872a 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -30,10 +30,21 @@ "auto_lock__title": "Auto-lock delay", "auto_lock__turned_on": "Auto-lock turned on", "backup__can_back_up_anytime": "You can back up your Trezor once, at any time.", - "backup__create_backup_to_prevent_loss": "Create a backup to avoid losing access to your funds", + "backup__create_backup_to_prevent_loss": { + "Bolt": "Create a backup to avoid losing access to your funds", + "Caesar": "Create a backup to avoid losing access to your funds", + "Delizia": "Create a backup to avoid losing access to your funds", + "Eckhart": "Create a wallet backup to avoid losing access to your funds." + }, + "backup__not_recommend": "We don't recommend to skip wallet backup creation.", "backup__info_multi_share_backup": "Your wallet backup contains multiple lists of words in a specific order (shares).", "backup__info_single_share_backup": "Your wallet backup contains {0} words in a specific order.", - "backup__it_should_be_backed_up": "You should back up your new wallet right now.", + "backup__it_should_be_backed_up": { + "Bolt": "You should back up your new wallet right now.", + "Caesar": "You should back up your new wallet right now.", + "Delizia": "You should back up your new wallet right now.", + "Eckhart": "Back up your new wallet now." + }, "backup__it_should_be_backed_up_now": "It should be backed up now!", "backup__new_wallet_created": "Wallet created.\n", "backup__new_wallet_successfully_created": "Wallet created successfully.", @@ -981,6 +992,7 @@ "words__good_to_know": "Good to know", "words__important": "Important", "words__instructions": "Instructions", + "words__pay_attention": "Pay attention", "words__keep_it_safe": "Keep it safe!", "words__know_what_your_doing": "Continue only if you know what you are doing!", "words__my_trezor": "My Trezor", diff --git a/core/translations/order.json b/core/translations/order.json index 2e26e27f7d..ec7cfa5089 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -995,5 +995,7 @@ "993": "ble__unpair_all", "994": "ble__unpair_current", "995": "ble__unpair_title", - "996": "reset__share_words_first" + "996": "reset__share_words_first", + "997": "backup__not_recommend", + "998": "words__pay_attention" }