1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-23 23:08:14 +00:00

feat(core/rust): new design of wallet creation dialogs

[no changelog]
This commit is contained in:
grdddj 2023-06-26 10:50:28 +02:00 committed by Jiří Musil
parent 672d6b7d13
commit 03f77c50e9
20 changed files with 236 additions and 198 deletions

View File

@ -37,6 +37,7 @@ static void _librust_qstrs(void) {
MP_QSTR_case_sensitive;
MP_QSTR_confirm_action;
MP_QSTR_confirm_address;
MP_QSTR_confirm_backup;
MP_QSTR_confirm_blob;
MP_QSTR_confirm_coinjoin;
MP_QSTR_confirm_emphasized;

View File

@ -97,14 +97,13 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
};
}
// Drawing text
Op::Text(text) => {
Op::Text(text, continued) => {
// Try to fit text on the current page and if they do not fit,
// return the appropriate OutOfBounds message
// Inserting the ellipsis at the very beginning of the text if needed
// (just once for the first Op::Text on the non-first page).
self.layout.continues_from_prev_page =
skip_bytes > 0 && total_processed_chars == 0;
// (just for incomplete texts that were separated)
self.layout.continues_from_prev_page = continued;
let fit = self.layout.layout_text(text.as_ref(), cursor, sink);
@ -146,14 +145,16 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
let mut skipped = 0;
ops_iter.filter_map(move |op| {
match op {
Op::Text(text) if skipped < skip_bytes => {
Op::Text(text, _continued) if skipped < skip_bytes => {
let skip_text_bytes_if_fits_partially = skip_bytes - skipped;
skipped = skipped.saturating_add(text.as_ref().len());
if skipped > skip_bytes {
// Fits partially
// Skipping some bytes at the beginning, leaving rest
// Signifying that the text continues from previous page
Some(Op::Text(
text.skip_prefix(skip_text_bytes_if_fits_partially),
true,
))
} else {
// Does not fit at all
@ -184,15 +185,15 @@ impl<T: StringType + Clone> OpTextLayout<T> {
}
pub fn text(self, text: T) -> Self {
self.with_new_item(Op::Text(text))
self.with_new_item(Op::Text(text, false))
}
pub fn newline(self) -> Self {
self.with_new_item(Op::Text("\n".into()))
self.text("\n".into())
}
pub fn newline_half(self) -> Self {
self.with_new_item(Op::Text("\r".into()))
self.text("\r".into())
}
pub fn next_page(self) -> Self {
@ -238,7 +239,9 @@ impl<T: StringType + Clone> OpTextLayout<T> {
#[derive(Clone)]
pub enum Op<T: StringType> {
/// Render text with current color and font.
Text(T),
/// Bool signifies whether this is a split Text Op continued from previous
/// page. If true, a leading ellipsis will be rendered.
Text(T, bool),
/// Set current text color.
Color(Color),
/// Set currently used font.

View File

@ -567,6 +567,15 @@ where
)
}
/// Left text and WIDE right arrow.
pub fn text_none_arrow_wide(text: T) -> Self {
Self::new(
Some(ButtonDetails::text(text)),
None,
Some(ButtonDetails::down_arrow_icon_wide()),
)
}
/// Only right text.
pub fn none_none_text(text: T) -> Self {
Self::new(None, None, Some(ButtonDetails::text(text)))

View File

@ -39,10 +39,11 @@ where
{
pub fn new(pages: FlowPages<F, T>) -> Self {
let current_page = pages.get(0);
let title = current_page.title().map(Title::new);
Self {
pages,
current_page,
title: None,
title,
content_area: Rect::zero(),
title_area: Rect::zero(),
scrollbar: Child::new(ScrollBar::to_be_filled_later()),
@ -85,11 +86,11 @@ where
/// position.
fn change_current_page(&mut self, ctx: &mut EventCtx) {
self.current_page = self.pages.get(self.page_counter);
if self.title.is_some() {
if let Some(title) = self.current_page.title() {
self.title = Some(Title::new(title));
self.title.place(self.title_area);
}
if let Some(title) = self.current_page.title() {
self.title = Some(Title::new(title));
self.title.place(self.title_area);
} else {
self.title = None;
}
let scrollbar_active_index = self
.pages

View File

@ -25,9 +25,7 @@ where
cancel_btn_details: Option<ButtonDetails<U>>,
/// Right button of the last screen
confirm_btn_details: Option<ButtonDetails<U>>,
/// Left button of the last page
last_back_btn_details: Option<ButtonDetails<U>>,
/// Left button of every screen in the middle
/// Left button of every screen
back_btn_details: Option<ButtonDetails<U>>,
/// Right button of every screen apart the last one
next_btn_details: Option<ButtonDetails<U>>,
@ -47,8 +45,7 @@ where
pad: Pad::with_background(background).with_clear(),
cancel_btn_details: Some(ButtonDetails::cancel_icon()),
confirm_btn_details: Some(ButtonDetails::text("CONFIRM".into())),
back_btn_details: Some(ButtonDetails::up_arrow_icon_wide()),
last_back_btn_details: Some(ButtonDetails::up_arrow_icon()),
back_btn_details: Some(ButtonDetails::up_arrow_icon()),
next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()),
// Setting empty layout for now, we do not yet know the page count.
// Initial button layout will be set in `place()` after we can call
@ -122,18 +119,15 @@ where
}
fn get_button_layout(&self, has_prev: bool, has_next: bool) -> ButtonLayout<U> {
let btn_left = self.get_left_button_details(!has_prev, !has_next);
let btn_left = self.get_left_button_details(!has_prev);
let btn_right = self.get_right_button_details(has_next);
ButtonLayout::new(btn_left, None, btn_right)
}
/// Get the left button details, depending whether the page is first, last,
/// or in the middle.
fn get_left_button_details(&self, is_first: bool, is_last: bool) -> Option<ButtonDetails<U>> {
/// Get the left button details, depending whether the page is first or not.
fn get_left_button_details(&self, is_first: bool) -> Option<ButtonDetails<U>> {
if is_first {
self.cancel_btn_details.clone()
} else if is_last {
self.last_back_btn_details.clone()
} else {
self.back_btn_details.clone()
}

View File

@ -11,15 +11,15 @@ use crate::{
use heapless::{String, Vec};
use super::{common::display_left, scrollbar::SCROLLBAR_SPACE, theme, title::Title, ScrollBar};
use super::{common::display_left, scrollbar::SCROLLBAR_SPACE, theme, ScrollBar};
const WORDS_PER_PAGE: usize = 3;
const EXTRA_LINE_HEIGHT: i16 = 2;
const NUMBER_X_OFFSET: i16 = 5;
const NUMBER_WORD_OFFSET: i16 = 20;
const NUMBER_X_OFFSET: i16 = 0;
const WORD_X_OFFSET: i16 = 25;
const NUMBER_FONT: Font = Font::DEMIBOLD;
const WORD_FONT: Font = Font::BIG;
const INFO_TOP_OFFSET: i16 = 15;
const INFO_TOP_OFFSET: i16 = 20;
const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less
/// Showing the given share words.
@ -28,7 +28,6 @@ where
T: StringType,
{
area: Rect,
title: Child<Title<T>>,
scrollbar: Child<ScrollBar>,
share_words: Vec<T, MAX_WORDS>,
page_index: usize,
@ -38,10 +37,9 @@ impl<T> ShareWords<T>
where
T: StringType + Clone,
{
pub fn new(title: T, share_words: Vec<T, MAX_WORDS>) -> Self {
pub fn new(share_words: Vec<T, MAX_WORDS>) -> Self {
let mut instance = Self {
area: Rect::zero(),
title: Child::new(Title::new(title)),
scrollbar: Child::new(ScrollBar::to_be_filled_later()),
share_words,
page_index: 0,
@ -53,15 +51,7 @@ where
}
fn word_index(&self) -> usize {
(self.page_index - 2) * WORDS_PER_PAGE
}
fn is_entry_page(&self) -> bool {
self.page_index == 0
}
fn is_second_page(&self) -> bool {
self.page_index == 1
self.page_index * WORDS_PER_PAGE
}
fn is_final_page(&self) -> bool {
@ -74,36 +64,13 @@ where
} else {
self.share_words.len() / WORDS_PER_PAGE + 1
};
// Two pages before the words, one after it
2 + word_screens + 1
// One page after the words
word_screens + 1
}
fn get_first_text(&self) -> String<100> {
fn get_final_text(&self) -> String<50> {
build_string!(
100,
"Write down all ",
inttostr!(self.share_words.len() as u8),
" words in order."
)
}
/// Display the first page with user information.
fn paint_entry_page(&mut self) {
self.render_text_on_screen(&self.get_first_text(), Font::MONO);
}
fn get_second_text(&self) -> String<100> {
build_string!(100, "Do NOT make digital copies!")
}
/// Display the second page with user information.
fn paint_second_page(&mut self) {
self.render_text_on_screen(&self.get_second_text(), Font::MONO);
}
fn get_final_text(&self) -> String<100> {
build_string!(
100,
50,
"I wrote down all ",
inttostr!(self.share_words.len() as u8),
" words in order."
@ -112,15 +79,10 @@ where
/// Display the final page with user confirmation.
fn paint_final_page(&mut self) {
self.render_text_on_screen(&self.get_final_text(), Font::MONO);
}
/// Shows text in the main screen area.
fn render_text_on_screen(&self, text: &str, font: Font) {
text_multiline(
self.area.split_top(INFO_TOP_OFFSET).1,
text,
font,
&self.get_final_text(),
Font::NORMAL,
theme::FG,
theme::BG,
Alignment::Start,
@ -138,9 +100,10 @@ where
break;
}
let word = &self.share_words[index];
let baseline = self.area.top_left() + Offset::new(NUMBER_X_OFFSET, y_offset);
display_left(baseline, &inttostr!(index as u8 + 1), NUMBER_FONT);
display_left(baseline + Offset::x(NUMBER_WORD_OFFSET), &word, WORD_FONT);
let baseline = self.area.top_left() + Offset::y(y_offset);
let ordinal = build_string!(5, inttostr!(index as u8 + 1), ".");
display_left(baseline + Offset::x(NUMBER_X_OFFSET), &ordinal, NUMBER_FONT);
display_left(baseline + Offset::x(WORD_X_OFFSET), &word, WORD_FONT);
}
}
}
@ -152,12 +115,11 @@ where
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
let (title_area, _) = bounds.split_top(theme::FONT_HEADER.line_height());
let (top_area, _) = bounds.split_top(theme::FONT_HEADER.line_height());
let (title_area, scrollbar_area) =
title_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE);
let (_, scrollbar_area) =
top_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE);
self.title.place(title_area);
self.scrollbar.place(scrollbar_area);
self.area = bounds;
@ -165,7 +127,6 @@ where
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.title.event(ctx, event);
self.scrollbar.event(ctx, event);
None
}
@ -174,12 +135,7 @@ where
// Showing scrollbar in all cases
// Individual pages are responsible for not colliding with it
self.scrollbar.paint();
if self.is_entry_page() {
self.title.paint();
self.paint_entry_page();
} else if self.is_second_page() {
self.paint_second_page();
} else if self.is_final_page() {
if self.is_final_page() {
self.paint_final_page();
} else {
self.paint_words();
@ -211,19 +167,10 @@ where
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ShareWords");
let content = if self.is_entry_page() {
build_string!(
100,
self.title.inner().get_text(),
"\n",
&self.get_first_text()
)
} else if self.is_second_page() {
self.get_second_text()
} else if self.is_final_page() {
let content = if self.is_final_page() {
self.get_final_text()
} else {
let mut content = String::<100>::new();
let mut content = String::<50>::new();
for i in 0..WORDS_PER_PAGE {
let index = self.word_index() + i;
if index >= self.share_words.len() {
@ -231,7 +178,7 @@ where
}
let word = &self.share_words[index];
let current_line =
build_string!(20, inttostr!(index as u8 + 1), " ", word.as_ref(), "\n");
build_string!(50, inttostr!(index as u8 + 1), ". ", word.as_ref(), "\n");
unwrap!(content.push_str(&current_line));
}
content

View File

@ -413,9 +413,43 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
.text_normal("More info at".into())
.newline()
.text_bold("trezor.io/tos".into());
let formatted = FormattedText::new(ops);
let formatted = FormattedText::new(ops).vertically_centered();
content_in_button_page(title, formatted, button, None, false)
content_in_button_page(title, formatted, button, Some("".into()), false)
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_backup(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], _kwargs: &Map| {
let get_page = move |page_index| match page_index {
0 => {
let btn_layout = ButtonLayout::text_none_arrow_wide("SKIP".into());
let btn_actions = ButtonActions::cancel_none_next();
let ops = OpTextLayout::new(theme::TEXT_NORMAL)
.text_normal("New wallet created.".into())
.newline()
.newline()
.text_normal("It should be backed up now!".into());
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title("SUCCESS".into())
}
1 => {
let btn_layout = ButtonLayout::up_arrow_none_text("BACK UP".into());
let btn_actions = ButtonActions::prev_none_confirm();
let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(
"You can use your backup to recover your wallet at any time.".into(),
);
let formatted = FormattedText::new(ops).vertically_centered();
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
.with_title("BACK UP WALLET".into())
}
_ => unreachable!(),
};
let pages = FlowPages::new(get_page, 2);
let obj = LayoutObj::new(Flow::new(pages))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
@ -532,7 +566,6 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
let address_label: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_label)?.try_into()?;
let amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?;
let address_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_title)?.try_into()?;
let address_title_clone = address_title.clone();
let amount_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_title)?.try_into()?;
let get_page = move |page_index| {
@ -566,7 +599,7 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
};
let pages = FlowPages::new(get_page, 2);
let obj = LayoutObj::new(Flow::new(pages).with_common_title(address_title_clone))?;
let obj = LayoutObj::new(Flow::new(pages))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -669,11 +702,11 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
.line_breaking(LineBreaking::BreakWordsNoHyphen)
.text_mono(address.clone());
let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted)
Page::new(btn_layout, btn_actions, formatted).with_title(title.clone())
};
let pages = FlowPages::new(get_page, 1);
let obj = LayoutObj::new(Flow::new(pages).with_common_title(title))?;
let obj = LayoutObj::new(Flow::new(pages))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -688,7 +721,7 @@ fn tutorial_screen(
btn_actions: ButtonActions,
) -> Page<StrBuffer> {
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL).text_normal(text.into());
let formatted = FormattedText::new(ops).vertically_aligned(geometry::Alignment::Center);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(title.into())
}
@ -766,11 +799,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
let pages = FlowPages::new(get_page, PAGE_COUNT);
let obj = LayoutObj::new(
Flow::new(pages)
.with_scrollbar(false)
.with_common_title("HELLO".into()),
)?;
let obj = LayoutObj::new(Flow::new(pages).with_scrollbar(false))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1156,16 +1185,17 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map)
extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
let share_words: Vec<StrBuffer, 33> = iter_into_vec(share_words_obj)?;
let cancel_btn = Some(ButtonDetails::up_arrow_icon());
let confirm_btn = Some(
ButtonDetails::<StrBuffer>::text("HOLD TO CONFIRM".into()).with_default_duration(),
);
let obj = LayoutObj::new(
ButtonPage::new(ShareWords::new(title, share_words), theme::BG)
ButtonPage::new(ShareWords::new(share_words), theme::BG)
.with_cancel_btn(cancel_btn)
.with_confirm_btn(confirm_btn),
)?;
Ok(obj.into())
@ -1460,6 +1490,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Confirm TOS before device setup."""
Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, new_confirm_reset_device).as_obj(),
/// def confirm_backup() -> object:
/// """Strongly recommend user to do backup."""
Qstr::MP_QSTR_confirm_backup => obj_fn_kw!(0, new_confirm_backup).as_obj(),
/// def show_address_details(
/// *,
/// address: str,
@ -1649,7 +1683,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def show_share_words(
/// *,
/// title: str,
/// share_words: Iterable[str],
/// ) -> object:
/// """Shows a backup seed."""

View File

@ -75,6 +75,11 @@ def confirm_reset_device(
"""Confirm TOS before device setup."""
# rust/src/ui/model_tr/layout.rs
def confirm_backup() -> object:
"""Strongly recommend user to do backup."""
# rust/src/ui/model_tr/layout.rs
def show_address_details(
*,
@ -285,7 +290,6 @@ def select_word(
# rust/src/ui/model_tr/layout.rs
def show_share_words(
*,
title: str,
share_words: Iterable[str],
) -> object:
"""Shows a backup seed."""

View File

@ -132,12 +132,12 @@ async def _show_confirmation_success(
async def _show_confirmation_failure() -> None:
from trezor.ui.layouts.recovery import show_recovery_warning
from trezor.ui.layouts.reset import show_reset_warning
await show_recovery_warning(
await show_reset_warning(
"warning_backup_check",
"Please check again.",
"That is the wrong word.",
"Please check again",
"Wrong word selected!",
"Check again",
ButtonRequestType.ResetDevice,
)
@ -171,7 +171,7 @@ async def bip39_show_and_confirm_mnemonic(mnemonic: str) -> None:
# make the user confirm some words from the mnemonic
if await _share_words_confirmed(None, words):
break # this share is confirmed, go to next one
break # mnemonic is confirmed, go next
# SLIP39

View File

@ -400,26 +400,25 @@ async def confirm_reset_device(
)
# TODO cleanup @ redesign
async def confirm_backup() -> bool:
if await get_bool(
"backup_device",
"SUCCESS",
description="New wallet has been created.\nIt should be backed up now!",
verb="BACK UP",
verb_cancel="SKIP",
br_code=ButtonRequestType.ResetDevice,
):
br_type = "backup_device"
br_code = ButtonRequestType.ResetDevice
result = await interact(
RustLayout(trezorui2.confirm_backup()),
br_type,
br_code,
)
if result is CONFIRMED:
return True
return await get_bool(
"backup_device",
"WARNING",
"Are you sure you want to skip the backup?\n",
"You can back up your Trezor once, at any time.",
br_type,
"SKIP BACKUP",
description="Are you sure you want to skip the backup?",
verb="BACK UP",
verb_cancel="SKIP",
br_code=ButtonRequestType.ResetDevice,
br_code=br_code,
)

View File

@ -6,7 +6,7 @@ from trezor.wire import ActionCancelled
import trezorui2
from ..common import interact
from . import RustLayout, confirm_action, raise_if_not_confirmed
from . import RustLayout, confirm_action, show_error
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
@ -21,9 +21,11 @@ async def show_share_words(
group_index: int | None = None,
) -> None:
# Showing words, asking for write down confirmation and preparing for check
br_type = "backup_words"
br_code = ButtonRequestType.ResetDevice
if share_index is None:
title = "RECOVERY SEED"
title = "STANDARD BACKUP"
check_title = "CHECK BACKUP"
elif group_index is None:
title = f"SHARE #{share_index + 1}"
@ -32,26 +34,37 @@ async def show_share_words(
title = f"G{group_index + 1} - SHARE {share_index + 1}"
check_title = f"GROUP {group_index + 1} - SHARE {share_index + 1}"
await raise_if_not_confirmed(
interact(
# We want the option to go back from words to the previous screen
# (by sending CANCELLED)
while True:
await confirm_action(
br_type,
title,
description=f"Write down all {len(share_words)} words in order.",
verb="SHOW WORDS",
verb_cancel=None,
br_code=br_code,
)
result = await interact(
RustLayout(
trezorui2.show_share_words( # type: ignore [Argument missing for parameter "pages"]
title=title,
trezorui2.show_share_words( # type: ignore [Arguments missing for parameters]
share_words=share_words, # type: ignore [No parameter named "share_words"]
)
),
"backup_words",
ButtonRequestType.ResetDevice,
br_type,
br_code,
)
)
if result is CONFIRMED:
break
await confirm_action(
"backup_words",
br_type,
check_title,
description="Select the correct word for each position.",
verb="CONTINUE",
verb_cancel=None,
br_code=ButtonRequestType.ResetDevice,
br_code=br_code,
)
@ -234,13 +247,12 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
async def show_warning_backup(slip39: bool) -> None:
await confirm_action(
await show_error(
"backup_warning",
"SHAMIR BACKUP" if slip39 else "WALLET BACKUP",
description="You can use your backup to recover your wallet at any time.",
verb="HOLD TO BEGIN",
hold=True,
br_code=ButtonRequestType.ResetDevice,
"REMEMBER",
"Never make a digital copy of your backup or upload it online!",
"OK, I UNDERSTAND",
ButtonRequestType.ResetDevice,
)
@ -253,3 +265,21 @@ async def show_success_backup() -> None:
verb_cancel=None,
br_code=ButtonRequestType.Success,
)
async def show_reset_warning(
ctx: GenericContext,
br_type: str,
content: str,
subheader: str | None = None,
button: str = "TRY AGAIN",
br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> None:
await show_error(
ctx,
br_type,
button.upper(),
subheader or "",
content,
br_code=br_code,
)

View File

@ -7,7 +7,7 @@ from trezor.wire.context import wait as ctx_wait
import trezorui2
from ..common import interact
from . import RustLayout
from . import RustLayout, raise_if_not_confirmed
if TYPE_CHECKING:
from typing import Callable, Sequence
@ -335,3 +335,26 @@ async def show_success_backup() -> None:
text = "Use your backup when you need to recover your wallet."
await show_success("success_backup", text, "Your backup is done.")
async def show_reset_warning(
br_type: str,
content: str,
subheader: str | None = None,
button: str = "TRY AGAIN",
br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> None:
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.show_warning(
title=subheader or "",
description=content,
button=button.upper(),
allow_cancel=False,
)
),
br_type,
br_code,
)
)

View File

@ -581,20 +581,20 @@ class DebugLink:
x, y = click
return self.input(x=x, y=y, hold_ms=hold_ms, wait=True)
def press_yes(self, wait: bool = False) -> None:
self.input(button=messages.DebugButton.YES, wait=wait)
def press_yes(self, wait: bool = False) -> Optional[LayoutContent]:
return self.input(button=messages.DebugButton.YES, wait=wait)
def press_no(self, wait: bool = False) -> None:
self.input(button=messages.DebugButton.NO, wait=wait)
def press_no(self, wait: bool = False) -> Optional[LayoutContent]:
return self.input(button=messages.DebugButton.NO, wait=wait)
def press_info(self, wait: bool = False) -> None:
self.input(button=messages.DebugButton.INFO, wait=wait)
def press_info(self, wait: bool = False) -> Optional[LayoutContent]:
return self.input(button=messages.DebugButton.INFO, wait=wait)
def swipe_up(self, wait: bool = False) -> None:
self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait)
def swipe_up(self, wait: bool = False) -> Optional[LayoutContent]:
return self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait)
def swipe_down(self, wait: bool = False) -> None:
self.input(swipe=messages.DebugSwipeDirection.DOWN, wait=wait)
def swipe_down(self, wait: bool = False) -> Optional[LayoutContent]:
return self.input(swipe=messages.DebugSwipeDirection.DOWN, wait=wait)
@overload
def swipe_right(self) -> None:

View File

@ -20,14 +20,10 @@ def confirm_new_wallet(debug: "DebugLink") -> None:
debug.press_right(wait=True)
def confirm_read(debug: "DebugLink", title: str, hold: bool = False) -> None:
def confirm_read(debug: "DebugLink", title: str, middle_r: bool = False) -> None:
layout = debug.read_layout()
if title == "Caution":
if debug.model == "T":
# TODO: could look into button texts
assert "OK, I UNDERSTAND" in layout.json_str
elif debug.model == "R":
assert "use your backup to recover" in layout.text_content()
assert "Never make a digital copy" in layout.text_content()
elif title == "Success":
# TODO: improve this
assert any(
@ -36,7 +32,7 @@ def confirm_read(debug: "DebugLink", title: str, hold: bool = False) -> None:
"success",
"finished",
"done",
"has been created",
"created",
"Keep it safe",
)
)
@ -50,12 +46,10 @@ def confirm_read(debug: "DebugLink", title: str, hold: bool = False) -> None:
elif debug.model == "R":
if layout.page_count() > 1:
debug.press_right(wait=True)
if hold:
# TODO: create debug.hold_right()?
debug.press_yes()
if middle_r:
debug.press_middle(wait=True)
else:
debug.press_right()
debug.wait_layout()
debug.press_right(wait=True)
def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
@ -94,18 +88,19 @@ def read_words(
assert layout.title() == "RECOVERY SEED"
elif debug.model == "R":
if backup_type == messages.BackupType.Slip39_Advanced:
assert "SHARE" in layout.text_content()
assert "SHARE" in layout.title()
elif backup_type == messages.BackupType.Slip39_Basic:
assert layout.text_content().startswith("SHARE #")
assert layout.title().startswith("SHARE #")
else:
assert layout.text_content().startswith("RECOVERY SEED")
assert layout.title() == "STANDARD BACKUP"
assert "Write down" in layout.text_content()
layout = debug.press_right(wait=True)
# Swiping through all the pages and loading the words
for i in range(layout.page_count() - 1):
# In model R, first two pages are just informational
if not (debug.model == "R" and i < 2):
words.extend(layout.seed_words())
layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True)
for _ in range(layout.page_count() - 1):
words.extend(layout.seed_words())
layout = debug.swipe_up(wait=True)
assert layout is not None
if debug.model == "T":
words.extend(layout.seed_words())

View File

@ -52,8 +52,8 @@ def test_reset_bip39(device_handler: "BackgroundDeviceHandler"):
# confirm back up
reset.confirm_read(debug, "Success")
# confirm backup warning (hold-to-confirm on TR)
reset.confirm_read(debug, "Caution", hold=True)
# confirm backup warning
reset.confirm_read(debug, "Caution", middle_r=True)
# read words
words = reset.read_words(debug, messages.BackupType.Bip39)

View File

@ -105,8 +105,8 @@ def test_reset_slip39_advanced(
else:
raise RuntimeError("not a supported combination")
# confirm backup warning (hold-to-confirm on TR)
reset.confirm_read(debug, "Caution", hold=True)
# confirm backup warning
reset.confirm_read(debug, "Caution", middle_r=True)
all_words: list[str] = []
for _ in range(group_count):

View File

@ -85,8 +85,8 @@ def test_reset_slip39_basic(
# confirm checklist
reset.confirm_read(debug, "Checklist")
# confirm backup warning (hold-to-confirm on TR)
reset.confirm_read(debug, "Caution", hold=True)
# confirm backup warning
reset.confirm_read(debug, "Caution", middle_r=True)
all_words: list[str] = []
for _ in range(num_of_shares):

View File

@ -339,14 +339,14 @@ def read_and_confirm_mnemonic_tr(
debug: "DebugLink", choose_wrong: bool = False
) -> Generator[None, "ButtonRequest", Optional[str]]:
mnemonic: list[str] = []
yield # write down all 12 words in order
debug.press_yes()
br = yield
assert br.pages is not None
for i in range(br.pages - 1):
for _ in range(br.pages - 1):
layout = debug.wait_layout()
# First two pages have just instructions
if i > 1:
words = layout.seed_words()
mnemonic.extend(words)
words = layout.seed_words()
mnemonic.extend(words)
debug.press_right()
debug.press_yes()

View File

@ -107,7 +107,7 @@ def test_skip_backup_msg(client: Client, backup_type, backup_flow):
@pytest.mark.skip_t1
@pytest.mark.parametrize("backup_type, backup_flow", VECTORS)
@pytest.mark.setup_client(uninitialized=True)
def test_skip_backup_manual(client: Client, backup_type, backup_flow):
def test_skip_backup_manual(client: Client, backup_type: BackupType, backup_flow):
with WITH_MOCK_URANDOM, client:
IF = InputFlowResetSkipBackup(client)
client.set_input_flow(IF.get())

View File

@ -2027,5 +2027,4 @@ class InputFlowResetSkipBackup(InputFlowBase):
yield # Skip Backup
self.debug.press_no()
yield # Confirm skip backup
self.debug.press_right()
self.debug.press_no()