mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-08-01 19:38:33 +00:00
WIP - bip39 design improvements
This commit is contained in:
parent
f1536c602b
commit
3434838bbe
@ -515,7 +515,13 @@ impl<T: Clone + AsRef<str>> ButtonDetails<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Duration of the hold-to-confirm.
|
/// Default duration of the hold-to-confirm.
|
||||||
|
pub fn with_default_duration(mut self) -> Self {
|
||||||
|
self.duration = Some(Duration::from_millis(1000));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specific duration of the hold-to-confirm.
|
||||||
pub fn with_duration(mut self, duration: Duration) -> Self {
|
pub fn with_duration(mut self, duration: Duration) -> Self {
|
||||||
self.duration = Some(duration);
|
self.duration = Some(duration);
|
||||||
self
|
self
|
||||||
|
@ -83,6 +83,21 @@ pub fn paint_header_left<T: AsRef<str>>(title: T, area: Rect) -> i16 {
|
|||||||
text_heigth
|
text_heigth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display title/header centered at the top of the given area.
|
||||||
|
/// Returning the painted height of the whole header.
|
||||||
|
pub fn paint_header_centered<T: AsRef<str>>(title: T, area: Rect) -> i16 {
|
||||||
|
let text_heigth = theme::FONT_HEADER.text_height();
|
||||||
|
let title_baseline = area.top_center() + Offset::y(text_heigth);
|
||||||
|
display::text_center(
|
||||||
|
title_baseline,
|
||||||
|
title.as_ref(),
|
||||||
|
theme::FONT_HEADER,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
);
|
||||||
|
text_heigth
|
||||||
|
}
|
||||||
|
|
||||||
/// Draws icon and text on the same line - icon on the left.
|
/// Draws icon and text on the same line - icon on the left.
|
||||||
pub fn icon_with_text<T: AsRef<str>>(baseline: Point, icon: Icon, text: T, font: Font) {
|
pub fn icon_with_text<T: AsRef<str>>(baseline: Point, icon: Icon, text: T, font: Font) {
|
||||||
icon.draw_bottom_left(baseline, theme::FG, theme::BG);
|
icon.draw_bottom_left(baseline, theme::FG, theme::BG);
|
||||||
|
@ -5,9 +5,12 @@ use crate::ui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Component for holding another component and displaying a title.
|
/// Component for holding another component and displaying a title.
|
||||||
|
/// Also is allocating space for a scrollbar.
|
||||||
pub struct Frame<T, U> {
|
pub struct Frame<T, U> {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
title: U,
|
title: U,
|
||||||
|
title_centered: bool,
|
||||||
|
account_for_scrollbar: bool,
|
||||||
content: Child<T>,
|
content: Child<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +23,8 @@ where
|
|||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
area: Rect::zero(),
|
area: Rect::zero(),
|
||||||
|
title_centered: false,
|
||||||
|
account_for_scrollbar: true,
|
||||||
content: Child::new(content),
|
content: Child::new(content),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,6 +32,20 @@ where
|
|||||||
pub fn inner(&self) -> &T {
|
pub fn inner(&self) -> &T {
|
||||||
self.content.inner()
|
self.content.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Aligning the title to the center, instead of the left.
|
||||||
|
/// Also disabling scrollbar in this case, as they are not compatible.
|
||||||
|
pub fn with_title_center(mut self, title_centered: bool) -> Self {
|
||||||
|
self.title_centered = title_centered;
|
||||||
|
self.account_for_scrollbar = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocating space for scrollbar in the top right. True by default.
|
||||||
|
pub fn with_scrollbar(mut self, account_for_scrollbar: bool) -> Self {
|
||||||
|
self.account_for_scrollbar = account_for_scrollbar;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Component for Frame<T, U>
|
impl<T, U> Component for Frame<T, U>
|
||||||
@ -43,10 +62,16 @@ where
|
|||||||
bounds.split_top(theme::FONT_HEADER.line_height());
|
bounds.split_top(theme::FONT_HEADER.line_height());
|
||||||
let content_area = content_area.inset(Insets::top(TITLE_SPACE));
|
let content_area = content_area.inset(Insets::top(TITLE_SPACE));
|
||||||
|
|
||||||
let (title_area, scrollbar_area) =
|
// Title area is different based on scrollbar.
|
||||||
title_and_scrollbar_area.split_right(ScrollBar::MAX_WIDTH);
|
let title_area = if self.account_for_scrollbar {
|
||||||
|
let (title_area, scrollbar_area) =
|
||||||
self.content.set_scrollbar_area(scrollbar_area);
|
title_and_scrollbar_area.split_right(ScrollBar::MAX_WIDTH);
|
||||||
|
// Sending the scrollbar area to the child component.
|
||||||
|
self.content.set_scrollbar_area(scrollbar_area);
|
||||||
|
title_area
|
||||||
|
} else {
|
||||||
|
title_and_scrollbar_area
|
||||||
|
};
|
||||||
|
|
||||||
self.area = title_area;
|
self.area = title_area;
|
||||||
self.content.place(content_area);
|
self.content.place(content_area);
|
||||||
@ -58,7 +83,11 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
common::paint_header_left(&self.title, self.area);
|
if self.title_centered {
|
||||||
|
common::paint_header_centered(&self.title, self.area);
|
||||||
|
} else {
|
||||||
|
common::paint_header_left(&self.title, self.area);
|
||||||
|
}
|
||||||
self.content.paint();
|
self.content.paint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
use crate::{
|
use crate::ui::{
|
||||||
time::Duration,
|
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||||
ui::{
|
display::Icon,
|
||||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
geometry::Rect,
|
||||||
display::Icon,
|
model_tr::theme,
|
||||||
geometry::Rect,
|
util::char_to_string,
|
||||||
model_tr::theme,
|
|
||||||
util::char_to_string,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -31,7 +28,6 @@ enum ChoiceCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MAX_PASSPHRASE_LENGTH: usize = 50;
|
const MAX_PASSPHRASE_LENGTH: usize = 50;
|
||||||
const HOLD_DURATION: Duration = Duration::from_secs(1);
|
|
||||||
|
|
||||||
const DIGITS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
const DIGITS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
const LOWERCASE_LETTERS: [char; 26] = [
|
const LOWERCASE_LETTERS: [char; 26] = [
|
||||||
@ -115,14 +111,10 @@ impl ChoiceFactoryPassphrase {
|
|||||||
// Including accept button on the left and cancel on the very right.
|
// Including accept button on the left and cancel on the very right.
|
||||||
// TODO: could have some icons instead of the shortcut text
|
// TODO: could have some icons instead of the shortcut text
|
||||||
if choice_index == 0 {
|
if choice_index == 0 {
|
||||||
menu_item.set_left_btn(Some(
|
menu_item.set_left_btn(Some(ButtonDetails::text("ACC").with_default_duration()));
|
||||||
ButtonDetails::text("ACC").with_duration(HOLD_DURATION),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if choice_index == MENU.len() as u8 - 1 {
|
if choice_index == MENU.len() as u8 - 1 {
|
||||||
menu_item.set_right_btn(Some(
|
menu_item.set_right_btn(Some(ButtonDetails::text("CAN").with_default_duration()));
|
||||||
ButtonDetails::text("CAN").with_duration(HOLD_DURATION),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Including icons for some items.
|
// Including icons for some items.
|
||||||
|
@ -175,7 +175,7 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
|||||||
// Optional HoldToConfirm
|
// Optional HoldToConfirm
|
||||||
if hold {
|
if hold {
|
||||||
// TODO: clients might want to set the duration
|
// TODO: clients might want to set the duration
|
||||||
confirm_btn = confirm_btn.map(|btn| btn.with_duration(Duration::from_secs(2)));
|
confirm_btn = confirm_btn.map(|btn| btn.with_default_duration());
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = ButtonPage::new_str_buf(
|
let content = ButtonPage::new_str_buf(
|
||||||
@ -255,8 +255,7 @@ extern "C" fn confirm_properties(n_args: usize, args: *const Obj, kwargs: *mut M
|
|||||||
|
|
||||||
let mut content = ButtonPage::new_str(paragraphs.into_paragraphs(), theme::BG);
|
let mut content = ButtonPage::new_str(paragraphs.into_paragraphs(), theme::BG);
|
||||||
if hold {
|
if hold {
|
||||||
let confirm_btn =
|
let confirm_btn = Some(ButtonDetails::text("CONFIRM").with_default_duration());
|
||||||
Some(ButtonDetails::text("CONFIRM").with_duration(Duration::from_secs(1)));
|
|
||||||
content = content.with_confirm_btn(confirm_btn);
|
content = content.with_confirm_btn(confirm_btn);
|
||||||
}
|
}
|
||||||
let obj = LayoutObj::new(Frame::new(title, content))?;
|
let obj = LayoutObj::new(Frame::new(title, content))?;
|
||||||
@ -298,10 +297,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
|
|||||||
let btn_layout = ButtonLayout::new(
|
let btn_layout = ButtonLayout::new(
|
||||||
Some(ButtonDetails::cancel_icon()),
|
Some(ButtonDetails::cancel_icon()),
|
||||||
None,
|
None,
|
||||||
Some(
|
Some(ButtonDetails::text("HOLD TO CONFIRM").with_default_duration()),
|
||||||
ButtonDetails::text("HOLD TO CONFIRM")
|
|
||||||
.with_duration(Duration::from_secs(2)),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
let btn_actions = ButtonActions::cancel_confirm();
|
let btn_actions = ButtonActions::cancel_confirm();
|
||||||
Page::<20>::new(btn_layout, btn_actions, Font::MONO)
|
Page::<20>::new(btn_layout, btn_actions, Font::MONO)
|
||||||
@ -342,7 +338,7 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
|
|||||||
let btn_layout = ButtonLayout::new(
|
let btn_layout = ButtonLayout::new(
|
||||||
Some(ButtonDetails::cancel_icon()),
|
Some(ButtonDetails::cancel_icon()),
|
||||||
None,
|
None,
|
||||||
Some(ButtonDetails::text("HOLD TO SEND").with_duration(Duration::from_secs(2))),
|
Some(ButtonDetails::text("HOLD TO SEND").with_default_duration()),
|
||||||
);
|
);
|
||||||
let btn_actions = ButtonActions::cancel_confirm();
|
let btn_actions = ButtonActions::cancel_confirm();
|
||||||
|
|
||||||
@ -523,8 +519,7 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
|
|||||||
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
|
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
|
||||||
let share_words: Vec<StrBuffer, 24> = iter_into_vec(share_words_obj)?;
|
let share_words: Vec<StrBuffer, 24> = iter_into_vec(share_words_obj)?;
|
||||||
|
|
||||||
let confirm_btn =
|
let confirm_btn = Some(ButtonDetails::text("HOLD TO CONFIRM").with_default_duration());
|
||||||
Some(ButtonDetails::text("HOLD TO CONFIRM").with_duration(Duration::from_secs(1)));
|
|
||||||
|
|
||||||
let obj = LayoutObj::new(
|
let obj = LayoutObj::new(
|
||||||
ButtonPage::new_str(ShareWords::new(share_words), theme::BG)
|
ButtonPage::new_str(ShareWords::new(share_words), theme::BG)
|
||||||
@ -564,7 +559,7 @@ extern "C" fn request_word_bip39(n_args: usize, args: *const Obj, kwargs: *mut M
|
|||||||
let block = |_args: &[Obj], kwargs: &Map| {
|
let block = |_args: &[Obj], kwargs: &Map| {
|
||||||
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||||
|
|
||||||
let obj = LayoutObj::new(Frame::new(prompt, Bip39Entry::new()))?;
|
let obj = LayoutObj::new(Frame::new(prompt, Bip39Entry::new()).with_title_center(true))?;
|
||||||
Ok(obj.into())
|
Ok(obj.into())
|
||||||
};
|
};
|
||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
@ -66,7 +66,7 @@ async def _continue_recovery_process(ctx: GenericContext) -> Success:
|
|||||||
# If we are starting recovery, ask for word count first...
|
# If we are starting recovery, ask for word count first...
|
||||||
# _request_word_count
|
# _request_word_count
|
||||||
await layout.homescreen_dialog(
|
await layout.homescreen_dialog(
|
||||||
ctx, "Select", "Select the number of words in your recovery seed"
|
ctx, "Select", "Select the number of words in your recovery seed."
|
||||||
)
|
)
|
||||||
# ask for the number of words
|
# ask for the number of words
|
||||||
word_count = await layout.request_word_count(ctx, dry_run)
|
word_count = await layout.request_word_count(ctx, dry_run)
|
||||||
|
@ -419,11 +419,12 @@ async def get_bool(
|
|||||||
ctx: wire.GenericContext,
|
ctx: wire.GenericContext,
|
||||||
br_type: str,
|
br_type: str,
|
||||||
title: str,
|
title: str,
|
||||||
data: str,
|
data: str | None = None,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
verb: str | None = "CONFIRM",
|
verb: str | None = "CONFIRM",
|
||||||
verb_cancel: str | None = "",
|
verb_cancel: str | None = "",
|
||||||
hold: bool = False,
|
hold: bool = False,
|
||||||
|
reverse: bool = False,
|
||||||
br_code: ButtonRequestType = ButtonRequestType.Other,
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
result = await interact(
|
result = await interact(
|
||||||
@ -436,7 +437,7 @@ async def get_bool(
|
|||||||
verb=verb,
|
verb=verb,
|
||||||
verb_cancel=verb_cancel,
|
verb_cancel=verb_cancel,
|
||||||
hold=hold,
|
hold=hold,
|
||||||
reverse=False,
|
reverse=reverse,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
br_type,
|
br_type,
|
||||||
@ -526,7 +527,7 @@ async def confirm_reset_device(
|
|||||||
if show_tutorial:
|
if show_tutorial:
|
||||||
await tutorial(ctx)
|
await tutorial(ctx)
|
||||||
|
|
||||||
to_show = "By continuing you agree to our terms and conditions.\n\nMore info at trezor.io/tos."
|
to_show = "By continuing you agree to our terms and conditions.\nMore info at trezor.io/tos."
|
||||||
if not recovery:
|
if not recovery:
|
||||||
to_show += "\nUse you backup to recover your wallet."
|
to_show += "\nUse you backup to recover your wallet."
|
||||||
|
|
||||||
|
@ -73,11 +73,20 @@ async def continue_recovery(
|
|||||||
info_func: Callable | None,
|
info_func: Callable | None,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
# NOTE: no need to implement `info_func`, as it is used only in
|
||||||
|
# Shamir backup, which is not implemented for TR.
|
||||||
|
|
||||||
|
description = text
|
||||||
|
if subtext:
|
||||||
|
description += f"\n\n{subtext}"
|
||||||
return await get_bool(
|
return await get_bool(
|
||||||
ctx=ctx,
|
ctx,
|
||||||
title="START RECOVERY",
|
"recovery",
|
||||||
data=f"{text}\n\n{subtext or ''}",
|
"START RECOVERY",
|
||||||
verb="START",
|
None,
|
||||||
br_type="recovery",
|
description,
|
||||||
|
verb="HOLD TO BEGIN",
|
||||||
|
hold=True,
|
||||||
|
reverse=True,
|
||||||
br_code=ButtonRequestType.RecoveryHomepage,
|
br_code=ButtonRequestType.RecoveryHomepage,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user