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

WIP - fixes, wording, tutorial

This commit is contained in:
grdddj 2022-11-04 11:19:43 +01:00
parent 51a65b9c26
commit 53f5122c73
11 changed files with 151 additions and 96 deletions

View File

@ -8,12 +8,10 @@ use heapless::LinearMap;
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never}, component::{Component, Event, EventCtx, Never},
display::{Color, Font}, display::{Color, Font},
geometry::{Rect}, geometry::Rect,
}; };
use super::layout::{ use super::layout::{LayoutFit, LayoutSink, LineBreaking, Op, TextLayout, TextRenderer, TextStyle};
LayoutFit, LayoutSink, LineBreaking, Op, TextLayout, TextRenderer, TextStyle,
};
pub const MAX_ARGUMENTS: usize = 6; pub const MAX_ARGUMENTS: usize = 6;

View File

@ -1,8 +1,13 @@
use crate::{ui::{constant, geometry::{Point, Offset, Rect}}, trezorhal::display}; use crate::{
trezorhal::display,
ui::{
constant,
geometry::{Offset, Point, Rect},
},
};
use core::slice; use core::slice;
use super::{Color, get_color_table, pixeldata, set_window, get_offset}; use super::{get_color_table, get_offset, pixeldata, set_window, Color};
pub struct Glyph { pub struct Glyph {
pub width: i16, pub width: i16,

View File

@ -44,7 +44,7 @@ where
T: TryFrom<Obj, Error = Error>, T: TryFrom<Obj, Error = Error>,
{ {
let err = Error::ValueError(cstr!("Invalid iterable length")); let err = Error::ValueError(cstr!("Invalid iterable length"));
let vec: Vec<T, N> = iter_into_vec(iterable)?; let vec: Vec<T, N> = iter_into_vec(iterable)?;
// Returns error if array.len() != N // Returns error if array.len() != N
vec.into_array().map_err(|_| err) vec.into_array().map_err(|_| err)
} }

View File

@ -576,6 +576,15 @@ impl ButtonLayout<&'static str> {
) )
} }
/// Left and right texts.
pub fn left_right_text(text_left: &'static str, text_right: &'static str) -> Self {
Self::new(
Some(ButtonDetails::text(text_left)),
None,
Some(ButtonDetails::text(text_right)),
)
}
/// Left and right arrow icons for navigation. /// Left and right arrow icons for navigation.
pub fn left_right_arrows() -> Self { pub fn left_right_arrows() -> Self {
Self::new( Self::new(
@ -611,6 +620,29 @@ impl ButtonLayout<&'static str> {
Some(ButtonDetails::text(text).with_duration(duration)), Some(ButtonDetails::text(text).with_duration(duration)),
) )
} }
/// Arrow back on left and hold-to-confirm text on the right.
pub fn back_and_htc_text(text: &'static str, duration: Duration) -> Self {
Self::new(
Some(ButtonDetails::left_arrow_icon()),
None,
Some(ButtonDetails::text(text).with_duration(duration)),
)
}
/// Arrow back on left and text on the right.
pub fn back_and_text(text: &'static str) -> Self {
Self::new(
Some(ButtonDetails::left_arrow_icon()),
None,
Some(ButtonDetails::text(text)),
)
}
/// Only armed text in the middle.
pub fn middle_armed_text(text: &'static str) -> Self {
Self::new(None, Some(ButtonDetails::armed_text(text)), None)
}
} }
/// What happens when a button is triggered. /// What happens when a button is triggered.

View File

