1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-06-26 18:02:35 +00:00

feat(eckhart): implement basic UiFirmware fns

- confirm_action, confirm_value
- few show_xyz
- provisional homescreen
This commit is contained in:
obrusvit 2024-12-23 14:45:50 +01:00 committed by Vít Obrusník
parent ea4d704a8a
commit 0696090691
4 changed files with 263 additions and 55 deletions

View File

@ -1667,7 +1667,7 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// title: str, /// title: str,
/// button: str, /// button: str,
/// description: str = "", /// description: str = "",
/// allow_cancel: bool = True, /// allow_cancel: bool = False,
/// time_ms: int = 0, /// time_ms: int = 0,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Success modal. No buttons shown when `button` is empty string.""" /// """Success modal. No buttons shown when `button` is empty string."""

View File

@ -12,6 +12,7 @@ use super::{
fonts, fonts,
}; };
pub const CONFIRM_HOLD_DURATION: Duration = Duration::from_millis(1500);
pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500); pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500);
// Color palette. // Color palette.
@ -136,6 +137,12 @@ pub const TEXT_MONO_LIGHT: TextStyle = TextStyle::new(
GREY_EXTRA_LIGHT, GREY_EXTRA_LIGHT,
); );
/// Decide the text style of chunkified text according to its length.
pub fn get_chunkified_text_style(_character_length: usize) -> &'static TextStyle {
// TODO: implement properly for Eckhart, see Delizia implemenation
&TEXT_MONO_MEDIUM
}
pub const fn label_title_main() -> TextStyle { pub const fn label_title_main() -> TextStyle {
TEXT_SMALL TEXT_SMALL
} }
@ -147,7 +154,7 @@ pub const fn button_default() -> ButtonStyleSheet {
font: fonts::FONT_SATOSHI_MEDIUM_26, font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: GREY_LIGHT, text_color: GREY_LIGHT,
button_color: BG, button_color: BG,
icon_color: FG, icon_color: GREY_LIGHT,
background_color: BG, background_color: BG,
}, },
active: &ButtonStyle { active: &ButtonStyle {
@ -167,6 +174,32 @@ pub const fn button_default() -> ButtonStyleSheet {
} }
} }
pub const fn button_confirm() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: GREEN_LIGHT,
button_color: BG,
icon_color: GREEN_LIGHT,
background_color: BG,
},
active: &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: GREEN,
button_color: GREY_SUPER_DARK,
icon_color: GREEN,
background_color: BG,
},
disabled: &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: GREY_LIGHT,
button_color: GREY_DARK,
icon_color: GREY_LIGHT,
background_color: GREY_DARK,
},
}
}
pub const fn button_header() -> ButtonStyleSheet { pub const fn button_header() -> ButtonStyleSheet {
ButtonStyleSheet { ButtonStyleSheet {

View File

@ -1,13 +1,20 @@
use crate::{ use crate::{
error::Error, error::Error,
io::BinaryData, io::BinaryData,
micropython::{gc::Gc, list::List, obj::Obj}, micropython::{gc::Gc, iter::IterBuf, list::List, obj::Obj, util},
strutil::TString, strutil::TString,
translations::TR,
ui::{ ui::{
component::Empty, component::{
text::{
op::OpTextLayout,
paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
},
Empty, FormattedText,
},
layout::{ layout::{
obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, obj::{LayoutMaybeTrace, LayoutObj, RootComponent},
util::RecoveryType, util::{ConfirmValueParams, RecoveryType, StrOrBytes},
}, },
ui_firmware::{ ui_firmware::{
FirmwareUI, MAX_CHECKLIST_ITEMS, MAX_GROUP_SHARE_LINES, MAX_WORD_QUIZ_ITEMS, FirmwareUI, MAX_CHECKLIST_ITEMS, MAX_GROUP_SHARE_LINES, MAX_WORD_QUIZ_ITEMS,
@ -17,25 +24,60 @@ use crate::{
}; };
use super::{ use super::{
component::{ActionBar, Button, GenericScreen, Header, Hint}, component::{ActionBar, Button, Header, HeaderMsg, Hint, TextScreen},
theme, UIEckhart, fonts, theme, UIEckhart,
}; };
impl FirmwareUI for UIEckhart { impl FirmwareUI for UIEckhart {
fn confirm_action( fn confirm_action(
_title: TString<'static>, title: TString<'static>,
_action: Option<TString<'static>>, action: Option<TString<'static>>,
_description: Option<TString<'static>>, description: 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>>,
_hold: bool, hold: bool,
_hold_danger: bool, _hold_danger: bool,
_reverse: bool, reverse: bool,
_prompt_screen: bool, _prompt_screen: bool,
_prompt_title: Option<TString<'static>>, _prompt_title: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) let action = action.unwrap_or("".into());
let description = description.unwrap_or("".into());
let formatted_text = {
let ops = if !reverse {
OpTextLayout::new(theme::TEXT_NORMAL)
.color(theme::GREY_LIGHT)
.text(action, fonts::FONT_SATOSHI_REGULAR_38)
.newline()
.color(theme::GREY)
.text(description, fonts::FONT_SATOSHI_REGULAR_22)
} else {
OpTextLayout::new(theme::TEXT_NORMAL)
.color(theme::GREY)
.text(description, fonts::FONT_SATOSHI_REGULAR_22)
.newline()
.color(theme::GREY_LIGHT)
.text(action, fonts::FONT_SATOSHI_REGULAR_38)
};
FormattedText::new(ops).vertically_centered()
};
let verb = verb.unwrap_or(TR::buttons__confirm.into());
let right_button = if hold {
Button::with_text(verb).with_long_press(theme::CONFIRM_HOLD_DURATION)
} else {
Button::with_text(verb)
};
let screen = TextScreen::new(formatted_text)
.with_header(Header::new(title).with_menu_button())
.with_hint(Hint::new_instruction(description, None))
.with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CHEVRON_LEFT),
right_button,
));
let layout = RootComponent::new(screen);
Ok(layout)
} }
fn confirm_address( fn confirm_address(
@ -146,22 +188,65 @@ impl FirmwareUI for UIEckhart {
} }
fn confirm_value( fn confirm_value(
_title: TString<'static>, title: TString<'static>,
_value: Obj, value: Obj,
_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,
_hold: bool, hold: bool,
_chunkify: bool, chunkify: bool,
_page_counter: bool, page_counter: bool,
_prompt_screen: bool, _prompt_screen: bool,
_cancel: bool, cancel: bool,
) -> Result<Gc<LayoutObj>, Error> { ) -> Result<Gc<LayoutObj>, Error> {
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented")) let paragraphs = ConfirmValueParams {
description: description.unwrap_or("".into()),
extra: extra.unwrap_or("".into()),
value: if value != Obj::const_none() {
value.try_into()?
} else {
StrOrBytes::Str("".into())
},
font: if chunkify {
let value: TString = value.try_into()?;
theme::get_chunkified_text_style(value.len())
} else if is_data {
&theme::TEXT_MONO_MEDIUM
} else {
&theme::TEXT_MEDIUM
},
description_font: &theme::TEXT_SMALL,
extra_font: &theme::TEXT_SMALL,
}
.into_paragraphs();
let verb = verb.unwrap_or(TR::buttons__confirm.into());
let right_button = if hold {
Button::with_text(verb).with_long_press(theme::CONFIRM_HOLD_DURATION)
} else {
Button::with_text(verb)
};
let header = if info {
Header::new(title)
.with_right_button(Button::with_icon(theme::ICON_INFO), HeaderMsg::Menu)
} else {
Header::new(title)
};
let mut screen = TextScreen::new(paragraphs)
.with_header(header)
.with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CROSS),
right_button,
));
if page_counter {
screen = screen.with_hint(Hint::new_page_counter());
}
LayoutObj::new(screen)
} }
fn confirm_value_intro( fn confirm_value_intro(
@ -369,12 +454,22 @@ impl FirmwareUI for UIEckhart {
} }
fn show_homescreen( fn show_homescreen(
_label: TString<'static>, label: TString<'static>,
_hold: bool, _hold: bool,
_notification: Option<TString<'static>>, notification: Option<TString<'static>>,
_notification_level: u8, _notification_level: u8,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) let paragraphs = ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_NORMAL, label),
Paragraph::new(
&theme::TEXT_NORMAL,
notification.unwrap_or(TString::empty()),
),
])
.into_paragraphs();
let layout = RootComponent::new(paragraphs);
Ok(layout)
} }
fn show_info( fn show_info(
@ -387,12 +482,31 @@ impl FirmwareUI for UIEckhart {
} }
fn show_info_with_cancel( fn show_info_with_cancel(
_title: TString<'static>, title: TString<'static>,
_items: Obj, items: Obj,
_horizontal: bool, _horizontal: bool,
_chunkify: bool, chunkify: bool,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) let mut paragraphs = ParagraphVecShort::new();
for para in IterBuf::new().try_iterate(items)? {
let [key, value]: [Obj; 2] = util::iter_into_array(para)?;
let key: TString = key.try_into()?;
let value: TString = value.try_into()?;
paragraphs.add(Paragraph::new(&theme::TEXT_MEDIUM, key).no_break());
if chunkify {
paragraphs.add(Paragraph::new(
theme::get_chunkified_text_style(value.len()),
value,
));
} else {
paragraphs.add(Paragraph::new(&theme::TEXT_MONO_MEDIUM, value));
}
}
let screen = TextScreen::new(paragraphs.into_paragraphs())
.with_header(Header::new(title).with_close_button());
let layout = RootComponent::new(screen);
Ok(layout)
} }
fn show_lockscreen( fn show_lockscreen(
@ -403,8 +517,22 @@ impl FirmwareUI for UIEckhart {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
} }
fn show_mismatch(_title: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> { fn show_mismatch(title: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) let description: TString = TR::addr_mismatch__contact_support_at.into();
let url: TString = TR::addr_mismatch__support_url.into();
let button: TString = TR::buttons__quit.into();
let paragraphs = ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_REGULAR, description).centered(),
Paragraph::new(&theme::TEXT_MONO_MEDIUM, url).centered(),
])
.into_paragraphs();
let screen = TextScreen::new(paragraphs)
.with_header(Header::new(title))
.with_action_bar(ActionBar::new_single(Button::with_text(button)));
let layout = RootComponent::new(screen);
Ok(layout)
} }
fn show_progress( fn show_progress(
@ -447,35 +575,82 @@ impl FirmwareUI for UIEckhart {
fn show_simple( fn show_simple(
text: TString<'static>, text: TString<'static>,
_title: Option<TString<'static>>, title: Option<TString<'static>>,
_button: Option<TString<'static>>, button: Option<TString<'static>>,
) -> Result<Gc<LayoutObj>, Error> { ) -> Result<Gc<LayoutObj>, Error> {
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented")) let paragraphs = Paragraph::new(&theme::TEXT_REGULAR, text).into_paragraphs();
let mut screen = TextScreen::new(paragraphs);
if let Some(title) = title {
screen = screen.with_header(Header::new(title));
}
if let Some(button) = button {
screen = screen.with_action_bar(ActionBar::new_single(Button::with_text(button)));
}
let obj = LayoutObj::new(screen)?;
Ok(obj)
} }
fn show_success( fn show_success(
_title: TString<'static>, title: TString<'static>,
_button: TString<'static>, button: TString<'static>,
_description: TString<'static>, description: TString<'static>,
_allow_cancel: bool, allow_cancel: bool,
_time_ms: u32, _time_ms: u32,
) -> Result<Gc<LayoutObj>, Error> { ) -> Result<Gc<LayoutObj>, Error> {
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented")) let paragraphs = Paragraph::new(&theme::TEXT_REGULAR, description).into_paragraphs();
let header = Header::new(title).with_icon(theme::ICON_DONE, theme::GREEN_LIGHT);
let action_bar = if allow_cancel {
ActionBar::new_double(
Button::with_icon(theme::ICON_CROSS),
Button::with_text(button),
)
} else {
ActionBar::new_single(Button::with_text(button))
};
let screen = TextScreen::new(paragraphs)
.with_header(header)
.with_action_bar(action_bar);
let layout = LayoutObj::new(screen)?;
Ok(layout)
} }
fn show_wait_text(_text: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> { fn show_wait_text(text: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) let paragraphs = Paragraph::new(&theme::TEXT_REGULAR, text).into_paragraphs();
let screen = TextScreen::new(paragraphs);
let layout = RootComponent::new(screen);
Ok(layout)
} }
fn show_warning( fn show_warning(
_title: TString<'static>, title: TString<'static>,
_button: TString<'static>, button: TString<'static>,
_value: TString<'static>, value: TString<'static>,
_description: TString<'static>, description: TString<'static>,
_allow_cancel: bool, allow_cancel: bool,
_danger: bool, danger: bool, // TODO: review if `danger` needed in all layouts since we have show_danger
) -> Result<Gc<LayoutObj>, Error> { ) -> Result<Gc<LayoutObj>, Error> {
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented")) let paragraphs = ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_SMALL, description),
Paragraph::new(&theme::TEXT_REGULAR, value),
])
.into_paragraphs();
let header = Header::new(title).with_icon(theme::ICON_INFO, theme::GREEN_LIGHT);
let action_bar = if allow_cancel {
ActionBar::new_double(
Button::with_icon(theme::ICON_CROSS),
Button::with_text(button),
)
} else {
ActionBar::new_single(Button::with_text(button))
};
let screen = TextScreen::new(paragraphs)
.with_header(header)
.with_action_bar(action_bar);
let layout = LayoutObj::new(screen)?;
Ok(layout)
} }
fn tutorial() -> Result<impl LayoutMaybeTrace, Error> { fn tutorial() -> Result<impl LayoutMaybeTrace, Error> {

View File

@ -351,9 +351,9 @@ def show_success(
) -> Awaitable[None]: ) -> Awaitable[None]:
return raise_if_not_confirmed( return raise_if_not_confirmed(
trezorui_api.show_success( trezorui_api.show_success(
title=content, title=subheader if subheader else "",
button="", button=button if button else "",
description=subheader if subheader else "", description=content,
), ),
br_name, br_name,
ButtonRequestType.Success, ButtonRequestType.Success,