From 0e2b4f4d5cc3a6de401d96bc8bb4335fe395dd11 Mon Sep 17 00:00:00 2001 From: obrusvit Date: Thu, 10 Jul 2025 00:18:01 +0200 Subject: [PATCH] feat(eckhart): bld wireless setup screen [no changelog] --- .../src/ui/layout_eckhart/bootloader/mod.rs | 4 + .../bootloader/wireless_setup_screen.rs | 170 ++++++++++++++++++ .../res/bootloader/QRCode_smaller.png | Bin 0 -> 1602 bytes .../res/bootloader/QRCode_smaller.toif | Bin 0 -> 1408 bytes .../src/ui/layout_eckhart/theme/bootloader.rs | 10 +- .../src/ui/layout_eckhart/ui_bootloader.rs | 12 +- 6 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 core/embed/rust/src/ui/layout_eckhart/bootloader/wireless_setup_screen.rs create mode 100644 core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.png create mode 100644 core/embed/rust/src/ui/layout_eckhart/res/bootloader/QRCode_smaller.toif 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 0000000000000000000000000000000000000000..4f1122b3665a7cead066dd04fc5f0d6ba8333740 GIT binary patch literal 1602 zcmV-I2EF--P)zaY4GM^9dkTO0(DIs^6`k2K3GwA)dbFB5l5G%r$RQq^#!i zwAJs$TqVutj5+4l;XL!nA0gcW2$-U6+e4ORS!s6w)wXS`vMhT8W^LQH_r8^+`RjP{ z(K(-df9T=~gk$y%Xn>V4+fTg&*1+_CaX!sGrmo$muvfhWiu>=I;}-KF((jn?*1HHx z!~%mq)2Wr**=th)PvSptp4*%K9%SHr1C$yT>uAm~2b#0a#~btOa2_DNKcbH%Yl|fv z6I%sf4L^8qv7m|uLsRfQ)|2al0}IGmcRe*ihq_|gt#oV0$$0MgX?I$&YS zUt`<$A5pLry9MVz30sFUPP=hTe(A6M;xV5VK;cW@ynFEiY>yE$Qy>c9r;aSnoF%8Z z_f1a=YhD)gH<{DfYrqu#Rsv@?nO^of?oDRm!-hWE4PQEePR)_hwkX_OA#8yfz=yL4 zKu|$Qf3i`k%uoJ$O5326mF zkU%C!-Fj~9kVeKsy1BmQPhWbAc}QKwQrK%%5f(LNX>V3Knln?jNa@uw5LZWW<=CM; zc#xxr_LYJtm31HHL@fh1VDV@I2nAFC3S?z9Q;~Wv>a@Yod+E*ThG+~RP3`b-7icO5 zT46!Xs5q5OIHrUsnQ#`LpzHS17hQic~(S%#|2?441jF|N?GPv znz}^10Kc?B8JWj(UIZ++Da{(#e|Uo4E^tWZwN4XI_Ws94&V=~9LcScsW`ZZW?O=abJ=f6Ku~r}-hy z8JhLx9HggCmOc}ilJ_`A?rc&+X|*H zzQv8F8K&WirK8h5lAKhSt2h=qqy76V)nOHkU#ukHQqRg`FI-Y!V&d;Ct6%GpC z_n?=U!+%c>tP9|2VVRXMSLs(9A3rf~+`1!TOTN$py~!_~#ammX zqi-HDcgMxW0+S-vANIWYLPsylrc>MWde<&JRk8Njd$IwsBTJ8oTS+5ggWGI@2++cz#Pcp0&to~% zh&T)jmg-}=DkXn^@L4yp;dkf)fRv7=FG^_|Uz3;oznSv=DT!r1p|$m6#20DW=i|m4 ztWrr7QfzxlS6$c8C6{#;ah^U8u%eR_BZLg07PQ?oi^#g%GaSZr5{$79zqIMT+RM z*k$NH>(3inh`V(dI9vwEUvtw^niju@O!F@H^loL7)NFhmdOn9f&CaBn7t@U`+#Ynv-J~g| z=i_1tYp$DW(^c2)riW=s$Ci`IDlu(5j~~Ek({WOJN?(*R2=>csK@RrG&n;aIg5ZRF zlYWCo$SZR^Ou?kQj|F+j(ANUH%Mfk5XpUk!uaB#-v+Fu*CF6Jt+v2j{=`nFV48sHB z154MaJSir_(9vrL#4ebisc^60P#)#RFdPKC3r`ldcro)xWj6r?xN)Ao#Enx5u(ZBUW7w4<}e!K)_s+5vR< zucd2YNCnYjLI|gmHY-Hm3|aYv-mKcD7jxp+#xG_rxh-hbrE}QuJO@sDo_7N;@=U6m zKB69Iy5@OE1$0S2Ksp9WX0D6QKvUCN8R#iJBHP-{bPx={)qr(^=Tf@(2W!EXOur)U z6I~1fZrm@EtRWUm)~t%gbJmKvNYoKZ1ZZJ@)24Z~lU}o)Gms-%dv29@zGLWyM;e3~ zbseibRH{Yv>q@EPp4M}4%Dcg=QqE(vR+h>|Gju4aIlVq%CUVrMWuv`a=tiXy5j~@> zVzqJw@ooKo1ME{DB3`IG!uQoZT;>(BLT%ys2GPdzQF~lFcr86KYcJW~v{meRnrzN% z&ug5uEHw3M)dbJbR{cwls=37R-)=ouQG!-nVC#^c@*M$dTArI_weq+{Fm O+-L#I{Apn?>Hh$l;?DK} literal 0 HcmV?d00001 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) } }