@ -1,7 +1,8 @@
use super::button::{Button, ButtonMsg::Clicked}; use super::button::{Button, ButtonMsg::Clicked};
use crate::ui::{ use crate::ui::{
component::{Child, Component, Event, EventCtx}, component::{Child, Component, Event, EventCtx},
geometry::Rect, model_tr::theme, geometry::Rect,
model_tr::theme,
}; };
pub enum DialogMsg<T> { pub enum DialogMsg<T> {

View File

@ -167,7 +167,8 @@ impl<T: AsRef<str>> Loader<T> {
// TODO: support painting icons // TODO: support painting icons
if let Some(text_overlay) = &mut self.text_overlay { if let Some(text_overlay) = &mut self.text_overlay {
// NOTE: need to calculate this in `i32`, it would overflow using `i16` // NOTE: need to calculate this in `i32`, it would overflow using `i16`
let invert_from = ((self.area.width() as i32 + 1) * done) / (display::LOADER_MAX as i32); let invert_from =
((self.area.width() as i32 + 1) * done) / (display::LOADER_MAX as i32);
// TODO: the text should be moved one pixel to the top so it is centered in the // TODO: the text should be moved one pixel to the top so it is centered in the
// loader // loader

View File

@ -36,6 +36,7 @@ pub use choice_item::ChoiceItem;
pub use dialog::{Dialog, DialogMsg}; pub use dialog::{Dialog, DialogMsg};
pub use flow::{Flow, FlowMsg}; pub use flow::{Flow, FlowMsg};
pub use flow_pages::{FlowPages, Page}; pub use flow_pages::{FlowPages, Page};
pub use flow_pages_poc_helpers::LineAlignment;
pub use frame::Frame; pub use frame::Frame;
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet}; pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
pub use page::ButtonPage; pub use page::ButtonPage;

View File

@ -28,6 +28,7 @@ use crate::{
util::iter_into_vec, util::iter_into_vec,
util::upy_disable_animation, util::upy_disable_animation,
}, },
model_tr::component::LineAlignment,
}, },
}; };
@ -305,6 +306,34 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
/// General pattern of most tutorial screens.
/// (title, text, btn_layout, btn_actions)
fn tutorial_screen(
data: (
StrBuffer,
StrBuffer,
ButtonLayout<&'static str>,
ButtonActions,
),
) -> Page<10> {
let (title, text, btn_layout, btn_actions) = data;
let mut page = Page::<10>::new(
btn_layout,
btn_actions,
if !title.is_empty() {
Font::BOLD
} else {
Font::MONO
},
);
// Add title if present
if !title.is_empty() {
page = page.text_bold(title).newline().newline_half()
}
page = page.text_mono(text);
page
}
extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], _kwargs: &Map| { let block = |_args: &[Obj], _kwargs: &Map| {
const PAGE_COUNT: u8 = 7; const PAGE_COUNT: u8 = 7;
@ -315,83 +344,72 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
// Cancelling the first screen will point to the last one, // Cancelling the first screen will point to the last one,
// which asks for confirmation whether user wants to // which asks for confirmation whether user wants to
// really cancel the tutorial. // really cancel the tutorial.
let screen = match page_index { match page_index {
// title, text, btn_layout, btn_actions // title, text, btn_layout, btn_actions
0 => ( 0 => {
"HELLO", tutorial_screen((
"Welcome to Trezor.\nPress right to continue.", "HELLO".into(),
ButtonLayout::cancel_and_arrow(), "Welcome to Trezor.\nPress right to continue.".into(),
ButtonActions::last_next(), ButtonLayout::cancel_and_arrow(),
), ButtonActions::last_next(),
1 => ( ))
"", },
"Use Trezor by clicking left & right.\n\nContinue right.", 1 => {
ButtonLayout::left_right_arrows(), tutorial_screen((
ButtonActions::prev_next(), "".into(),
), "Use Trezor by clicking left and right.\n\nContinue right.".into(),
2 => ( ButtonLayout::left_right_arrows(),
"HOLD TO CONFIRM", ButtonActions::prev_next(),
"Press & hold right to approve important operations.", ))
ButtonLayout::new( },
Some(ButtonDetails::left_arrow_icon()), 2 => {
None, tutorial_screen((
Some( "HOLD TO CONFIRM".into(),
ButtonDetails::text("HOLD TO CONFIRM") "Press and hold right to approve important operations.".into(),
.with_duration(Duration::from_millis(2000)), ButtonLayout::back_and_htc_text("HOLD TO CONFIRM", Duration::from_millis(1000)),
), ButtonActions::prev_next(),
), ))
ButtonActions::prev_next(), },
), 3 => {
3 => ( tutorial_screen((
"SCREEN SCROLL", "SCREEN SCROLL".into(),
"Press right to scroll down to read all content when text\ndoesn't fit on one screen. Press left to scroll up.", "Press right to scroll down to read all content when text\ndoesn't fit on one screen. Press left to scroll up.".into(),
ButtonLayout::new( ButtonLayout::back_and_text("GOT IT"),
Some(ButtonDetails::left_arrow_icon()), ButtonActions::prev_next(),
None, ))
Some(ButtonDetails::text("GOT IT")), ), },
ButtonActions::prev_next(), 4 => {
), tutorial_screen((
4 => ( "CONFIRM".into(),
"CONFIRM", "Press both left and right at the same time to confirm.".into(),
"Press both left & right at the same time to confirm.", ButtonLayout::middle_armed_text("CONFIRM"),
ButtonLayout::new( ButtonActions::prev_next_with_middle(),
Some(ButtonDetails::left_arrow_icon()), ))
Some(ButtonDetails::armed_text("CONFIRM")), },
None, // This page is special
), 5 => {
ButtonActions::prev_next_with_middle(), Page::<10>::new(
), ButtonLayout::left_right_text("AGAIN", "FINISH"),
5 => ( ButtonActions::beginning_confirm(),
"CONGRATS!", Font::MONO,
"You're ready to use Trezor.", )
ButtonLayout::new( .newline()
Some(ButtonDetails::text("AGAIN")), .text_mono("Tutorial complete.".into())
None, .newline()
Some(ButtonDetails::text("FINISH")), .newline()
), .alignment(LineAlignment::Center)
ButtonActions::beginning_confirm(), .text_bold("You're ready to\nuse Trezor.".into())
), },
6 => ( 6 => {
"SKIP TUTORIAL", tutorial_screen((
"Sure you want to skip the tutorial?", "SKIP TUTORIAL".into(),
ButtonLayout::new( "Are you sure you want to skip the tutorial?".into(),
Some(ButtonDetails::left_arrow_icon()), ButtonLayout::cancel_and_text("SKIP"),
None, ButtonActions::beginning_cancel(),
Some(ButtonDetails::text("SKIP")), ))
), },
ButtonActions::beginning_cancel(),
),
_ => unreachable!(), _ => unreachable!(),
};
let mut page = Page::<10>::new(screen.2.clone(), screen.3.clone(), Font::BOLD);
// Add title if present
if !screen.0.is_empty() {
page = page.text_bold(screen.0.into()).newline().newline_half()
} }
page = page.text_mono(screen.1.into());
page
}; };
let pages = FlowPages::new(get_page, PAGE_COUNT); let pages = FlowPages::new(get_page, PAGE_COUNT);
@ -532,6 +550,7 @@ extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?; let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
let words: Vec<StrBuffer, 3> = iter_into_vec(words_iterable)?; let words: Vec<StrBuffer, 3> = iter_into_vec(words_iterable)?;
// TODO: should return int, to be consistent with TT's select_word
let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(words).into_child()))?; let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(words).into_child()))?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -660,9 +679,8 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// *, /// *,
/// title: str, /// title: str,
/// words: Iterable[str], /// words: Iterable[str],
/// ) -> int: /// ) -> str:
/// """Select mnemonic word from three possibilities - seed check after backup. The /// """Select a word from a list. TODO: should return int, to be consistent with TT's select_word"""
/// iterable must be of exact size. Returns index in range `0..3`."""
Qstr::MP_QSTR_select_word => obj_fn_kw!(0, select_word).as_obj(), Qstr::MP_QSTR_select_word => obj_fn_kw!(0, select_word).as_obj(),
/// def request_word_count( /// def request_word_count(

