1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-23 15:08:19 +00:00

feat(eckhart): bld wireless setup screen

[no changelog]
This commit is contained in:
obrusvit 2025-07-10 00:18:01 +02:00 committed by Vít Obrusník
parent cf1e892d13
commit 0e2b4f4d5c
6 changed files with 183 additions and 13 deletions

View File

@ -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;

View File

@ -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<Self::Msg> {
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);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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,
},
}

View File

@ -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::<PairingModeScreen>(f, event),
#[cfg(feature = "ble")]
BootloaderLayout::WirelessSetup(f) => {
process_frame_event::<PairingModeScreen>(f, event)
process_frame_event::<WirelessSetupScreen>(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)
}
}