diff --git a/core/embed/rust/src/ui/constant.rs b/core/embed/rust/src/ui/constant.rs index 65b3e5d366..071d3aaa72 100644 --- a/core/embed/rust/src/ui/constant.rs +++ b/core/embed/rust/src/ui/constant.rs @@ -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::*; diff --git a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs index 81e13fa054..2421c0a702 100644 --- a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs +++ b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs @@ -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 { + match msg { + HomescreenMsg::Dismissed => Ok(CANCELLED.as_obj()), + } + } +} + impl ComponentMsgObj for TextScreen 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 { + match msg { + ConfirmHomescreenMsg::Cancelled => Ok(CANCELLED.as_obj()), + ConfirmHomescreenMsg::Confirmed => Ok(CONFIRMED.as_obj()), + } + } +} diff --git a/core/embed/rust/src/ui/layout_eckhart/constant.rs b/core/embed/rust/src/ui/layout_eckhart/constant.rs index 5c22be306f..fbf838d7b3 100644 --- a/core/embed/rust/src/ui/layout_eckhart/constant.rs +++ b/core/embed/rust/src/ui/layout_eckhart/constant.rs @@ -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) } diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs index 4bff91e3c1..c06ec99809 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs @@ -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 diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/confirm_homescreen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/confirm_homescreen.rs new file mode 100644 index 0000000000..a1b5c8c147 --- /dev/null +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/confirm_homescreen.rs @@ -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>, + image: Option>, + action_bar: ActionBar, +} + +pub enum ConfirmHomescreenMsg { + Cancelled, + Confirmed, +} + +impl ConfirmHomescreen { + pub fn new(title: TString<'static>, image: BinaryData<'static>) -> Result { + 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.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"); + } +} diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/hint.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/hint.rs index 89cb7d5bed..587a97a1c2 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/hint.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/hint.rs @@ -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>>(text: T, icon: Option) -> 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>>(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())) } diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs new file mode 100644 index 0000000000..af4afc9d91 --- /dev/null +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs @@ -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>, + /// Home action bar + action_bar: ActionBar, + /// Background image + image: Option>, + /// 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 { + 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 { + 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 { + 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> { + 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()); + } +} diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs index 51b2a74fd3..900800a2bf 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs @@ -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}; diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs b/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs index b2a247ede5..a25474ac3c 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs @@ -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; diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs index d18c801031..b94a74b8cc 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs @@ -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 { diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs b/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs index 48eb02db8a..3b16f66b85 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs @@ -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] diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs index 015845b25d..d83208e60e 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs @@ -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 { - Err::, 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::, 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>, - _notification_level: u8, + notification_level: u8, ) -> Result { - 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 { - Err::, 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 {