View File

@ -121,9 +121,8 @@ def select_word(
*, *,
title: str, title: str,
words: Iterable[str], words: Iterable[str],
) -> int: ) -> str:
"""Select mnemonic word from three possibilities - seed check after backup. The """Select a word from a list. TODO: should return int, to be consistent with TT's select_word"""
iterable must be of exact size. Returns index in range `0..3`."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs

View File

@ -561,8 +561,8 @@ async def confirm_reset_device(
return await _placeholder_confirm( return await _placeholder_confirm(
ctx=ctx, ctx=ctx,
br_type="recover_device" if recovery else "setup_device", br_type="recover_device" if recovery else "setup_device",
title="RECOVERY MODE" if recovery else "CREATE NEW WALLET", title="START RECOVERY" if recovery else "CREATE NEW WALLET",
data="By continuing you agree to trezor.io/tos", data="By continuing you agree to our terms and conditions.\nSee trezor.io/tos.",
description=prompt, description=prompt,
br_code=ButtonRequestType.ProtectCall br_code=ButtonRequestType.ProtectCall
if recovery if recovery

View File

@ -51,10 +51,10 @@ async def select_word(
) )
) )
) )
if __debug__ and isinstance(result, str): for word in words:
return result if word.upper() == result:
assert isinstance(result, int) and 0 <= result <= 2 return word
return words[result] raise ValueError("Invalid word")
async def slip39_show_checklist( async def slip39_show_checklist(