1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-05-09 10:28:46 +00:00

feat(eckhart): update request number trait function

add request number flow instead of screen only
add updatable info screen to show extra information
This commit is contained in:
Lukas Bielesch 2025-04-07 13:00:49 +02:00
parent 9c1203522c
commit a606b1374b
7 changed files with 276 additions and 25 deletions

View File

@ -15,10 +15,9 @@ use crate::{
use super::firmware::{
AllowedTextContent, ConfirmHomescreen, ConfirmHomescreenMsg, DeviceMenuMsg, DeviceMenuScreen,
Homescreen, HomescreenMsg, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg,
NumberInputScreen, NumberInputScreenMsg, PinKeyboard, PinKeyboardMsg, SelectWordCountMsg,
SelectWordCountScreen, SelectWordMsg, SelectWordScreen, SetBrightnessScreen, TextScreen,
TextScreenMsg,
Homescreen, HomescreenMsg, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PinKeyboard,
PinKeyboardMsg, SelectWordCountMsg, SelectWordCountScreen, SelectWordMsg, SelectWordScreen,
SetBrightnessScreen, TextScreen, TextScreenMsg,
};
impl ComponentMsgObj for PinKeyboard<'_> {
@ -111,16 +110,6 @@ impl ComponentMsgObj for SelectWordCountScreen {
}
}
impl ComponentMsgObj for NumberInputScreen {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
NumberInputScreenMsg::Confirmed(i) => i.try_into(),
NumberInputScreenMsg::Cancelled => Ok(CANCELLED.as_obj()),
NumberInputScreenMsg::Menu => Ok(INFO.as_obj()),
}
}
}
impl ComponentMsgObj for ConfirmHomescreen {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {

View File

@ -12,6 +12,7 @@ mod qr_screen;
mod select_word_screen;
mod share_words;
mod text_screen;
mod updatable_info_screen;
mod vertical_menu;
mod vertical_menu_screen;
@ -36,6 +37,7 @@ pub use qr_screen::{QrMsg, QrScreen};
pub use select_word_screen::{SelectWordMsg, SelectWordScreen};
pub use share_words::{ShareWordsScreen, ShareWordsScreenMsg};
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
pub use updatable_info_screen::UpdatableInfoScreen;
pub use vertical_menu::{VerticalMenu, VerticalMenuMsg, MENU_MAX_ITEMS};
pub use vertical_menu_screen::{VerticalMenuScreen, VerticalMenuScreenMsg};

View File

@ -2,9 +2,11 @@ use crate::{
strutil::{self, TString},
translations::TR,
ui::{
component::{Component, Event, EventCtx, Label, Maybe, Never},
component::{swipe_detect::SwipeConfig, Component, Event, EventCtx, Label, Maybe},
flow::Swipable,
geometry::{Alignment, Insets, Offset, Rect},
shape::{self, Renderer},
util::Pager,
},
};
@ -20,6 +22,7 @@ use super::{
pub enum NumberInputScreenMsg {
Cancelled,
Confirmed(u32),
Changed(u32),
Menu,
}
@ -87,7 +90,7 @@ impl Component for NumberInputScreen {
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.number_input.event(ctx, event);
let changed = self.number_input.event(ctx, event);
if let Some(HeaderMsg::Menu) = self.header.event(ctx, event) {
return Some(NumberInputScreenMsg::Menu);
@ -103,6 +106,10 @@ impl Component for NumberInputScreen {
}
}
if let Some(NumberInputMsg::Changed(value)) = changed {
return Some(NumberInputScreenMsg::Changed(value));
}
None
}
@ -114,6 +121,15 @@ impl Component for NumberInputScreen {
}
}
impl Swipable for NumberInputScreen {
fn get_pager(&self) -> Pager {
Pager::single_page()
}
fn get_swipe_config(&self) -> SwipeConfig {
SwipeConfig::default()
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for NumberInputScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
@ -132,6 +148,10 @@ struct NumberInput {
value: u32,
}
pub enum NumberInputMsg {
Changed(u32),
}
impl NumberInput {
const BUTTON_PADDING: i16 = 10;
const BUTTON_SIZE: Offset = Offset::new(138, 130);
@ -204,7 +224,7 @@ impl NumberInput {
}
impl Component for NumberInput {
type Msg = Never;
type Msg = NumberInputMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
@ -231,12 +251,19 @@ impl Component for NumberInput {
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let mut changed = false;
if let Some(ButtonMsg::Clicked) = self.dec.event(ctx, event) {
self.decrease(ctx);
changed = true;
};
if let Some(ButtonMsg::Clicked) = self.inc.event(ctx, event) {
self.increase(ctx);
changed = true;
};
if changed {
ctx.request_paint();
return Some(NumberInputMsg::Changed(self.value));
}
None
}

View File

@ -0,0 +1,105 @@
use crate::{
strutil::TString,
translations::TR,
ui::{
component::{
swipe_detect::SwipeConfig,
text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
Component, Event, EventCtx,
},
flow::Swipable,
geometry::{LinearPlacement, Rect},
shape::Renderer,
util::Pager,
},
};
use super::{theme, Header, TextScreen, TextScreenMsg};
pub struct UpdatableInfoScreen<F>
where
F: Fn() -> TString<'static>,
{
info_func: F,
paragraphs: Paragraphs<Paragraph<'static>>,
area: Rect,
text_screen: TextScreen<Paragraphs<Paragraph<'static>>>,
}
impl<F> UpdatableInfoScreen<F>
where
F: Fn() -> TString<'static>,
{
pub fn new(info_func: F) -> Self {
let paragraphs = Paragraph::new(&theme::TEXT_REGULAR, TString::empty())
.into_paragraphs()
.with_placement(LinearPlacement::vertical());
let text_screen = create_text_screen(paragraphs.clone());
Self {
info_func,
paragraphs,
area: Rect::zero(),
text_screen,
}
}
fn update_text(&mut self, ctx: &mut EventCtx) {
let text = (self.info_func)();
self.paragraphs.update(text);
self.text_screen = create_text_screen(self.paragraphs.clone());
self.text_screen.place(self.area);
ctx.request_paint();
}
}
impl<F> Component for UpdatableInfoScreen<F>
where
F: Fn() -> TString<'static>,
{
type Msg = TextScreenMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.text_screen.place(bounds);
self.area = bounds;
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Attach(_) = event {
self.update_text(ctx);
}
self.text_screen.event(ctx, event)
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.text_screen.render(target);
}
}
fn create_text_screen(
paragraphs: Paragraphs<Paragraph<'static>>,
) -> TextScreen<Paragraphs<Paragraph<'static>>> {
TextScreen::new(paragraphs)
.with_header(Header::new(TR::buttons__more_info.into()).with_close_button())
}
impl<F: Fn() -> TString<'static>> Swipable for UpdatableInfoScreen<F> {
fn get_pager(&self) -> Pager {
Pager::single_page()
}
fn get_swipe_config(&self) -> SwipeConfig {
SwipeConfig::default()
}
}
#[cfg(feature = "ui_debug")]
impl<F> crate::trace::Trace for UpdatableInfoScreen<F>
where
F: Fn() -> TString<'static>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("UpdatableInfoScreen");
t.child("screen", &self.text_screen);
}
}

View File

@ -5,6 +5,7 @@ pub mod confirm_summary;
pub mod continue_recovery_homepage;
pub mod get_address;
pub mod prompt_backup;
pub mod request_number;
pub mod request_passphrase;
pub mod show_danger;
pub mod show_share_words;
@ -16,6 +17,7 @@ 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;
pub use request_number::new_request_number;
pub use request_passphrase::RequestPassphrase;
pub use show_danger::ShowDanger;
pub use show_share_words::new_show_share_words_flow;

View File

@ -0,0 +1,122 @@
use crate::{
error,
strutil::TString,
translations::TR,
ui::{
component::ComponentExt,
flow::{
base::{Decision, DecisionBuilder as _},
FlowController, FlowMsg, SwipeFlow,
},
geometry::{Alignment, Direction, Offset},
},
};
use core::sync::atomic::{AtomicU16, Ordering};
use super::super::{
component::Button,
firmware::{
Header, NumberInputScreen, NumberInputScreenMsg, TextScreenMsg, UpdatableInfoScreen,
VerticalMenu, VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme,
};
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum RequestNumber {
Number,
Menu,
Info,
}
impl FlowController for RequestNumber {
#[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::Number, FlowMsg::Info) => Self::Menu.goto(),
(Self::Menu, FlowMsg::Choice(0)) => Self::Info.goto(),
(Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Cancelled),
(Self::Menu, FlowMsg::Cancelled) => Self::Number.goto(),
(Self::Info, FlowMsg::Cancelled) => Self::Menu.goto(),
(Self::Number, FlowMsg::Choice(n)) => self.return_msg(FlowMsg::Choice(n)),
_ => self.do_nothing(),
}
}
}
static NUM_DISPLAYED: AtomicU16 = AtomicU16::new(0);
#[allow(clippy::too_many_arguments)]
pub fn new_request_number(
title: TString<'static>,
count: u32,
min_count: u32,
max_count: u32,
description: TString<'static>,
info_closure: impl Fn(u32) -> TString<'static> + 'static,
) -> Result<SwipeFlow, error::Error> {
NUM_DISPLAYED.store(count as u16, Ordering::Relaxed);
// wrap the closure for obtaining MoreInfo text and call it with NUM_DISPLAYED
let info_closure = move || {
let curr_number = NUM_DISPLAYED.load(Ordering::Relaxed);
info_closure(curr_number as u32)
};
let content_input = NumberInputScreen::new(min_count, max_count, count, description)
.with_header(Header::new(title).with_menu_button())
.map(|msg| match msg {
NumberInputScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
NumberInputScreenMsg::Confirmed(n) => {
NUM_DISPLAYED.store(n as u16, Ordering::Relaxed);
Some(FlowMsg::Choice(n as usize))
}
NumberInputScreenMsg::Changed(n) => {
NUM_DISPLAYED.store(n as u16, Ordering::Relaxed);
None
}
NumberInputScreenMsg::Menu => Some(FlowMsg::Info),
});
let menu_items = VerticalMenu::empty()
.item(
Button::with_text(TR::buttons__more_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)),
);
let content_menu = VerticalMenuScreen::new(menu_items)
.with_header(Header::new(TString::empty()).with_close_button())
.map(move |msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
_ => None,
});
let content_info = UpdatableInfoScreen::new(info_closure).map(|msg| match msg {
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
_ => None,
});
let mut res = SwipeFlow::new(&RequestNumber::Number)?;
res.add_page(&RequestNumber::Number, content_input)?
.add_page(&RequestNumber::Menu, content_menu)?
.add_page(&RequestNumber::Info, content_info)?;
Ok(res)
}

