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

feat(eckhart): show danger flow

This commit is contained in:
Lukas Bielesch 2025-03-04 14:24:54 +01:00 committed by obrusvit
parent 5e8a75619e
commit 2b06ba21bd
11 changed files with 170 additions and 9 deletions

View File

@ -323,6 +323,7 @@ static void _librust_qstrs(void) {
MP_QSTR_max_feerate; MP_QSTR_max_feerate;
MP_QSTR_max_len; MP_QSTR_max_len;
MP_QSTR_max_rounds; MP_QSTR_max_rounds;
MP_QSTR_menu_title;
MP_QSTR_message; MP_QSTR_message;
MP_QSTR_min_count; MP_QSTR_min_count;
MP_QSTR_misc__decrypt_value; MP_QSTR_misc__decrypt_value;

View File

@ -736,12 +736,16 @@ extern "C" fn new_show_danger(n_args: usize, args: *const Obj, kwargs: *mut Map)
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?;
let menu_title: Option<TString> = kwargs
.get(Qstr::MP_QSTR_menu_title)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb_cancel: Option<TString> = kwargs let verb_cancel: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_cancel) .get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none()) .unwrap_or_else(|_| Obj::const_none())
.try_into_option()?; .try_into_option()?;
let layout = ModelUI::show_danger(title, description, value, verb_cancel)?; let layout = ModelUI::show_danger(title, description, value, menu_title, verb_cancel)?;
Ok(LayoutObj::new_root(layout)?.into()) Ok(LayoutObj::new_root(layout)?.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1518,6 +1522,7 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// title: str, /// title: str,
/// description: str, /// description: str,
/// value: str = "", /// value: str = "",
/// menu_title: str | None = None,
/// verb_cancel: str | None = None, /// verb_cancel: str | None = None,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Warning modal that makes it easier to cancel than to continue.""" /// """Warning modal that makes it easier to cancel than to continue."""

View File

@ -815,6 +815,7 @@ impl FirmwareUI for UIBolt {
_title: TString<'static>, _title: TString<'static>,
_description: TString<'static>, _description: TString<'static>,
_value: TString<'static>, _value: TString<'static>,
_menu_title: Option<TString<'static>>,
_verb_cancel: Option<TString<'static>>, _verb_cancel: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_danger not supported")) Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_danger not supported"))

View File

@ -986,6 +986,7 @@ impl FirmwareUI for UICaesar {
_title: TString<'static>, _title: TString<'static>,
_description: TString<'static>, _description: TString<'static>,
_value: TString<'static>, _value: TString<'static>,
_menu_title: Option<TString<'static>>,
_verb_cancel: Option<TString<'static>>, _verb_cancel: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_danger not supported")) Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_danger not supported"))

View File

@ -829,6 +829,7 @@ impl FirmwareUI for UIDelizia {
title: TString<'static>, title: TString<'static>,
description: TString<'static>, description: TString<'static>,
value: TString<'static>, value: TString<'static>,
_menu_title: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>, verb_cancel: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
let flow = flow::show_danger::new_show_danger(title, description, value, verb_cancel)?; let flow = flow::show_danger::new_show_danger(title, description, value, verb_cancel)?;

View File

@ -2,10 +2,12 @@ pub mod confirm_reset;
pub mod get_address; pub mod get_address;
pub mod prompt_backup; pub mod prompt_backup;
pub mod request_passphrase; pub mod request_passphrase;
pub mod show_danger;
pub mod show_share_words; pub mod show_share_words;
pub use confirm_reset::new_confirm_reset; pub use confirm_reset::new_confirm_reset;
pub use get_address::GetAddress; pub use get_address::GetAddress;
pub use prompt_backup::PromptBackup; pub use prompt_backup::PromptBackup;
pub use request_passphrase::RequestPassphrase; pub use request_passphrase::RequestPassphrase;
pub use show_danger::ShowDanger;
pub use show_share_words::new_show_share_words_flow; pub use show_share_words::new_show_share_words_flow;

View File

@ -0,0 +1,137 @@
use crate::{
error,
strutil::TString,
translations::TR,
ui::{
component::{
text::paragraphs::{Paragraph, ParagraphSource},
ComponentExt,
},
flow::{
base::{Decision, DecisionBuilder as _},
FlowController, FlowMsg, SwipeFlow,
},
geometry::{Alignment, Direction, LinearPlacement, Offset},
},
};
use super::super::{
component::Button,
firmware::{
ActionBar, Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen,
VerticalMenuScreenMsg,
},
theme,
};
const TIMEOUT_MS: u32 = 2000;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ShowDanger {
Message,
Menu,
Cancelled,
}
impl FlowController for ShowDanger {
#[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::Message, FlowMsg::Info) => Self::Menu.goto(),
(Self::Message, FlowMsg::Cancelled) => Self::Cancelled.goto(),
(Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Confirmed),
(Self::Menu, FlowMsg::Choice(_)) => Self::Cancelled.goto(),
(Self::Menu, FlowMsg::Cancelled) => Self::Message.goto(),
(Self::Cancelled, _) => self.return_msg(FlowMsg::Cancelled),
_ => self.do_nothing(),
}
}
}
pub fn new_show_danger(
title: TString<'static>,
description: TString<'static>,
value: TString<'static>,
menu_title: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>,
) -> Result<SwipeFlow, error::Error> {
let verb_cancel = verb_cancel.unwrap_or(TR::words__cancel_and_exit.into());
// Message
let paragraphs = [
Paragraph::new(&theme::TEXT_REGULAR, description),
Paragraph::new(&theme::TEXT_MONO_EXTRA_LIGHT, value),
]
.into_paragraphs()
.with_placement(LinearPlacement::vertical().with_spacing(35));
let content_message = TextScreen::new(paragraphs)
.with_header(
Header::new(title)
.with_menu_button()
.with_icon(theme::ICON_INFO, theme::ORANGE)
.with_text_style(theme::label_title_danger()),
)
.with_action_bar(ActionBar::new_single(Button::with_text(verb_cancel)))
.map(|msg| match msg {
TextScreenMsg::Menu => Some(FlowMsg::Info),
TextScreenMsg::Confirmed => Some(FlowMsg::Cancelled),
_ => None,
});
// Menu
let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty()
.with_separators()
.item(
Button::with_text(verb_cancel)
.styled(theme::menu_item_title())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
)
.item(
Button::with_text(TR::words__continue_anyway.into())
.styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
),
)
.with_header(
Header::new(menu_title.unwrap_or("".into()))
.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,
});
// Cancelled
let content_cancelled = TextScreen::new(
Paragraph::new(&theme::TEXT_REGULAR, TR::words__operation_cancelled)
.into_paragraphs()
.with_placement(LinearPlacement::vertical()),
)
.with_header(Header::new(TR::words__title_done.into()).with_icon(theme::ICON_DONE, theme::GREY))
.with_action_bar(ActionBar::new_timeout(
Button::with_text(TR::instructions__continue_in_app.into()),
TIMEOUT_MS,
))
.map(|_| Some(FlowMsg::Confirmed));
let mut res = SwipeFlow::new(&ShowDanger::Message)?;
res.add_page(&ShowDanger::Message, content_message)?
.add_page(&ShowDanger::Menu, content_menu)?
.add_page(&ShowDanger::Cancelled, content_cancelled)?;
Ok(res)
}

