1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-05-24 09:48:45 +00:00

chore(eckhart): update show_mismatch, confirm_output and confirm_value and progress_screen functions

This commit is contained in:
Lukas Bielesch 2025-04-04 16:49:05 +02:00 committed by obrusvit
parent ae199b98a8
commit 11fb6f3e90
4 changed files with 137 additions and 66 deletions

View File

@ -168,7 +168,7 @@ fn render_percentage<'s>(progress: u16, target: &mut impl Renderer<'s>) {
impl crate::trace::Trace for ProgressScreen { impl crate::trace::Trace for ProgressScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
if self.coinjoin_progress { if self.coinjoin_progress {
t.component("CoinjoinProgress"); t.component("CoinJoinProgress");
} else { } else {
t.component("Progress"); t.component("Progress");
} }

View File

@ -16,6 +16,7 @@ use crate::{
FlowController, FlowMsg, SwipeFlow, FlowController, FlowMsg, SwipeFlow,
}, },
geometry::{Alignment, Direction, LinearPlacement, Offset}, geometry::{Alignment, Direction, LinearPlacement, Offset},
layout::util::StrOrBytes,
}, },
}; };
@ -134,7 +135,7 @@ pub enum ConfirmOutputWithSummary {
SummaryMenu, SummaryMenu,
SummaryMenuCancel, SummaryMenuCancel,
SummaryMenuFeeInfo, SummaryMenuFeeInfo,
SummaryMenuAccountInfo, SummaryMenuExtraInfo,
Cancelled, Cancelled,
} }
@ -175,12 +176,12 @@ impl FlowController for ConfirmOutputWithSummary {
Self::SummaryMenuFeeInfo.goto() Self::SummaryMenuFeeInfo.goto()
} }
(Self::SummaryMenu, FlowMsg::Choice(MENU_ITEM_EXTRA_INFO)) => { (Self::SummaryMenu, FlowMsg::Choice(MENU_ITEM_EXTRA_INFO)) => {
Self::SummaryMenuAccountInfo.goto() Self::SummaryMenuExtraInfo.goto()
} }
(Self::SummaryMenu, FlowMsg::Cancelled) => Self::Summary.goto(), (Self::SummaryMenu, FlowMsg::Cancelled) => Self::Summary.goto(),
(Self::SummaryMenuCancel, FlowMsg::Cancelled) => Self::SummaryMenu.goto(), (Self::SummaryMenuCancel, FlowMsg::Cancelled) => Self::SummaryMenu.goto(),
(Self::SummaryMenuCancel, FlowMsg::Confirmed) => Self::Cancelled.goto(), (Self::SummaryMenuCancel, FlowMsg::Confirmed) => Self::Cancelled.goto(),
(Self::SummaryMenuAccountInfo | Self::SummaryMenuFeeInfo, FlowMsg::Cancelled) => { (Self::SummaryMenuExtraInfo | Self::SummaryMenuFeeInfo, FlowMsg::Cancelled) => {
Self::SummaryMenu.goto() Self::SummaryMenu.goto()
} }
(Self::Cancelled, _) => self.return_msg(FlowMsg::Cancelled), (Self::Cancelled, _) => self.return_msg(FlowMsg::Cancelled),
@ -220,7 +221,8 @@ fn content_main_menu(
main_menu.item( main_menu.item(
Button::with_text(address_title) Button::with_text(address_title)
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start), .with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
); );
unwrap!(main_menu_items.push(MENU_ITEM_ADDRESS_INFO)); unwrap!(main_menu_items.push(MENU_ITEM_ADDRESS_INFO));
} }
@ -277,42 +279,37 @@ fn content_menu_info(
pub fn new_confirm_output( pub fn new_confirm_output(
title: Option<TString<'static>>, title: Option<TString<'static>>,
subtitle: Option<TString<'static>>, subtitle: Option<TString<'static>>,
chunkify: bool, main_paragraphs: ParagraphVecShort<'static>,
message: Obj,
amount: Option<Obj>, amount: Option<Obj>,
br_name: TString<'static>, br_name: TString<'static>,
br_code: u16, br_code: u16,
account_title: TString<'static>, account_title: TString<'static>,
account_paragraphs: Option<ParagraphVecShort<'static>>, account_paragraphs: Option<ParagraphVecShort<'static>>,
address_title: Option<TString<'static>>, address_title: Option<TString<'static>>,
address_paragraphs: Option<ParagraphVecShort<'static>>, address_paragraph: Option<Paragraph<'static>>,
summary_title: Option<TString<'static>>, summary_title: Option<TString<'static>>,
summary_paragraphs: Option<ParagraphVecShort<'static>>, summary_paragraphs: Option<ParagraphVecShort<'static>>,
summary_br_code: Option<u16>, summary_br_code: Option<u16>,
summary_br_name: Option<TString<'static>>, summary_br_name: Option<TString<'static>>,
fee_params: Option<ParagraphVecShort<'static>>, extra_title: Option<TString<'static>>,
extra_paragraph: Option<Paragraph<'static>>,
fee_paragraphs: Option<ParagraphVecShort<'static>>,
cancel_menu_label: Option<TString<'static>>, cancel_menu_label: Option<TString<'static>>,
) -> Result<SwipeFlow, error::Error> { ) -> Result<SwipeFlow, error::Error> {
let cancel_menu_label = cancel_menu_label.unwrap_or(TR::buttons__cancel.into()); let cancel_menu_label = cancel_menu_label.unwrap_or(TR::buttons__cancel.into());
let address_menu_item = address_paragraphs.is_some(); let address_menu_item = address_paragraph.is_some();
let account_menu_item = account_paragraphs.is_some(); let account_menu_item = account_paragraphs.is_some();
let fee_menu_item = fee_params.is_some(); let fee_menu_item = fee_paragraphs.is_some();
let extra_menu_item = extra_paragraph.is_some();
let address_title = address_title.unwrap_or(TR::words__address.into()); let address_title = address_title.unwrap_or(TR::words__address.into());
let account_subtitle = Some(TR::send__send_from.into()); let account_subtitle = Some(TR::send__send_from.into());
// Main // Main
let main_paragraphs = Paragraph::new(
if chunkify {
&theme::TEXT_MONO_ADDRESS_CHUNKS
} else {
&theme::TEXT_MONO_LIGHT
},
message.try_into().unwrap_or(TString::empty()),
);
let content_main = TextScreen::new( let content_main = TextScreen::new(
main_paragraphs main_paragraphs
.into_paragraphs() .into_paragraphs()
.with_placement(LinearPlacement::vertical()), .with_placement(LinearPlacement::vertical())
.with_spacing(12),
) )
.with_header(Header::new(title.unwrap_or(TString::empty())).with_menu_button()) .with_header(Header::new(title.unwrap_or(TString::empty())).with_menu_button())
.with_action_bar(ActionBar::new_single(Button::with_text( .with_action_bar(ActionBar::new_single(Button::with_text(
@ -324,8 +321,7 @@ pub fn new_confirm_output(
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled), TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
TextScreenMsg::Menu => Some(FlowMsg::Info), TextScreenMsg::Menu => Some(FlowMsg::Info),
}) })
.one_button_request(ButtonRequest::from_num(br_code, br_name)) .one_button_request(ButtonRequest::from_num(br_code, br_name));
.with_pages(|_| 1);
// Cancelled // Cancelled
let content_cancelled = TextScreen::new( let content_cancelled = TextScreen::new(
@ -345,7 +341,10 @@ pub fn new_confirm_output(
Paragraph::new(&theme::TEXT_SMALL_LIGHT, TR::words__amount).no_break(), Paragraph::new(&theme::TEXT_SMALL_LIGHT, TR::words__amount).no_break(),
Paragraph::new( Paragraph::new(
&theme::TEXT_MONO_MEDIUM_LIGHT, &theme::TEXT_MONO_MEDIUM_LIGHT,
amount.try_into().unwrap_or(TString::empty()), amount
.try_into()
.unwrap_or(StrOrBytes::Str("".into()))
.as_str_offset(0),
), ),
]); ]);
@ -365,8 +364,7 @@ pub fn new_confirm_output(
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled), TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
TextScreenMsg::Menu => Some(FlowMsg::Info), TextScreenMsg::Menu => Some(FlowMsg::Info),
}) })
.one_button_request(ButtonRequest::from_num(br_code, br_name)) .one_button_request(ButtonRequest::from_num(br_code, br_name));
.with_pages(|_| 1);
let mut flow = SwipeFlow::new(&ConfirmOutputWithAmount::Address)?; let mut flow = SwipeFlow::new(&ConfirmOutputWithAmount::Address)?;
flow.add_page(&ConfirmOutputWithAmount::Address, content_main)? flow.add_page(&ConfirmOutputWithAmount::Address, content_main)?
@ -420,6 +418,7 @@ pub fn new_confirm_output(
.with_action_bar(ActionBar::new_double( .with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CHEVRON_UP), Button::with_icon(theme::ICON_CHEVRON_UP),
Button::with_text(TR::instructions__hold_to_sign.into()) Button::with_text(TR::instructions__hold_to_sign.into())
.with_long_press(theme::CONFIRM_HOLD_DURATION)
.styled(theme::button_confirm()), .styled(theme::button_confirm()),
)) ))
.map(|msg| match msg { .map(|msg| match msg {
@ -430,22 +429,36 @@ pub fn new_confirm_output(
.one_button_request(ButtonRequest::from_num( .one_button_request(ButtonRequest::from_num(
summary_br_code.unwrap(), summary_br_code.unwrap(),
summary_br_name.unwrap(), summary_br_name.unwrap(),
)) ));
.with_pages(|_| 1);
// SummaryMenu // SummaryMenu
let mut summary_menu = VerticalMenu::<ShortMenuVec>::empty(); let mut summary_menu = VerticalMenu::<ShortMenuVec>::empty();
let mut summary_menu_items = Vec::<usize, 3>::new(); let mut summary_menu_items = Vec::<usize, 3>::new();
if account_menu_item {
summary_menu.item(Button::with_text(account_title)); if extra_menu_item {
summary_menu.item(
Button::with_text(extra_title.unwrap_or(TString::empty()))
.styled(theme::menu_item_title())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
);
unwrap!(summary_menu_items.push(MENU_ITEM_EXTRA_INFO)); unwrap!(summary_menu_items.push(MENU_ITEM_EXTRA_INFO));
} }
if fee_menu_item { if fee_menu_item {
summary_menu.item(Button::with_text(TR::confirm_total__title_fee.into())); summary_menu.item(
Button::with_text(TR::confirm_total__title_fee.into())
.styled(theme::menu_item_title())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
);
unwrap!(summary_menu_items.push(MENU_ITEM_FEE_INFO)); unwrap!(summary_menu_items.push(MENU_ITEM_FEE_INFO));
} }
summary_menu summary_menu.item(
.item(Button::with_text(cancel_menu_label).styled(theme::menu_item_title_orange())); Button::with_text(cancel_menu_label)
.styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)),
);
unwrap!(summary_menu_items.push(MENU_ITEM_CANCEL)); unwrap!(summary_menu_items.push(MENU_ITEM_CANCEL));
let content_summary_menu = VerticalMenuScreen::new(summary_menu) let content_summary_menu = VerticalMenuScreen::new(summary_menu)
.with_header(Header::new(TString::empty()).with_close_button()) .with_header(Header::new(TString::empty()).with_close_button())
@ -472,7 +485,12 @@ pub fn new_confirm_output(
.add_page(&ConfirmOutputWithSummary::MainMenuCancel, content_cancel())? .add_page(&ConfirmOutputWithSummary::MainMenuCancel, content_cancel())?
.add_page( .add_page(
&ConfirmOutputWithSummary::MainMenuAddresInfo, &ConfirmOutputWithSummary::MainMenuAddresInfo,
content_menu_info(address_title, None, address_paragraphs), content_menu_info(
address_title,
None,
address_paragraph
.map(|address_paragraph| ParagraphVecShort::from_iter([address_paragraph])),
),
)? )?
.add_page( .add_page(
&ConfirmOutputWithSummary::MainMenuAccountInfo, &ConfirmOutputWithSummary::MainMenuAccountInfo,
@ -486,11 +504,16 @@ pub fn new_confirm_output(
)? )?
.add_page( .add_page(
&ConfirmOutputWithSummary::SummaryMenuFeeInfo, &ConfirmOutputWithSummary::SummaryMenuFeeInfo,
content_menu_info(TR::confirm_total__title_fee.into(), None, fee_params), content_menu_info(TR::confirm_total__title_fee.into(), None, fee_paragraphs),
)? )?
.add_page( .add_page(
&ConfirmOutputWithSummary::SummaryMenuAccountInfo, &ConfirmOutputWithSummary::SummaryMenuExtraInfo,
content_menu_info(account_title, account_subtitle, account_paragraphs), content_menu_info(
extra_title.unwrap_or(TString::empty()),
None,
extra_paragraph
.map(|extra_paragraph| ParagraphVecShort::from_iter([extra_paragraph])),
),
)? )?
.add_page(&ConfirmOutputWithSummary::Cancelled, content_cancelled)?; .add_page(&ConfirmOutputWithSummary::Cancelled, content_cancelled)?;
flow flow

View File

@ -368,7 +368,7 @@ impl FirmwareUI for UIEckhart {
description: Option<TString<'static>>, description: Option<TString<'static>>,
is_data: bool, is_data: bool,
extra: Option<TString<'static>>, extra: Option<TString<'static>>,
_subtitle: Option<TString<'static>>, subtitle: Option<TString<'static>>,
verb: Option<TString<'static>>, verb: Option<TString<'static>>,
_verb_cancel: Option<TString<'static>>, _verb_cancel: Option<TString<'static>>,
info: bool, info: bool,
@ -390,21 +390,29 @@ impl FirmwareUI for UIEckhart {
let value: TString = value.try_into()?; let value: TString = value.try_into()?;
theme::get_chunkified_text_style(value.len()) theme::get_chunkified_text_style(value.len())
} else if is_data { } else if is_data {
&theme::TEXT_MONO_MEDIUM &theme::TEXT_MONO_ADDRESS
} else { } else {
&theme::TEXT_MEDIUM &theme::TEXT_MEDIUM
}, },
description_font: &theme::TEXT_SMALL, description_font: &theme::TEXT_SMALL,
extra_font: &theme::TEXT_SMALL, extra_font: &theme::TEXT_SMALL,
} }
.into_paragraphs(); .into_paragraphs()
.with_placement(LinearPlacement::vertical());
let verb = verb.unwrap_or(TR::buttons__confirm.into()); let mut right_button = if hold {
let right_button = if hold { let verb = verb.unwrap_or(TR::buttons__hold_to_confirm.into());
Button::with_text(verb).with_long_press(theme::CONFIRM_HOLD_DURATION)
} else {
Button::with_text(verb) Button::with_text(verb)
.with_long_press(theme::CONFIRM_HOLD_DURATION)
.styled(theme::firmware::button_confirm())
} else if let Some(verb) = verb {
Button::with_text(verb)
} else {
Button::with_text(TR::buttons__confirm.into()).styled(theme::firmware::button_confirm())
}; };
if warning_footer.is_some() {
right_button = right_button.styled(theme::button_cancel_gradient());
}
let header = if info { let header = if info {
Header::new(title) Header::new(title)
.with_right_button(Button::with_icon(theme::ICON_INFO), HeaderMsg::Menu) .with_right_button(Button::with_icon(theme::ICON_INFO), HeaderMsg::Menu)
@ -414,6 +422,7 @@ impl FirmwareUI for UIEckhart {
let mut screen = TextScreen::new(paragraphs) let mut screen = TextScreen::new(paragraphs)
.with_header(header) .with_header(header)
.with_subtitle(subtitle.unwrap_or(TString::empty()))
.with_action_bar(ActionBar::new_double( .with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CROSS), Button::with_icon(theme::ICON_CROSS),
right_button, right_button,
@ -523,19 +532,19 @@ impl FirmwareUI for UIEckhart {
fn flow_confirm_output( fn flow_confirm_output(
title: Option<TString<'static>>, title: Option<TString<'static>>,
subtitle: Option<TString<'static>>, subtitle: Option<TString<'static>>,
_description: Option<TString<'static>>, description: Option<TString<'static>>,
_extra: Option<TString<'static>>, extra: Option<TString<'static>>,
message: Obj, message: Obj,
amount: Option<Obj>, amount: Option<Obj>,
chunkify: bool, chunkify: bool,
_text_mono: bool, text_mono: bool,
account_title: TString<'static>, account_title: TString<'static>,
account: Option<TString<'static>>, account: Option<TString<'static>>,
account_path: Option<TString<'static>>, account_path: Option<TString<'static>>,
br_code: u16, br_code: u16,
br_name: TString<'static>, br_name: TString<'static>,
address_item: Option<(TString<'static>, Obj)>, address_item: Option<(TString<'static>, Obj)>,
_extra_item: Option<(TString<'static>, Obj)>, extra_item: Option<(TString<'static>, Obj)>,
summary_items: Option<Obj>, summary_items: Option<Obj>,
fee_items: Option<Obj>, fee_items: Option<Obj>,
summary_title: Option<TString<'static>>, summary_title: Option<TString<'static>>,
@ -543,14 +552,36 @@ impl FirmwareUI for UIEckhart {
summary_br_name: Option<TString<'static>>, summary_br_name: Option<TString<'static>>,
cancel_text: Option<TString<'static>>, cancel_text: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
let (address_title, address_paragraphs) = if let Some(address_item) = address_item { let mut main_paragraphs = ParagraphVecShort::new();
let mut paragraphs = ParagraphVecShort::new(); if let Some(description) = description {
for pair in IterBuf::new().try_iterate(address_item.1)? { unwrap!(main_paragraphs.push(Paragraph::new(&theme::TEXT_NORMAL, description)));
let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
unwrap!(paragraphs.push(Paragraph::new(&theme::TEXT_SMALL_LIGHT, label).no_break()));
unwrap!(paragraphs.push(Paragraph::new(&theme::TEXT_MONO_MEDIUM_LIGHT, value)));
} }
(Some(address_item.0), Some(paragraphs)) if let Some(extra) = extra {
unwrap!(main_paragraphs.push(Paragraph::new(&theme::TEXT_SMALL, extra)));
}
let font = if chunkify {
&theme::TEXT_MONO_ADDRESS_CHUNKS
} else if text_mono {
&theme::TEXT_MONO_LIGHT
} else {
&theme::TEXT_MEDIUM
};
unwrap!(main_paragraphs.push(Paragraph::new(
font,
message
.try_into()
.unwrap_or(StrOrBytes::Str("".into()))
.as_str_offset(0),
)));
let (address_title, address_paragraph) = if let Some((title, item)) = address_item {
let paragraph = Paragraph::new(
&theme::TEXT_MONO_ADDRESS_CHUNKS,
item.try_into()
.unwrap_or(StrOrBytes::Str("".into()))
.as_str_offset(0),
);
(Some(title), Some(paragraph))
} else { } else {
(None, None) (None, None)
}; };
@ -609,22 +640,35 @@ impl FirmwareUI for UIEckhart {
None None
}; };
let (extra_title, extra_paragraph) = if let Some((title, item)) = extra_item {
let paragraph = Paragraph::new(
&theme::TEXT_MONO_ADDRESS,
item.try_into()
.unwrap_or(StrOrBytes::Str("".into()))
.as_str_offset(0),
);
(Some(title), Some(paragraph))
} else {
(None, None)
};
let flow = flow::confirm_output::new_confirm_output( let flow = flow::confirm_output::new_confirm_output(
title, title,
subtitle, subtitle,
chunkify, main_paragraphs,
message,
amount, amount,
br_name, br_name,
br_code, br_code,
account_title, account_title,
account_paragraphs, account_paragraphs,
address_title, address_title,
address_paragraphs, address_paragraph,
summary_title, summary_title,
summary_paragraphs, summary_paragraphs,
summary_br_code, summary_br_code,
summary_br_name, summary_br_name,
extra_title,
extra_paragraph,
fee_paragraphs, fee_paragraphs,
cancel_text, cancel_text,
)?; )?;
@ -1049,14 +1093,18 @@ impl FirmwareUI for UIEckhart {
let url: TString = TR::addr_mismatch__support_url.into(); let url: TString = TR::addr_mismatch__support_url.into();
let button: TString = TR::buttons__quit.into(); let button: TString = TR::buttons__quit.into();
let paragraphs = ParagraphVecShort::from_iter([ let text_style = theme::TEXT_REGULAR;
Paragraph::new(&theme::TEXT_REGULAR, description).centered(), let ops = OpTextLayout::new(text_style)
Paragraph::new(&theme::TEXT_MONO_MEDIUM, url).centered(), .text(description, text_style.text_font)
]) .text(url, theme::TEXT_MONO_MEDIUM.text_font);
.into_paragraphs(); let text = FormattedText::new(ops);
let screen = TextScreen::new(paragraphs)
let screen = TextScreen::new(text)
.with_header(Header::new(title)) .with_header(Header::new(title))
.with_action_bar(ActionBar::new_single(Button::with_text(button))); .with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CROSS),
Button::with_text(button),
));
let layout = RootComponent::new(screen); let layout = RootComponent::new(screen);
Ok(layout) Ok(layout)

View File

@ -582,7 +582,7 @@ def confirm_address(
br_name, br_name,
br_code, br_code,
subtitle=subtitle, subtitle=subtitle,
verb=(verb or TR.buttons__confirm), verb=verb,
chunkify=chunkify, chunkify=chunkify,
) )