View File

@ -33,8 +33,8 @@ use super::{
component::Button,
firmware::{
ActionBar, Bip39Input, ConfirmHomescreen, DeviceMenuScreen, Header, HeaderMsg, Hint,
Homescreen, MnemonicKeyboard, NumberInputScreen, PinKeyboard, SelectWordCountScreen,
SelectWordScreen, SetBrightnessScreen, Slip39Input, TextScreen,
Homescreen, MnemonicKeyboard, PinKeyboard, SelectWordCountScreen, SelectWordScreen,
SetBrightnessScreen, Slip39Input, TextScreen,
},
flow, fonts, theme, UIEckhart,
};
@ -619,15 +619,19 @@ impl FirmwareUI for UIEckhart {
min_count: u32,
max_count: u32,
description: Option<TString<'static>>,
_more_info_callback: Option<impl Fn(u32) -> TString<'static> + 'static>,
more_info_callback: Option<impl Fn(u32) -> TString<'static> + 'static>,
) -> Result<impl LayoutMaybeTrace, Error> {
let description = description.unwrap_or(TString::empty());
let component = NumberInputScreen::new(min_count, max_count, count, description)
.with_header(Header::new(title));
let layout = RootComponent::new(component);
Ok(layout)
let flow = flow::request_number::new_request_number(
title,
count,
min_count,
max_count,
description,
unwrap!(more_info_callback),
)?;
Ok(flow)
}
fn request_pin(