View File

@ -39,10 +39,10 @@ pub const TEXT_BIG: TextStyle = TextStyle::new(
/// TT Satoshi Regular - 38 (Screen text, Menu item label) /// TT Satoshi Regular - 38 (Screen text, Menu item label)
pub const TEXT_REGULAR: TextStyle = TextStyle::new( pub const TEXT_REGULAR: TextStyle = TextStyle::new(
fonts::FONT_SATOSHI_REGULAR_38, fonts::FONT_SATOSHI_REGULAR_38,
GREY_EXTRA_LIGHT, GREY_LIGHT,
BG, BG,
GREY_EXTRA_LIGHT, GREY_LIGHT,
GREY_EXTRA_LIGHT, GREY_LIGHT,
); );
/// TT Satoshi Medium - 26 (Screen text, Button label, Input value) /// TT Satoshi Medium - 26 (Screen text, Button label, Input value)
pub const TEXT_MEDIUM: TextStyle = TextStyle::new( pub const TEXT_MEDIUM: TextStyle = TextStyle::new(
@ -105,6 +105,14 @@ pub fn get_chunkified_text_style(_character_length: usize) -> &'static TextStyle
&TEXT_MONO_ADDRESS_CHUNKS &TEXT_MONO_ADDRESS_CHUNKS
} }
pub const TEXT_MONO_EXTRA_LIGHT: TextStyle = TextStyle::new(
fonts::FONT_MONO_LIGHT_30,
GREY_EXTRA_LIGHT,
BG,
GREY_EXTRA_LIGHT,
GREY_EXTRA_LIGHT,
);
// Macro for styles differing only in text color // Macro for styles differing only in text color
macro_rules! label_title { macro_rules! label_title {
($color:expr) => { ($color:expr) => {

View File

@ -498,12 +498,15 @@ impl FirmwareUI for UIEckhart {
} }
fn show_danger( fn show_danger(
_title: TString<'static>, title: TString<'static>,
_description: TString<'static>, description: TString<'static>,
_value: TString<'static>, value: TString<'static>,
_verb_cancel: Option<TString<'static>>, menu_title: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) let flow =
flow::show_danger::new_show_danger(title, description, value, menu_title, verb_cancel)?;
Ok(flow)
} }
fn show_error( fn show_error(

View File

@ -278,6 +278,7 @@ pub trait FirmwareUI {
title: TString<'static>, title: TString<'static>,
description: TString<'static>, description: TString<'static>,
value: TString<'static>, value: TString<'static>,
menu_title: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>, verb_cancel: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error>; ) -> Result<impl LayoutMaybeTrace, Error>;

View File

@ -491,6 +491,7 @@ def show_danger(
title: str, title: str,
description: str, description: str,
value: str = "", value: str = "",
menu_title: str | None = None,
verb_cancel: str | None = None, verb_cancel: str | None = None,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Warning modal that makes it easier to cancel than to continue.""" """Warning modal that makes it easier to cancel than to continue."""