mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-04-22 01:59:02 +00:00
feat(eckhart): confirm summary flow
This commit is contained in:
parent
6f2f59ddaf
commit
51c917bfed
226
core/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rs
Normal file
226
core/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rs
Normal file
@ -0,0 +1,226 @@
|
||||
use heapless::Vec;
|
||||
|
||||
use crate::{
|
||||
error::{self},
|
||||
strutil::TString,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
|
||||
ComponentExt, MsgMap,
|
||||
},
|
||||
flow::{
|
||||
base::{Decision, DecisionBuilder as _},
|
||||
FlowController, FlowMsg, SwipeFlow,
|
||||
},
|
||||
geometry::{Alignment, Direction, LinearPlacement, Offset},
|
||||
},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::Button,
|
||||
firmware::{
|
||||
ActionBar, Header, Hint, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen,
|
||||
VerticalMenuScreenMsg,
|
||||
},
|
||||
theme,
|
||||
};
|
||||
|
||||
const MENU_ITEM_CANCEL: usize = 0;
|
||||
const MENU_ITEM_EXTRA_INFO: usize = 1;
|
||||
const MENU_ITEM_ACCOUNT_INFO: usize = 2;
|
||||
|
||||
const TIMEOUT_MS: u32 = 2000;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ConfirmSummary {
|
||||
Summary,
|
||||
Menu,
|
||||
ExtraInfo,
|
||||
AccountInfo,
|
||||
Cancel,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl FlowController for ConfirmSummary {
|
||||
#[inline]
|
||||
fn index(&'static self) -> usize {
|
||||
*self as usize
|
||||
}
|
||||
|
||||
fn handle_swipe(&'static self, _direction: Direction) -> Decision {
|
||||
self.do_nothing()
|
||||
}
|
||||
|
||||
fn handle_event(&'static self, msg: FlowMsg) -> Decision {
|
||||
match (self, msg) {
|
||||
(Self::Summary, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
|
||||
(Self::Summary, FlowMsg::Info) => Self::Menu.goto(),
|
||||
(Self::Menu, FlowMsg::Choice(MENU_ITEM_CANCEL)) => Self::Cancel.goto(),
|
||||
(Self::Menu, FlowMsg::Choice(MENU_ITEM_EXTRA_INFO)) => Self::ExtraInfo.goto(),
|
||||
(Self::Menu, FlowMsg::Choice(MENU_ITEM_ACCOUNT_INFO)) => Self::AccountInfo.goto(),
|
||||
(Self::Menu, FlowMsg::Cancelled) => Self::Summary.goto(),
|
||||
(_, FlowMsg::Cancelled) => Self::Menu.goto(),
|
||||
(Self::Cancel, FlowMsg::Confirmed) => Self::Cancelled.goto(),
|
||||
(Self::Cancelled, _) => self.return_msg(FlowMsg::Cancelled),
|
||||
_ => self.do_nothing(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn content_menu_info(
|
||||
title: TString<'static>,
|
||||
subtitle: Option<TString<'static>>,
|
||||
paragraphs: Option<ParagraphVecShort<'static>>,
|
||||
) -> MsgMap<
|
||||
TextScreen<Paragraphs<ParagraphVecShort<'static>>>,
|
||||
impl Fn(TextScreenMsg) -> Option<FlowMsg>,
|
||||
> {
|
||||
TextScreen::new(
|
||||
paragraphs
|
||||
.map_or_else(ParagraphVecShort::new, |p| p)
|
||||
.into_paragraphs()
|
||||
.with_placement(LinearPlacement::vertical())
|
||||
.with_spacing(12),
|
||||
)
|
||||
.with_header(Header::new(title).with_close_button())
|
||||
.with_subtitle(subtitle.unwrap_or(TString::empty()))
|
||||
.map(|_| Some(FlowMsg::Cancelled))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_confirm_summary(
|
||||
title: TString<'static>,
|
||||
amount: TString<'static>,
|
||||
amount_label: TString<'static>,
|
||||
fee: TString<'static>,
|
||||
fee_label: TString<'static>,
|
||||
account_paragraphs: Option<ParagraphVecShort<'static>>,
|
||||
extra_title: Option<TString<'static>>,
|
||||
extra_paragraphs: Option<ParagraphVecShort<'static>>,
|
||||
verb_cancel: Option<TString<'static>>,
|
||||
) -> Result<SwipeFlow, error::Error> {
|
||||
// Summary
|
||||
let summary_paragraphs = ParagraphVecShort::from_iter([
|
||||
Paragraph::new(&theme::TEXT_SMALL_LIGHT, amount_label),
|
||||
Paragraph::new(&theme::TEXT_MONO_MEDIUM_LIGHT, amount),
|
||||
Paragraph::new(&theme::TEXT_SMALL_LIGHT, fee_label),
|
||||
Paragraph::new(&theme::TEXT_MONO_MEDIUM_LIGHT, fee),
|
||||
]);
|
||||
|
||||
let content_summary = TextScreen::new(
|
||||
summary_paragraphs
|
||||
.into_paragraphs()
|
||||
.with_placement(LinearPlacement::vertical())
|
||||
.with_spacing(12),
|
||||
)
|
||||
.with_header(Header::new(title).with_menu_button())
|
||||
.with_action_bar(ActionBar::new_single(
|
||||
Button::with_text(TR::instructions__hold_to_sign.into())
|
||||
.with_long_press(theme::CONFIRM_HOLD_DURATION)
|
||||
.styled(theme::button_confirm()),
|
||||
))
|
||||
.with_hint(Hint::new_instruction(
|
||||
TR::send__send_in_the_app,
|
||||
Some(theme::ICON_INFO),
|
||||
))
|
||||
.map(|msg| match msg {
|
||||
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
|
||||
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
|
||||
TextScreenMsg::Menu => Some(FlowMsg::Info),
|
||||
});
|
||||
|
||||
// Menu
|
||||
let mut menu = VerticalMenu::empty();
|
||||
let mut menu_items = Vec::<usize, 3>::new();
|
||||
|
||||
if account_paragraphs.is_some() {
|
||||
menu = menu.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)),
|
||||
);
|
||||
unwrap!(menu_items.push(MENU_ITEM_ACCOUNT_INFO));
|
||||
}
|
||||
if extra_paragraphs.is_some() {
|
||||
menu = menu.item(
|
||||
Button::with_text(extra_title.unwrap_or(TR::buttons__more_info.into()))
|
||||
.styled(theme::menu_item_title())
|
||||
.with_text_align(Alignment::Start)
|
||||
.with_content_offset(Offset::x(12)),
|
||||
);
|
||||
unwrap!(menu_items.push(MENU_ITEM_EXTRA_INFO));
|
||||
}
|
||||
menu = menu.item(
|
||||
Button::with_text(verb_cancel.unwrap_or(TR::buttons__cancel.into()))
|
||||
.styled(theme::menu_item_title_orange())
|
||||
.with_text_align(Alignment::Start)
|
||||
.with_content_offset(Offset::x(12)),
|
||||
);
|
||||
unwrap!(menu_items.push(MENU_ITEM_CANCEL));
|
||||
let content_menu = VerticalMenuScreen::new(menu)
|
||||
.with_header(Header::new(title).with_close_button())
|
||||
.map(move |msg| match msg {
|
||||
VerticalMenuScreenMsg::Selected(i) => {
|
||||
let selected_item = menu_items[i];
|
||||
Some(FlowMsg::Choice(selected_item))
|
||||
}
|
||||
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// ExtraInfo
|
||||
let content_extra = content_menu_info(
|
||||
extra_title.unwrap_or(TR::buttons__more_info.into()),
|
||||
None,
|
||||
extra_paragraphs,
|
||||
);
|
||||
|
||||
// AccountInfo
|
||||
let content_account = content_menu_info(
|
||||
TR::address_details__account_info.into(),
|
||||
Some(TR::send__send_from.into()),
|
||||
account_paragraphs,
|
||||
);
|
||||
|
||||
// Cancel
|
||||
let content_cancel = TextScreen::new(
|
||||
Paragraph::new(&theme::TEXT_REGULAR, TR::send__cancel_sign)
|
||||
.into_paragraphs()
|
||||
.with_placement(LinearPlacement::vertical()),
|
||||
)
|
||||
.with_header(Header::new(TR::words__send.into()))
|
||||
.with_action_bar(ActionBar::new_double(
|
||||
Button::with_icon(theme::ICON_CHEVRON_LEFT),
|
||||
Button::with_text(TR::buttons__cancel.into()).styled(theme::button_cancel()),
|
||||
))
|
||||
.map(|msg| match msg {
|
||||
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
|
||||
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// Cancelled
|
||||
let content_cancelled = TextScreen::new(
|
||||
Paragraph::new(&theme::TEXT_REGULAR, TR::send__sign_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(&ConfirmSummary::Summary)?;
|
||||
res.add_page(&ConfirmSummary::Summary, content_summary)?
|
||||
.add_page(&ConfirmSummary::Menu, content_menu)?
|
||||
.add_page(&ConfirmSummary::ExtraInfo, content_extra)?
|
||||
.add_page(&ConfirmSummary::AccountInfo, content_account)?
|
||||
.add_page(&ConfirmSummary::Cancel, content_cancel)?
|
||||
.add_page(&ConfirmSummary::Cancelled, content_cancelled)?;
|
||||
|
||||
Ok(res)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod confirm_reset;
|
||||
pub mod confirm_set_new_pin;
|
||||
pub mod confirm_summary;
|
||||
pub mod continue_recovery_homepage;
|
||||
pub mod get_address;
|
||||
pub mod prompt_backup;
|
||||
@ -9,6 +10,7 @@ pub mod show_share_words;
|
||||
|
||||
pub use confirm_reset::new_confirm_reset;
|
||||
pub use confirm_set_new_pin::new_set_new_pin;
|
||||
pub use confirm_summary::new_confirm_summary;
|
||||
pub use continue_recovery_homepage::new_continue_recovery_homepage;
|
||||
pub use get_address::GetAddress;
|
||||
pub use prompt_backup::PromptBackup;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use core::cmp::Ordering;
|
||||
use heapless::Vec;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
@ -217,17 +218,52 @@ impl FirmwareUI for UIEckhart {
|
||||
}
|
||||
|
||||
fn confirm_summary(
|
||||
_amount: TString<'static>,
|
||||
_amount_label: TString<'static>,
|
||||
_fee: TString<'static>,
|
||||
_fee_label: TString<'static>,
|
||||
_title: Option<TString<'static>>,
|
||||
_account_items: Option<Obj>,
|
||||
_extra_items: Option<Obj>,
|
||||
_extra_title: Option<TString<'static>>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
amount: TString<'static>,
|
||||
amount_label: TString<'static>,
|
||||
fee: TString<'static>,
|
||||
fee_label: TString<'static>,
|
||||
title: Option<TString<'static>>,
|
||||
account_items: Option<Obj>,
|
||||
extra_items: Option<Obj>,
|
||||
extra_title: Option<TString<'static>>,
|
||||
verb_cancel: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
// collect available info
|
||||
let account_paragraphs = if let Some(items) = account_items {
|
||||
let mut paragraphs = ParagraphVecShort::new();
|
||||
for pair in IterBuf::new().try_iterate(items)? {
|
||||
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_LIGHT, value)));
|
||||
}
|
||||
Some(paragraphs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let extra_paragraphs = if let Some(items) = extra_items {
|
||||
let mut paragraphs = ParagraphVecShort::new();
|
||||
for pair in IterBuf::new().try_iterate(items)? {
|
||||
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_LIGHT, value)));
|
||||
}
|
||||
Some(paragraphs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let flow = flow::new_confirm_summary(
|
||||
title.unwrap_or(TString::empty()),
|
||||
amount,
|
||||
amount_label,
|
||||
fee,
|
||||
fee_label,
|
||||
account_paragraphs,
|
||||
extra_title,
|
||||
extra_paragraphs,
|
||||
verb_cancel,
|
||||
)?;
|
||||
Ok(flow)
|
||||
}
|
||||
|
||||
fn confirm_properties(
|
||||
|
@ -677,14 +677,14 @@ def confirm_total(
|
||||
br_name: str = "confirm_total",
|
||||
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
||||
) -> Awaitable[None]:
|
||||
title = title or TR.words__title_summary # def_arg
|
||||
title = title or TR.words__send # def_arg
|
||||
total_label = total_label or TR.send__total_amount # def_arg
|
||||
fee_label = fee_label or TR.send__incl_transaction_fee # def_arg
|
||||
|
||||
fee_items = []
|
||||
account_items = []
|
||||
if source_account:
|
||||
account_items.append((TR.confirm_total__sending_from_account, source_account))
|
||||
account_items.append((TR.words__account, source_account))
|
||||
if source_account_path:
|
||||
account_items.append((TR.address_details__derivation_path, source_account_path))
|
||||
if fee_rate_amount:
|
||||
@ -718,7 +718,7 @@ def _confirm_summary(
|
||||
br_name: str = "confirm_total",
|
||||
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
||||
) -> Awaitable[None]:
|
||||
title = title or TR.words__title_summary # def_arg
|
||||
title = title or TR.words__send # def_arg
|
||||
|
||||
return raise_if_not_confirmed(
|
||||
trezorui_api.confirm_summary(
|
||||
|
Loading…
Reference in New Issue
Block a user