diff --git a/core/embed/rust/src/ui/layout_eckhart/bootloader/mod.rs b/core/embed/rust/src/ui/layout_eckhart/bootloader/mod.rs index 92e0e0f2c0..a48c086e60 100644 --- a/core/embed/rust/src/ui/layout_eckhart/bootloader/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/bootloader/mod.rs @@ -11,6 +11,8 @@ mod pairing_finalization; #[cfg(feature = "ble")] mod pairing_mode; mod welcome_screen; +#[cfg(feature = "ble")] +mod wireless_setup_screen; pub use bld_actionbar::{BldActionBar, BldActionBarMsg}; pub use bld_header::{BldHeader, BldHeaderMsg}; @@ -24,5 +26,7 @@ pub use connect::ConnectScreen; pub use pairing_finalization::PairingFinalizationScreen; #[cfg(feature = "ble")] pub use pairing_mode::PairingModeScreen; +#[cfg(feature = "ble")] +pub use wireless_setup_screen::WirelessSetupScreen; pub use welcome_screen::BldWelcomeScreen; diff --git a/core/embed/rust/src/ui/layout_eckhart/bootloader/wireless_setup_screen.rs b/core/embed/rust/src/ui/layout_eckhart/bootloader/wireless_setup_screen.rs new file mode 100644 index 0000000000..0130e8d03f --- /dev/null +++ b/core/embed/rust/src/ui/layout_eckhart/bootloader/wireless_setup_screen.rs @@ -0,0 +1,170 @@ +use crate::{ + strutil::TString, + ui::{ + component::{Component, Event, EventCtx, Label}, + event::BLEEvent, + geometry::{Alignment2D, Insets, Offset, Rect}, + shape::{self, Renderer}, + }, +}; + +use super::{ + super::{component::Button, constant::SCREEN, theme}, + pairing_mode::PairingMsg, + BldActionBar, BldActionBarMsg, BldHeader, BldHeaderMsg, +}; + +/// Full-screen component for the wireless setup screen. It shows instructions +/// for the user and QR code to download the Trezor Suite app. +pub struct WirelessSetupScreen { + /// Header with the device name + header: BldHeader<'static>, + /// Instruction text for the user + instruction: Label<'static>, + /// Area for the QR code + qr_area: Rect, + /// Action bar to invoke more info + action_bar: BldActionBar, + /// More info section that can be toggled + more_info: MoreInfo<'static>, + /// Flag to indicate if the more info section is currently showing + more_info_showing: bool, +} + +struct MoreInfo<'a> { + header: BldHeader<'a>, + instruction_primary: Label<'a>, + instruction_secondary: Label<'a>, + action_bar: BldActionBar, +} + +impl WirelessSetupScreen { + pub fn new(name: TString<'static>) -> Self { + let instruction = Label::left_aligned( + "Get the Trezor Suite app to begin setup.".into(), + theme::TEXT_NORMAL, + ); + let btn = Button::with_text("More info".into()).styled(theme::button_default()); + let btn_more_info = Button::with_text("More at trezor.io/start".into()) + .styled(theme::button_default()) + .initially_enabled(false); + let more_info = MoreInfo { + header: BldHeader::new("More info".into()).with_close_button(), + instruction_primary: Label::left_aligned( + "The Trezor Suite app is required to set up your Trezor.".into(), + theme::TEXT_NORMAL, + ), + instruction_secondary: Label::left_aligned( + "Setup via USB-C isn't supported on Apple devices running iOS or iPadOS.".into(), + theme::TEXT_SMALL_GREY, + ), + action_bar: BldActionBar::new_single(btn_more_info), + }; + + Self { + header: BldHeader::new(name), + instruction, + qr_area: Rect::zero(), + action_bar: BldActionBar::new_single(btn), + more_info, + more_info_showing: false, + } + } + + fn render_qr_code<'s>(&'s self, target: &mut impl Renderer<'s>) { + if self.qr_area.is_empty() { + return; + } + + let qr_code_size = theme::bootloader::ICON_QR_TREZOR_IO_START.toif.width(); + let padding = 20; + let qr_code_pad_size = qr_code_size + padding; + let pad_area = Rect::snap( + self.qr_area.center(), + Offset::new(qr_code_pad_size, qr_code_pad_size), + Alignment2D::CENTER, + ); + + shape::Bar::new(pad_area) + .with_fg(theme::WHITE) + .with_bg(theme::WHITE) + .with_radius(4) + .render(target); + // TODO: uncomment on space bump + // shape::ToifImage::new( + // pad_area.center(), + // theme::bootloader::ICON_QR_TREZOR_IO_START.toif, + // ) + // .with_align(Alignment2D::CENTER) + // .with_fg(theme::WHITE) + // .with_bg(theme::BLACK) + // .render(target); + } +} + +impl Component for WirelessSetupScreen { + type Msg = PairingMsg; + + fn place(&mut self, _bounds: Rect) -> Rect { + let (header_area, rest) = SCREEN.split_top(theme::HEADER_HEIGHT); + let (content_area, action_bar_area) = rest.split_bottom(theme::ACTION_BAR_HEIGHT); + // let content_area = content_area.inset(theme::SIDE_INSETS); + let content_area = content_area.inset(Insets::sides(20)); + + self.header.place(header_area); + self.instruction.place(content_area); + self.action_bar.place(action_bar_area); + // remaining space is used for the QR code which will be rendered in the center + self.qr_area = content_area.inset(Insets::top( + self.instruction.text_height(content_area.width()) + 32, + )); + + // Place the toggled MoreInfo sections accordingly + self.more_info.header.place(header_area); + self.more_info.instruction_primary.place(content_area); + self.more_info.instruction_secondary.place( + content_area.inset(Insets::top( + self.more_info + .instruction_primary + .text_height(content_area.width()) + + 32, + )), + ); + self.more_info.action_bar.place(action_bar_area); + SCREEN + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + if let Some(BldActionBarMsg::Confirmed) = self.action_bar.event(ctx, event) { + self.more_info_showing = true; + } + if let Some(BldHeaderMsg::Cancelled) = self.more_info.header.event(ctx, event) { + self.more_info_showing = false; + } + + if let Event::BLE(BLEEvent::PairingRequest(code)) = event { + return Some(PairingMsg::Pairing(code)); + } + if let Event::BLE(BLEEvent::PairingCanceled) = event { + return Some(PairingMsg::Cancel); + } + if let Event::BLE(BLEEvent::Disconnected) = event { + return Some(PairingMsg::Cancel); + } + None + } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + if self.more_info_showing { + self.more_info.header.render(target); + self.more_info.instruction_primary.render(target); + self.more_info.instruction_secondary.render(target); + self.more_info.action_bar.render(target); + } else { + self.header.render(target); + self.instruction.render(target); + self.action_bar.render(target); + self.render_qr_code(target); + } + } +} diff --git a/core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.png b/core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.png new file mode 100644 index 0000000000..4f1122b366 Binary files /dev/null and b/core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.png differ diff --git a/core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.toif b/core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.toif new file mode 100644 index 0000000000..41e99d6352 Binary files /dev/null and b/core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.toif differ 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 bc8ce0b400..4f6cfae2b1 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs @@ -9,8 +9,8 @@ use super::{ component::{ButtonStyle, ButtonStyleSheet}, fonts, }, - BLACK, BLUE, GREEN_LIGHT, GREY, GREY_DARK, GREY_EXTRA_LIGHT, GREY_LIGHT, GREY_SUPER_DARK, - ORANGE, RED, WHITE, + BLACK, BLUE, GREY, GREY_DARK, GREY_EXTRA_LIGHT, GREY_LIGHT, GREY_SUPER_DARK, ORANGE, RED, + WHITE, }; pub const BLD_BG: Color = BLACK; @@ -20,6 +20,7 @@ pub const WELCOME_COLOR: Color = BLACK; // UI icons specific to bootloader (white color) include_icon!(ICON_SEVEN, "layout_eckhart/res/bootloader/7.toif"); +// alternatively: "layout_eckhart/res/bootloader/QRCode_smaller.toif" include_icon!( ICON_QR_TREZOR_IO_START, "layout_eckhart/res/bootloader/QRCode.toif" @@ -198,12 +199,11 @@ pub fn button_welcome_screen() -> ButtonStyleSheet { icon_color: GREY_EXTRA_LIGHT, background_color: BLD_BG, }, - // used for the "Trezor is paired" button/footer disabled: &ButtonStyle { font: fonts::FONT_SATOSHI_MEDIUM_26, - text_color: GREEN_LIGHT, + text_color: GREY, button_color: BLD_BG, - icon_color: GREEN_LIGHT, + icon_color: GREY, background_color: BLD_FG, }, } diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs b/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs index f97a79152e..ed1f4ebfe9 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs @@ -17,7 +17,7 @@ use ufmt::uwrite; use super::{ bootloader::{ BldActionBar, BldHeader, BldHeaderMsg, BldMenuScreen, BldTextScreen, BldWelcomeScreen, - ConnectScreen, + ConnectScreen, WirelessSetupScreen, }, component::Button, cshape::{render_loader, ScreenBorder}, @@ -94,7 +94,7 @@ pub enum BootloaderLayout { #[cfg(feature = "ble")] PairingMode(PairingModeScreen), #[cfg(feature = "ble")] - WirelessSetup(PairingModeScreen), + WirelessSetup(WirelessSetupScreen), #[cfg(feature = "ble")] WirelessSetupFinal(ConnectScreen), } @@ -109,7 +109,7 @@ impl BootloaderLayoutType for BootloaderLayout { BootloaderLayout::PairingMode(f) => process_frame_event::(f, event), #[cfg(feature = "ble")] BootloaderLayout::WirelessSetup(f) => { - process_frame_event::(f, event) + process_frame_event::(f, event) } #[cfg(feature = "ble")] BootloaderLayout::WirelessSetupFinal(f) => { @@ -169,11 +169,7 @@ impl BootloaderLayoutType for BootloaderLayout { #[cfg(feature = "ble")] fn init_wireless_setup(name: &'static str) -> Self { - // todo implement correct UI - let btn = Button::with_text("Cancel".into()).styled(button_default()); - - let screen = PairingModeScreen::new("QR_CODE".into(), name.into()) - .with_action_bar(BldActionBar::new_single(btn)); + let screen = WirelessSetupScreen::new(name.into()); Self::WirelessSetup(screen) } }