mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-04-07 10:55:56 +00:00
feat(eckhart): implement homescreen and lockscreen
- HomeScreen, ConfirmHomescreen full-screen components - respective FirmwareUI implementation
This commit is contained in:
parent
2a825c1634
commit
8f2ee16557
@ -2,19 +2,22 @@
|
||||
//! current feature (Trezor model)
|
||||
|
||||
#[cfg(all(
|
||||
feature = "layout_bolt",
|
||||
feature = "layout_eckhart",
|
||||
not(feature = "layout_bolt"),
|
||||
not(feature = "layout_caesar"),
|
||||
not(feature = "layout_delizia"),
|
||||
not(feature = "layout_eckhart")
|
||||
not(feature = "layout_delizia")
|
||||
))]
|
||||
pub use super::layout_bolt::constant::*;
|
||||
#[cfg(all(
|
||||
feature = "layout_caesar",
|
||||
not(feature = "layout_delizia"),
|
||||
not(feature = "layout_eckhart")
|
||||
))]
|
||||
pub use super::layout_caesar::constant::*;
|
||||
#[cfg(all(feature = "layout_delizia", not(feature = "layout_eckhart")))]
|
||||
pub use super::layout_delizia::constant::*;
|
||||
#[cfg(feature = "layout_eckhart")]
|
||||
pub use super::layout_eckhart::constant::*;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "layout_delizia",
|
||||
not(feature = "layout_bolt"),
|
||||
not(feature = "layout_caesar")
|
||||
))]
|
||||
pub use super::layout_delizia::constant::*;
|
||||
|
||||
#[cfg(all(feature = "layout_caesar", not(feature = "layout_bolt")))]
|
||||
pub use super::layout_caesar::constant::*;
|
||||
|
||||
#[cfg(feature = "layout_bolt")]
|
||||
pub use super::layout_bolt::constant::*;
|
||||
|
@ -14,9 +14,10 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::firmware::{
|
||||
AllowedTextContent, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputScreen,
|
||||
NumberInputScreenMsg, PinKeyboard, PinKeyboardMsg, SelectWordCountMsg, SelectWordCountScreen,
|
||||
SelectWordMsg, SelectWordScreen, TextScreen, TextScreenMsg,
|
||||
AllowedTextContent, ConfirmHomescreen, ConfirmHomescreenMsg, Homescreen, HomescreenMsg,
|
||||
MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputScreen, NumberInputScreenMsg,
|
||||
PinKeyboard, PinKeyboardMsg, SelectWordCountMsg, SelectWordCountScreen, SelectWordMsg,
|
||||
SelectWordScreen, TextScreen, TextScreenMsg,
|
||||
};
|
||||
|
||||
impl ComponentMsgObj for PinKeyboard<'_> {
|
||||
@ -69,6 +70,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentMsgObj for Homescreen {
|
||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||
match msg {
|
||||
HomescreenMsg::Dismissed => Ok(CANCELLED.as_obj()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ComponentMsgObj for TextScreen<T>
|
||||
where
|
||||
T: AllowedTextContent,
|
||||
@ -109,3 +118,12 @@ impl ComponentMsgObj for NumberInputScreen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentMsgObj for ConfirmHomescreen {
|
||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||
match msg {
|
||||
ConfirmHomescreenMsg::Cancelled => Ok(CANCELLED.as_obj()),
|
||||
ConfirmHomescreenMsg::Confirmed => Ok(CONFIRMED.as_obj()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,9 @@ use crate::trezorhal::display::{DISPLAY_RESX, DISPLAY_RESY};
|
||||
pub const WIDTH: i16 = DISPLAY_RESX as _;
|
||||
pub const HEIGHT: i16 = DISPLAY_RESY as _;
|
||||
|
||||
// TODO: below constants copied from mercury
|
||||
pub const LINE_SPACE: i16 = 4;
|
||||
pub const FONT_BPP: i16 = 4;
|
||||
|
||||
pub const LOADER_OUTER: i16 = 60;
|
||||
pub const LOADER_INNER: i16 = 52;
|
||||
pub const LOADER_ICON_MAX_SIZE: i16 = 64;
|
||||
|
||||
pub const fn size() -> Offset {
|
||||
Offset::new(WIDTH, HEIGHT)
|
||||
}
|
||||
|
@ -105,6 +105,13 @@ impl ActionBar {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_cancel_confirm() -> Self {
|
||||
Self::new_double(
|
||||
Button::with_icon(theme::ICON_CROSS).styled(theme::button_cancel()),
|
||||
Button::with_text(TR::buttons__confirm.into()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_left_short(mut self, left_short: bool) -> Self {
|
||||
self.left_short = left_short;
|
||||
self
|
||||
|
@ -0,0 +1,108 @@
|
||||
use crate::{
|
||||
error::{value_error, Error},
|
||||
io::BinaryData,
|
||||
strutil::TString,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Label},
|
||||
constant::SCREEN,
|
||||
display::image::ImageInfo,
|
||||
geometry::{Insets, Rect},
|
||||
shape::{self, Renderer},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{check_homescreen_format, theme, ActionBar, ActionBarMsg, Header};
|
||||
|
||||
/// Full-screen component for confirming a new homescreen image. If the image is
|
||||
/// empty, the user is asked to confirm the default homescreen.
|
||||
pub struct ConfirmHomescreen {
|
||||
header: Header,
|
||||
text: Option<Label<'static>>,
|
||||
image: Option<BinaryData<'static>>,
|
||||
action_bar: ActionBar,
|
||||
}
|
||||
|
||||
pub enum ConfirmHomescreenMsg {
|
||||
Cancelled,
|
||||
Confirmed,
|
||||
}
|
||||
|
||||
impl ConfirmHomescreen {
|
||||
pub fn new(title: TString<'static>, image: BinaryData<'static>) -> Result<Self, Error> {
|
||||
let action_bar = ActionBar::new_cancel_confirm();
|
||||
let header = Header::new(title);
|
||||
|
||||
if image.is_empty() {
|
||||
// Use default homescreen
|
||||
Ok(Self {
|
||||
header,
|
||||
text: Some(Label::left_aligned(
|
||||
TR::homescreen__set_default.into(),
|
||||
theme::firmware::TEXT_REGULAR,
|
||||
)),
|
||||
image: None,
|
||||
action_bar,
|
||||
})
|
||||
} else {
|
||||
// Validate and use custom homescreen
|
||||
if !check_homescreen_format(image) {
|
||||
return Err(value_error!(c"Invalid image."));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
text: None,
|
||||
image: Some(image),
|
||||
action_bar,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ConfirmHomescreen {
|
||||
type Msg = ConfirmHomescreenMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
// assert full screen
|
||||
debug_assert_eq!(bounds.height(), SCREEN.height());
|
||||
debug_assert_eq!(bounds.width(), SCREEN.width());
|
||||
|
||||
let (header_area, rest) = bounds.split_top(Header::HEADER_HEIGHT);
|
||||
let (rest, action_bar_area) = rest.split_bottom(ActionBar::ACTION_BAR_HEIGHT);
|
||||
|
||||
self.header.place(header_area);
|
||||
self.action_bar.place(action_bar_area);
|
||||
self.text.place(rest);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.action_bar.event(ctx, event).and_then(|msg| match msg {
|
||||
ActionBarMsg::Cancelled => Some(Self::Msg::Cancelled),
|
||||
ActionBarMsg::Confirmed => Some(Self::Msg::Confirmed),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
if let Some(image) = self.image {
|
||||
if let ImageInfo::Jpeg(_) = ImageInfo::parse(image) {
|
||||
let clip = SCREEN.inset(Insets::bottom(theme::ACTION_BAR_HEIGHT));
|
||||
target.in_clip(clip, &|t| {
|
||||
shape::JpegImage::new_image(SCREEN.top_left(), image).render(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.header.render(target);
|
||||
self.text.render(target);
|
||||
self.action_bar.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for ConfirmHomescreen {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ConfirmHomescreen");
|
||||
}
|
||||
}
|
@ -62,6 +62,7 @@ impl<'a> Hint<'a> {
|
||||
Instruction::new(text.into(), theme::GREY, icon, Some(theme::GREY_LIGHT));
|
||||
Self::from_content(HintContent::Instruction(instruction_component))
|
||||
}
|
||||
|
||||
pub fn new_instruction_green<T: Into<TString<'static>>>(text: T, icon: Option<Icon>) -> Self {
|
||||
let instruction_component = Instruction::new(
|
||||
text.into(),
|
||||
@ -72,6 +73,16 @@ impl<'a> Hint<'a> {
|
||||
Self::from_content(HintContent::Instruction(instruction_component))
|
||||
}
|
||||
|
||||
pub fn new_warning_severe<T: Into<TString<'static>>>(text: T) -> Self {
|
||||
let instruction_component = Instruction::new(
|
||||
text.into(),
|
||||
theme::RED,
|
||||
Some(theme::ICON_WARNING),
|
||||
Some(theme::RED),
|
||||
);
|
||||
Self::from_content(HintContent::Instruction(instruction_component))
|
||||
}
|
||||
|
||||
pub fn new_page_counter() -> Self {
|
||||
Self::from_content(HintContent::PageCounter(PageCounter::new()))
|
||||
}
|
||||
|
236
core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs
Normal file
236
core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs
Normal file
@ -0,0 +1,236 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
io::BinaryData,
|
||||
strutil::TString,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{text::TextStyle, Component, Event, EventCtx, Label, Never},
|
||||
display::image::ImageInfo,
|
||||
geometry::{Insets, Offset, Rect},
|
||||
layout::util::get_user_custom_image,
|
||||
shape::{self, Renderer},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
super::{component::Button, fonts},
|
||||
constant::{HEIGHT, SCREEN, WIDTH},
|
||||
theme::{self, firmware::button_homebar_style, BLACK, GREEN_DARK, GREEN_EXTRA_DARK},
|
||||
ActionBar, ActionBarMsg, Hint,
|
||||
};
|
||||
|
||||
/// Full-screen component for the homescreen and lockscreen.
|
||||
pub struct Homescreen {
|
||||
/// Device name with shadow
|
||||
label: HomeLabel,
|
||||
/// Notification
|
||||
hint: Option<Hint<'static>>,
|
||||
/// Home action bar
|
||||
action_bar: ActionBar,
|
||||
/// Background image
|
||||
image: Option<BinaryData<'static>>,
|
||||
/// Whether the PIN is set and device can be locked
|
||||
lockable: bool,
|
||||
/// Whether the homescreen is locked
|
||||
locked: bool,
|
||||
}
|
||||
|
||||
pub enum HomescreenMsg {
|
||||
Dismissed,
|
||||
}
|
||||
|
||||
impl Homescreen {
|
||||
pub fn new(
|
||||
label: TString<'static>,
|
||||
lockable: bool,
|
||||
locked: bool,
|
||||
bootscreen: bool,
|
||||
coinjoin_authorized: bool,
|
||||
notification: Option<(TString<'static>, u8)>,
|
||||
) -> Result<Self, Error> {
|
||||
let image = get_homescreen_image();
|
||||
|
||||
// Notification
|
||||
let mut notification_level = 4;
|
||||
let hint = if let Some((text, level)) = notification {
|
||||
notification_level = level;
|
||||
if notification_level == 0 {
|
||||
Some(Hint::new_warning_severe(text))
|
||||
} else {
|
||||
Some(Hint::new_instruction(text, Some(theme::ICON_INFO)))
|
||||
}
|
||||
} else if locked && coinjoin_authorized {
|
||||
Some(Hint::new_instruction_green(
|
||||
TR::coinjoin__do_not_disconnect,
|
||||
Some(theme::ICON_INFO),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// ActionBar button
|
||||
let button_style = button_homebar_style(notification_level);
|
||||
let button = if bootscreen {
|
||||
Button::with_homebar_content(Some(TR::lockscreen__tap_to_connect.into()))
|
||||
.styled(button_style)
|
||||
} else if locked {
|
||||
Button::with_homebar_content(Some(TR::lockscreen__tap_to_unlock.into()))
|
||||
.styled(button_style)
|
||||
} else {
|
||||
// TODO: Battery/Connectivity button content
|
||||
Button::with_homebar_content(None).styled(button_style)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
label: HomeLabel::new(label),
|
||||
hint,
|
||||
action_bar: ActionBar::new_single(button),
|
||||
image,
|
||||
lockable,
|
||||
locked,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Homescreen {
|
||||
type Msg = HomescreenMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
// assert full screen
|
||||
debug_assert_eq!(bounds.height(), SCREEN.height());
|
||||
debug_assert_eq!(bounds.width(), SCREEN.width());
|
||||
|
||||
let (rest, bar_area) = bounds.split_bottom(theme::ACTION_BAR_HEIGHT);
|
||||
let rest = if let Some(hint) = &mut self.hint {
|
||||
let (rest, hint_area) = rest.split_bottom(hint.height());
|
||||
hint.place(hint_area);
|
||||
rest
|
||||
} else {
|
||||
rest
|
||||
};
|
||||
let label_area = rest
|
||||
.inset(theme::SIDE_INSETS)
|
||||
.inset(Insets::top(theme::PADDING));
|
||||
|
||||
self.label.place(label_area);
|
||||
self.action_bar.place(bar_area);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Some(ActionBarMsg::Confirmed) = self.action_bar.event(ctx, event) {
|
||||
if self.locked {
|
||||
return Some(HomescreenMsg::Dismissed);
|
||||
} else {
|
||||
// TODO: Show menu and handle "lock" action differently
|
||||
if self.lockable {
|
||||
return Some(HomescreenMsg::Dismissed);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
if let Some(image) = self.image {
|
||||
if let ImageInfo::Jpeg(_) = ImageInfo::parse(image) {
|
||||
shape::JpegImage::new_image(SCREEN.top_left(), image).render(target);
|
||||
}
|
||||
} else {
|
||||
render_default_hs(target);
|
||||
}
|
||||
self.label.render(target);
|
||||
self.hint.render(target);
|
||||
self.action_bar.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper component to render a label with a shadow.
|
||||
struct HomeLabel {
|
||||
label: Label<'static>,
|
||||
label_shadow: Label<'static>,
|
||||
}
|
||||
|
||||
impl HomeLabel {
|
||||
const LABEL_SHADOW_OFFSET: Offset = Offset::uniform(2);
|
||||
const LABEL_TEXT_STYLE: TextStyle = theme::firmware::TEXT_BIG;
|
||||
const LABEL_SHADOW_TEXT_STYLE: TextStyle = TextStyle::new(
|
||||
fonts::FONT_SATOSHI_EXTRALIGHT_46,
|
||||
BLACK,
|
||||
BLACK,
|
||||
BLACK,
|
||||
BLACK,
|
||||
);
|
||||
|
||||
fn new(label: TString<'static>) -> Self {
|
||||
let label_primary = Label::left_aligned(label, Self::LABEL_TEXT_STYLE).top_aligned();
|
||||
let label_shadow = Label::left_aligned(label, Self::LABEL_SHADOW_TEXT_STYLE).top_aligned();
|
||||
Self {
|
||||
label: label_primary,
|
||||
label_shadow,
|
||||
}
|
||||
}
|
||||
|
||||
fn inner(&self) -> &Label<'static> {
|
||||
&self.label
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for HomeLabel {
|
||||
type Msg = Never;
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.label.place(bounds);
|
||||
self.label_shadow
|
||||
.place(bounds.translate(Self::LABEL_SHADOW_OFFSET));
|
||||
bounds
|
||||
}
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.label_shadow.render(target);
|
||||
self.label.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_homescreen_format(image: BinaryData) -> bool {
|
||||
match ImageInfo::parse(image) {
|
||||
ImageInfo::Jpeg(info) => {
|
||||
info.width() == WIDTH && info.height() == HEIGHT && info.mcu_height() <= 16
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_default_hs<'a>(target: &mut impl Renderer<'a>) {
|
||||
shape::Bar::new(SCREEN)
|
||||
.with_fg(theme::BG)
|
||||
.with_bg(theme::BG)
|
||||
.render(target);
|
||||
|
||||
shape::Circle::new(SCREEN.center(), 48)
|
||||
.with_fg(GREEN_DARK)
|
||||
.with_thickness(4)
|
||||
.render(target);
|
||||
shape::Circle::new(SCREEN.center(), 42)
|
||||
.with_fg(GREEN_EXTRA_DARK)
|
||||
.with_thickness(4)
|
||||
.render(target);
|
||||
}
|
||||
|
||||
fn get_homescreen_image() -> Option<BinaryData<'static>> {
|
||||
if let Ok(image) = get_user_custom_image() {
|
||||
if check_homescreen_format(image) {
|
||||
return Some(image);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for Homescreen {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Homescreen");
|
||||
t.child("label", self.label.inner());
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
mod action_bar;
|
||||
mod confirm_homescreen;
|
||||
mod header;
|
||||
mod hint;
|
||||
mod hold_to_confirm;
|
||||
mod homescreen;
|
||||
mod keyboard;
|
||||
mod number_input_screen;
|
||||
mod qr_screen;
|
||||
@ -12,10 +14,11 @@ mod vertical_menu;
|
||||
mod vertical_menu_screen;
|
||||
|
||||
pub use action_bar::{ActionBar, ActionBarMsg};
|
||||
pub use confirm_homescreen::{ConfirmHomescreen, ConfirmHomescreenMsg};
|
||||
pub use header::{Header, HeaderMsg};
|
||||
pub use hint::Hint;
|
||||
pub use hold_to_confirm::HoldToConfirmAnim;
|
||||
#[cfg(feature = "translations")]
|
||||
pub use homescreen::{check_homescreen_format, Homescreen, HomescreenMsg};
|
||||
pub use keyboard::{
|
||||
bip39::Bip39Input,
|
||||
mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg},
|
||||
@ -27,7 +30,6 @@ pub use keyboard::{
|
||||
pub use number_input_screen::{NumberInputScreen, NumberInputScreenMsg};
|
||||
pub use qr_screen::{QrMsg, QrScreen};
|
||||
pub use select_word_screen::{SelectWordMsg, SelectWordScreen};
|
||||
#[cfg(feature = "translations")]
|
||||
pub use share_words::{ShareWordsScreen, ShareWordsScreenMsg};
|
||||
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
|
||||
pub use vertical_menu::{VerticalMenu, VerticalMenuMsg, MENU_MAX_ITEMS};
|
||||
|
@ -8,8 +8,7 @@ use super::{
|
||||
component::{ButtonStyle, ButtonStyleSheet},
|
||||
fonts,
|
||||
},
|
||||
BLACK, BLUE, GREY, GREY_DARK, GREY_EXTRA_DARK, GREY_EXTRA_LIGHT, GREY_LIGHT, GREY_SUPER_DARK,
|
||||
RED, WHITE,
|
||||
BLACK, BLUE, GREY, GREY_DARK, GREY_EXTRA_LIGHT, GREY_LIGHT, GREY_SUPER_DARK, RED, WHITE,
|
||||
};
|
||||
|
||||
pub const BLD_BG: Color = BLACK;
|
||||
|
@ -287,6 +287,44 @@ pub const fn menu_item_title_orange() -> ButtonStyleSheet {
|
||||
menu_item_title!(ORANGE)
|
||||
}
|
||||
|
||||
macro_rules! button_homebar_style {
|
||||
($text_color:expr, $icon_color:expr) => {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: fonts::FONT_SATOSHI_MEDIUM_26,
|
||||
text_color: $text_color,
|
||||
button_color: BG,
|
||||
icon_color: $icon_color,
|
||||
background_color: BG,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: fonts::FONT_SATOSHI_MEDIUM_26,
|
||||
text_color: GREY_LIGHT,
|
||||
button_color: GREY_SUPER_DARK,
|
||||
icon_color: GREY_LIGHT,
|
||||
background_color: GREY_SUPER_DARK,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: fonts::FONT_SATOSHI_MEDIUM_26,
|
||||
text_color: GREY_LIGHT,
|
||||
button_color: GREY_SUPER_DARK,
|
||||
icon_color: GREY_LIGHT,
|
||||
background_color: GREY_SUPER_DARK,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
pub const fn button_homebar_style(level: u8) -> ButtonStyleSheet {
|
||||
// NOTE: 0 is the highest severity.
|
||||
match level {
|
||||
4 => button_homebar_style!(GREY_LIGHT, GREY_LIGHT),
|
||||
3 => button_homebar_style!(GREY_LIGHT, GREEN_LIME),
|
||||
2 => button_homebar_style!(GREY_LIGHT, YELLOW),
|
||||
1 => button_homebar_style!(GREY_LIGHT, YELLOW),
|
||||
_ => button_homebar_style!(RED, RED),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn button_select_word() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
|
@ -45,8 +45,9 @@ pub const FATAL_ERROR_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E);
|
||||
pub const FATAL_ERROR_HIGHLIGHT_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41);
|
||||
|
||||
// Common constants
|
||||
pub const PADDING: i16 = 24; // px
|
||||
pub const HEADER_HEIGHT: i16 = 96; // [px]
|
||||
pub const SIDE_INSETS: Insets = Insets::sides(24); // [px]
|
||||
pub const SIDE_INSETS: Insets = Insets::sides(PADDING);
|
||||
pub const ACTION_BAR_HEIGHT: i16 = 90; // [px]
|
||||
pub const TEXT_VERTICAL_SPACING: i16 = 24; // [px]
|
||||
|
||||
|
@ -27,8 +27,9 @@ use crate::{
|
||||
use super::{
|
||||
component::Button,
|
||||
firmware::{
|
||||
ActionBar, Bip39Input, Header, HeaderMsg, Hint, MnemonicKeyboard, NumberInputScreen,
|
||||
PinKeyboard, SelectWordCountScreen, SelectWordScreen, Slip39Input, TextScreen,
|
||||
ActionBar, Bip39Input, ConfirmHomescreen, Header, HeaderMsg, Hint, Homescreen,
|
||||
MnemonicKeyboard, NumberInputScreen, PinKeyboard, SelectWordCountScreen, SelectWordScreen,
|
||||
Slip39Input, TextScreen,
|
||||
},
|
||||
flow, fonts, theme, UIEckhart,
|
||||
};
|
||||
@ -105,10 +106,12 @@ impl FirmwareUI for UIEckhart {
|
||||
}
|
||||
|
||||
fn confirm_homescreen(
|
||||
_title: TString<'static>,
|
||||
_image: BinaryData<'static>,
|
||||
title: TString<'static>,
|
||||
image: BinaryData<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
let screen = ConfirmHomescreen::new(title, image)?;
|
||||
let layout = RootComponent::new(screen);
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
fn confirm_coinjoin(
|
||||
@ -314,8 +317,8 @@ impl FirmwareUI for UIEckhart {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn check_homescreen_format(_image: BinaryData, _accept_toif: bool) -> bool {
|
||||
false // not implemented
|
||||
fn check_homescreen_format(image: BinaryData, _accept_toif: bool) -> bool {
|
||||
super::firmware::check_homescreen_format(image)
|
||||
}
|
||||
|
||||
fn continue_recovery_homepage(
|
||||
@ -570,20 +573,22 @@ impl FirmwareUI for UIEckhart {
|
||||
|
||||
fn show_homescreen(
|
||||
label: TString<'static>,
|
||||
_hold: bool,
|
||||
hold: bool,
|
||||
notification: Option<TString<'static>>,
|
||||
_notification_level: u8,
|
||||
notification_level: u8,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
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);
|
||||
let locked = false;
|
||||
let bootscreen = false;
|
||||
let coinjoin_authorized = false;
|
||||
let notification = notification.map(|w| (w, notification_level));
|
||||
let layout = RootComponent::new(Homescreen::new(
|
||||
label,
|
||||
hold,
|
||||
locked,
|
||||
bootscreen,
|
||||
coinjoin_authorized,
|
||||
notification,
|
||||
)?);
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
@ -632,11 +637,22 @@ impl FirmwareUI for UIEckhart {
|
||||
}
|
||||
|
||||
fn show_lockscreen(
|
||||
_label: TString<'static>,
|
||||
_bootscreen: bool,
|
||||
_coinjoin_authorized: bool,
|
||||
label: TString<'static>,
|
||||
bootscreen: bool,
|
||||
coinjoin_authorized: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
let locked = true;
|
||||
let notification = None;
|
||||
let hold = false;
|
||||
let layout = RootComponent::new(Homescreen::new(
|
||||
label,
|
||||
hold,
|
||||
locked,
|
||||
bootscreen,
|
||||
coinjoin_authorized,
|
||||
notification,
|
||||
)?);
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
fn show_mismatch(title: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
|
Loading…
Reference in New Issue
Block a user