mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 15:38:11 +00:00
feat(core/rust/ui): recovery layouts
[no changelog]
This commit is contained in:
parent
5052594789
commit
5a9c2a1363
BIN
core/assets/warn-icon.png
Normal file
BIN
core/assets/warn-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 383 B |
@ -27,6 +27,7 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_confirm_text;
|
||||
MP_QSTR_confirm_total;
|
||||
MP_QSTR_confirm_with_info;
|
||||
MP_QSTR_confirm_recovery;
|
||||
MP_QSTR_show_checklist;
|
||||
MP_QSTR_show_error;
|
||||
MP_QSTR_show_qr;
|
||||
@ -40,6 +41,9 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_request_bip39;
|
||||
MP_QSTR_request_slip39;
|
||||
MP_QSTR_select_word;
|
||||
MP_QSTR_select_word_count;
|
||||
MP_QSTR_show_group_share_success;
|
||||
MP_QSTR_show_remaining_shares;
|
||||
MP_QSTR_show_share_words;
|
||||
|
||||
MP_QSTR_attach_timer_fn;
|
||||
@ -85,4 +89,5 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_items;
|
||||
MP_QSTR_active;
|
||||
MP_QSTR_info_button;
|
||||
MP_QSTR_time_ms;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use cstr_core::CStr;
|
||||
use crate::micropython::{ffi, obj::Obj, qstr::Qstr};
|
||||
|
||||
#[allow(clippy::enum_variant_names)] // We mimic the Python exception classnames here.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Error {
|
||||
TypeError,
|
||||
OutOfRange,
|
||||
|
@ -12,6 +12,7 @@ pub mod paginated;
|
||||
pub mod painter;
|
||||
pub mod placed;
|
||||
pub mod text;
|
||||
pub mod timeout;
|
||||
|
||||
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken};
|
||||
pub use border::Border;
|
||||
@ -28,3 +29,4 @@ pub use text::{
|
||||
formatted::FormattedText,
|
||||
layout::{LineBreaking, PageBreaking, TextLayout},
|
||||
};
|
||||
pub use timeout::{Timeout, TimeoutMsg};
|
||||
|
60
core/embed/rust/src/ui/component/timeout.rs
Normal file
60
core/embed/rust/src/ui/component/timeout.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use crate::{
|
||||
time::Duration,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, TimerToken},
|
||||
geometry::Rect,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Timeout {
|
||||
time_ms: u32,
|
||||
timer: Option<TimerToken>,
|
||||
}
|
||||
|
||||
pub enum TimeoutMsg {
|
||||
TimedOut,
|
||||
}
|
||||
|
||||
impl Timeout {
|
||||
pub fn new(time_ms: u32) -> Self {
|
||||
Self {
|
||||
time_ms,
|
||||
timer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Timeout {
|
||||
type Msg = TimeoutMsg;
|
||||
|
||||
fn place(&mut self, _bounds: Rect) -> Rect {
|
||||
Rect::zero()
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
match event {
|
||||
// Set up timer.
|
||||
Event::Attach => {
|
||||
self.timer = Some(ctx.request_timer(Duration::from_millis(self.time_ms)));
|
||||
None
|
||||
}
|
||||
// Fire.
|
||||
Event::Timer(token) if Some(token) == self.timer => {
|
||||
self.timer = None;
|
||||
Some(TimeoutMsg::TimedOut)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for Timeout {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("Timeout");
|
||||
t.int(self.time_ms as i64);
|
||||
t.close();
|
||||
}
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
pub mod obj;
|
||||
pub mod result;
|
||||
pub mod util;
|
||||
|
23
core/embed/rust/src/ui/layout/util.rs
Normal file
23
core/embed/rust/src/ui/layout/util.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::{
|
||||
iter::{Iter, IterBuf},
|
||||
obj::Obj,
|
||||
},
|
||||
};
|
||||
use cstr_core::cstr;
|
||||
use heapless::Vec;
|
||||
|
||||
pub fn iter_into_array<T, const N: usize>(iterable: Obj) -> Result<[T; N], Error>
|
||||
where
|
||||
T: TryFrom<Obj, Error = Error>,
|
||||
{
|
||||
let err = Error::ValueError(cstr!("Invalid iterable length"));
|
||||
let mut vec = Vec::<T, N>::new();
|
||||
let mut iter_buf = IterBuf::new();
|
||||
for item in Iter::try_from_obj_with_buf(iterable, &mut iter_buf)? {
|
||||
vec.push(item.try_into()?).map_err(|_| err)?;
|
||||
}
|
||||
// Returns error if array.len() != N
|
||||
vec.into_array().map_err(|_| err)
|
||||
}
|
@ -434,6 +434,38 @@ impl<T> Button<T> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn abort_info_enter() -> CancelInfoConfirm<
|
||||
&'static str,
|
||||
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
|
||||
> {
|
||||
let left = Button::with_text("ABORT").styled(theme::button_cancel());
|
||||
let middle = Button::with_text("INFO");
|
||||
let right = Button::with_text("ENTER").styled(theme::button_confirm());
|
||||
theme::button_bar((
|
||||
GridPlaced::new(left)
|
||||
.with_grid(1, 3)
|
||||
.with_spacing(theme::BUTTON_SPACING)
|
||||
.with_row_col(0, 0)
|
||||
.map(|msg| {
|
||||
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Cancelled)
|
||||
}),
|
||||
GridPlaced::new(middle)
|
||||
.with_grid(1, 3)
|
||||
.with_spacing(theme::BUTTON_SPACING)
|
||||
.with_row_col(0, 1)
|
||||
.map(|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Info)),
|
||||
GridPlaced::new(right)
|
||||
.with_grid(1, 3)
|
||||
.with_spacing(theme::BUTTON_SPACING)
|
||||
.with_row_col(0, 2)
|
||||
.map(|msg| {
|
||||
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Confirmed)
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn select_word(
|
||||
words: [T; 3],
|
||||
) -> CancelInfoConfirm<
|
||||
|
@ -117,6 +117,24 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new_shares(lines: [T; 4], controls: U) -> Self {
|
||||
let [l0, l1, l2, l3] = lines;
|
||||
Self {
|
||||
image: Child::new(Image::new(theme::IMAGE_SUCCESS)),
|
||||
paragraphs: Paragraphs::new()
|
||||
.with_placement(LinearPlacement::vertical().align_at_center())
|
||||
.add_color(theme::TEXT_NORMAL, theme::OFF_WHITE, l0)
|
||||
.centered()
|
||||
.add(theme::TEXT_MEDIUM, l1)
|
||||
.centered()
|
||||
.add_color(theme::TEXT_NORMAL, theme::OFF_WHITE, l2)
|
||||
.centered()
|
||||
.add(theme::TEXT_MEDIUM, l3)
|
||||
.centered(),
|
||||
controls: Child::new(controls),
|
||||
}
|
||||
}
|
||||
|
||||
pub const ICON_AREA_PADDING: i32 = 2;
|
||||
pub const ICON_AREA_HEIGHT: i32 = 60;
|
||||
pub const VALUE_SPACE: i32 = 5;
|
||||
|
@ -1,8 +1,8 @@
|
||||
use super::theme;
|
||||
use crate::ui::{
|
||||
component::{Child, Component, Event, EventCtx},
|
||||
display,
|
||||
geometry::{Insets, Rect},
|
||||
display::{self, Color, Font},
|
||||
geometry::{Insets, Offset, Rect},
|
||||
};
|
||||
|
||||
pub struct Frame<T, U> {
|
||||
@ -91,3 +91,99 @@ where
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotificationFrame<T, U> {
|
||||
area: Rect,
|
||||
border: Insets,
|
||||
icon: &'static [u8],
|
||||
title: U,
|
||||
content: Child<T>,
|
||||
}
|
||||
|
||||
impl<T, U> NotificationFrame<T, U>
|
||||
where
|
||||
T: Component,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
const HEIGHT: i32 = 42;
|
||||
const COLOR: Color = theme::YELLOW;
|
||||
const FONT: Font = theme::FONT_BOLD;
|
||||
const TEXT_OFFSET: Offset = Offset::new(1, -2);
|
||||
const ICON_SPACE: i32 = 8;
|
||||
|
||||
pub fn new(icon: &'static [u8], title: U, content: T) -> Self {
|
||||
Self {
|
||||
icon,
|
||||
title,
|
||||
area: Rect::zero(),
|
||||
border: theme::borders_notification(),
|
||||
content: Child::new(content),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &T {
|
||||
self.content.inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Component for NotificationFrame<T, U>
|
||||
where
|
||||
T: Component,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
type Msg = T::Msg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
let (title_area, content_area) = bounds.split_top(Self::HEIGHT);
|
||||
let content_area = content_area.inset(self.border);
|
||||
|
||||
self.area = title_area;
|
||||
self.content.place(content_area);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.content.event(ctx, event)
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
let toif_info = unwrap!(display::toif_info(self.icon), "Invalid TOIF data");
|
||||
let icon_width = toif_info.0.y;
|
||||
let text_width = Self::FONT.text_width(self.title.as_ref());
|
||||
let text_height = Self::FONT.text_height();
|
||||
let text_center =
|
||||
self.area.center() + Offset::new((icon_width + Self::ICON_SPACE) / 2, text_height / 2);
|
||||
let icon_center = self.area.center() - Offset::x((text_width + Self::ICON_SPACE) / 2);
|
||||
|
||||
display::rect_fill_rounded(self.area, Self::COLOR, theme::BG, 2);
|
||||
display::text_center(
|
||||
text_center + Self::TEXT_OFFSET,
|
||||
self.title.as_ref(),
|
||||
Self::FONT,
|
||||
theme::BG,
|
||||
Self::COLOR,
|
||||
);
|
||||
display::icon(icon_center, self.icon, theme::BG, Self::COLOR);
|
||||
|
||||
self.content.paint();
|
||||
}
|
||||
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
sink(self.area);
|
||||
self.content.bounds(sink);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, U> crate::trace::Trace for NotificationFrame<T, U>
|
||||
where
|
||||
T: crate::trace::Trace,
|
||||
U: crate::trace::Trace + AsRef<str>,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("NotificationFrame");
|
||||
t.field("title", &self.title);
|
||||
t.field("content", &self.content);
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,6 @@ pub mod mnemonic;
|
||||
pub mod passphrase;
|
||||
pub mod pin;
|
||||
pub mod slip39;
|
||||
pub mod word_count;
|
||||
|
||||
mod common;
|
||||
|
@ -0,0 +1,73 @@
|
||||
use crate::ui::{
|
||||
component::{Component, Event, EventCtx},
|
||||
geometry::{Grid, GridCellSpan, Rect},
|
||||
model_tt::{
|
||||
component::button::{Button, ButtonMsg},
|
||||
theme,
|
||||
},
|
||||
};
|
||||
|
||||
const NUMBERS: [u32; 5] = [12, 18, 20, 24, 33];
|
||||
const LABELS: [&str; 5] = ["12", "18", "20", "24", "33"];
|
||||
const CELLS: [(usize, usize); 5] = [(0, 0), (0, 2), (0, 4), (1, 1), (1, 3)];
|
||||
|
||||
pub struct SelectWordCount {
|
||||
button: [Button<&'static str>; NUMBERS.len()],
|
||||
}
|
||||
|
||||
pub enum SelectWordCountMsg {
|
||||
Selected(u32),
|
||||
}
|
||||
|
||||
impl SelectWordCount {
|
||||
pub fn new() -> Self {
|
||||
SelectWordCount {
|
||||
button: LABELS.map(Button::with_text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SelectWordCount {
|
||||
type Msg = SelectWordCountMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
let (_, bounds) = bounds.split_bottom(theme::button_rows(2));
|
||||
let grid = Grid::new(bounds, 2, 6).with_spacing(theme::BUTTON_SPACING);
|
||||
for (btn, (x, y)) in self.button.iter_mut().zip(CELLS) {
|
||||
btn.place(grid.cells(GridCellSpan {
|
||||
from: (x, y),
|
||||
to: (x, y + 1),
|
||||
}));
|
||||
}
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
for (i, btn) in self.button.iter_mut().enumerate() {
|
||||
if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) {
|
||||
return Some(SelectWordCountMsg::Selected(NUMBERS[i]));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
for btn in self.button.iter_mut() {
|
||||
btn.paint()
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
for btn in self.button.iter() {
|
||||
btn.bounds(sink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for SelectWordCount {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("SelectWordCount");
|
||||
t.close();
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ pub use button::{
|
||||
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg,
|
||||
CancelInfoConfirmMsg, SelectWordMsg,
|
||||
};
|
||||
pub use dialog::{Dialog, DialogLayout, DialogMsg, IconDialog};
|
||||
pub use frame::Frame;
|
||||
pub use dialog::{Dialog, DialogMsg, IconDialog};
|
||||
pub use frame::{Frame, NotificationFrame};
|
||||
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
|
||||
pub use keyboard::{
|
||||
bip39::Bip39Input,
|
||||
@ -22,6 +22,7 @@ pub use keyboard::{
|
||||
passphrase::{PassphraseKeyboard, PassphraseKeyboardMsg},
|
||||
pin::{PinKeyboard, PinKeyboardMsg},
|
||||
slip39::Slip39Input,
|
||||
word_count::{SelectWordCount, SelectWordCountMsg},
|
||||
};
|
||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
||||
pub use number_input::{NumberInputDialog, NumberInputDialogMsg};
|
||||
|
@ -1,5 +1,4 @@
|
||||
use core::{cmp::Ordering, convert::TryInto, ops::Deref};
|
||||
use cstr_core::cstr;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
@ -18,12 +17,13 @@ use crate::{
|
||||
paginated::{PageMsg, Paginate},
|
||||
painter,
|
||||
text::paragraphs::{Checklist, Paragraphs},
|
||||
Border, Component,
|
||||
Border, Component, Timeout, TimeoutMsg,
|
||||
},
|
||||
geometry,
|
||||
layout::{
|
||||
obj::{ComponentMsgObj, LayoutObj},
|
||||
result::{CANCELLED, CONFIRMED, INFO},
|
||||
util::iter_into_array,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -32,9 +32,10 @@ use super::{
|
||||
component::{
|
||||
Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, CancelInfoConfirmMsg,
|
||||
Dialog, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg, IconDialog, MnemonicInput,
|
||||
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
|
||||
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, SelectWordMsg,
|
||||
Slip39Input, SwipeHoldPage, SwipePage,
|
||||
MnemonicKeyboard, MnemonicKeyboardMsg, NotificationFrame, NumberInputDialog,
|
||||
NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard,
|
||||
PinKeyboardMsg, SelectWordCount, SelectWordCountMsg, SelectWordMsg, Slip39Input,
|
||||
SwipeHoldPage, SwipePage,
|
||||
},
|
||||
theme,
|
||||
};
|
||||
@ -72,6 +73,16 @@ impl TryFrom<SelectWordMsg> for Obj {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SelectWordCountMsg> for Obj {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: SelectWordCountMsg) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
SelectWordCountMsg::Selected(i) => i.try_into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ComponentMsgObj for Dialog<T, U>
|
||||
where
|
||||
T: ComponentMsgObj,
|
||||
@ -162,6 +173,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ComponentMsgObj for NotificationFrame<T, U>
|
||||
where
|
||||
T: ComponentMsgObj,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||
self.inner().msg_try_into_obj(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ComponentMsgObj for SwipePage<T, U>
|
||||
where
|
||||
T: Component + Paginate,
|
||||
@ -512,11 +533,12 @@ fn new_show_modal(
|
||||
) -> Result<Obj, Error> {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let description: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
|
||||
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
|
||||
let button: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_button, "CONTINUE".into())?;
|
||||
let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?;
|
||||
let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?;
|
||||
|
||||
let obj = if allow_cancel {
|
||||
LayoutObj::new(
|
||||
let obj = match (allow_cancel, time_ms) {
|
||||
(true, 0) => LayoutObj::new(
|
||||
IconDialog::new(
|
||||
icon,
|
||||
title,
|
||||
@ -528,19 +550,29 @@ fn new_show_modal(
|
||||
)
|
||||
.with_description(description),
|
||||
)?
|
||||
.into()
|
||||
} else {
|
||||
LayoutObj::new(
|
||||
.into(),
|
||||
(false, 0) => LayoutObj::new(
|
||||
IconDialog::new(
|
||||
icon,
|
||||
title,
|
||||
Button::with_text(button).styled(button_style).map(|msg| {
|
||||
theme::button_bar(Button::with_text(button).styled(button_style).map(|msg| {
|
||||
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
|
||||
})),
|
||||
)
|
||||
.with_description(description),
|
||||
)?
|
||||
.into(),
|
||||
(_, time_ms) => LayoutObj::new(
|
||||
IconDialog::new(
|
||||
icon,
|
||||
title,
|
||||
Timeout::new(time_ms).map(|msg| {
|
||||
(matches!(msg, TimeoutMsg::TimedOut)).then(|| CancelConfirmMsg::Confirmed)
|
||||
}),
|
||||
)
|
||||
.with_description(description),
|
||||
)?
|
||||
.into()
|
||||
.into(),
|
||||
};
|
||||
|
||||
Ok(obj)
|
||||
@ -709,17 +741,7 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map)
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||
let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
|
||||
|
||||
let mut words = [StrBuffer::empty(), StrBuffer::empty(), StrBuffer::empty()];
|
||||
let mut iter_buf = IterBuf::new();
|
||||
let mut iter = Iter::try_from_obj_with_buf(words_iterable, &mut iter_buf)?;
|
||||
let words_err = || Error::ValueError(cstr!("Invalid words count"));
|
||||
for item in &mut words {
|
||||
*item = iter.next().ok_or_else(words_err)?.try_into()?;
|
||||
}
|
||||
if iter.next().is_some() {
|
||||
return Err(words_err());
|
||||
}
|
||||
let words: [StrBuffer; 3] = iter_into_array(words_iterable)?;
|
||||
|
||||
let paragraphs = Paragraphs::new().add(theme::TEXT_NORMAL, description);
|
||||
let buttons = Button::select_word(words);
|
||||
@ -826,6 +848,140 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title).unwrap().try_into().unwrap();
|
||||
let description: StrBuffer = kwargs
|
||||
.get(Qstr::MP_QSTR_description)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let button: StrBuffer = kwargs
|
||||
.get(Qstr::MP_QSTR_button)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let dry_run: bool = kwargs
|
||||
.get(Qstr::MP_QSTR_dry_run)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false).unwrap();
|
||||
|
||||
let paragraphs = Paragraphs::new()
|
||||
.with_spacing(theme::RECOVERY_SPACING)
|
||||
.add(theme::TEXT_BOLD, title)
|
||||
.centered()
|
||||
.add_color(theme::TEXT_NORMAL, theme::OFF_WHITE, description)
|
||||
.centered();
|
||||
|
||||
let notification = if dry_run {
|
||||
"SEED CHECK"
|
||||
} else {
|
||||
"RECOVERY MODE"
|
||||
};
|
||||
|
||||
let obj = if info_button {
|
||||
LayoutObj::new(
|
||||
NotificationFrame::new(
|
||||
theme::ICON_WARN,
|
||||
notification,
|
||||
Dialog::new(paragraphs, Button::<&'static str>::abort_info_enter()),
|
||||
)
|
||||
.into_child(),
|
||||
)?
|
||||
} else {
|
||||
LayoutObj::new(
|
||||
NotificationFrame::new(
|
||||
theme::ICON_WARN,
|
||||
notification,
|
||||
Dialog::new(paragraphs, Button::cancel_confirm_text(None, button)),
|
||||
)
|
||||
.into_child(),
|
||||
)?
|
||||
};
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let dry_run: bool = kwargs
|
||||
.get(Qstr::MP_QSTR_dry_run)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let title = if dry_run {
|
||||
"SEED CHECK"
|
||||
} else {
|
||||
"RECOVERY MODE"
|
||||
};
|
||||
|
||||
let paragraphs = Paragraphs::new()
|
||||
.add(theme::TEXT_BOLD, "Number of words?")
|
||||
.centered();
|
||||
|
||||
let obj = LayoutObj::new(
|
||||
Frame::new(title, Dialog::new(paragraphs, SelectWordCount::new()))
|
||||
.with_border(theme::borders())
|
||||
.into_child(),
|
||||
)?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_group_share_success(
|
||||
n_args: usize,
|
||||
args: *const Obj,
|
||||
kwargs: *mut Map,
|
||||
) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let lines_iterable: Obj = kwargs.get(Qstr::MP_QSTR_lines)?;
|
||||
let lines: [StrBuffer; 4] = iter_into_array(lines_iterable)?;
|
||||
|
||||
let obj = LayoutObj::new(IconDialog::new_shares(
|
||||
lines,
|
||||
theme::button_bar(Button::with_text("CONTINUE").map(|msg| {
|
||||
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
|
||||
})),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let pages_iterable: Obj = kwargs.get(Qstr::MP_QSTR_pages)?;
|
||||
|
||||
let mut paragraphs = Paragraphs::new();
|
||||
let mut iter_buf = IterBuf::new();
|
||||
let iter = Iter::try_from_obj_with_buf(pages_iterable, &mut iter_buf)?;
|
||||
for page in iter {
|
||||
let [title, description]: [StrBuffer; 2] = iter_into_array(page)?;
|
||||
paragraphs = paragraphs
|
||||
.add(theme::TEXT_BOLD, title)
|
||||
.add(theme::TEXT_NORMAL, description)
|
||||
.add_break();
|
||||
}
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(
|
||||
"REMAINING SHARES",
|
||||
SwipePage::new(
|
||||
paragraphs,
|
||||
theme::button_bar(Button::with_text("CONTINUE").map(|msg| {
|
||||
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
|
||||
})),
|
||||
theme::BG,
|
||||
),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub static mp_module_trezorui2: Module = obj_module! {
|
||||
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(),
|
||||
@ -932,9 +1088,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def show_error(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// button: str,
|
||||
/// button: str = "CONTINUE",
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// """Error modal."""
|
||||
Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(),
|
||||
@ -942,9 +1099,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def show_warning(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// button: str,
|
||||
/// button: str = "CONTINUE",
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// """Warning modal."""
|
||||
Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(),
|
||||
@ -952,9 +1110,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def show_success(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// button: str,
|
||||
/// button: str = "CONTINUE",
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// """Success modal."""
|
||||
Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(),
|
||||
@ -962,9 +1121,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def show_info(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// button: str,
|
||||
/// button: str = "CONTINUE",
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// """Info modal."""
|
||||
Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(),
|
||||
@ -1068,6 +1228,38 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// """Checklist of backup steps. Active index is highlighted, previous items have check
|
||||
/// mark nex to them."""
|
||||
Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(),
|
||||
|
||||
/// def confirm_recovery(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// description: str,
|
||||
/// button: str,
|
||||
/// dry_run: bool,
|
||||
/// info_button: bool,
|
||||
/// ) -> object:
|
||||
/// """Device recovery homescreen."""
|
||||
Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(),
|
||||
|
||||
/// def select_word_count(
|
||||
/// *,
|
||||
/// dry_run: bool,
|
||||
/// ) -> int | trezorui2.CANCELLED:
|
||||
/// """Select mnemonic word count from (12, 18, 20, 24, 33)."""
|
||||
Qstr::MP_QSTR_select_word_count => obj_fn_kw!(0, new_select_word_count).as_obj(),
|
||||
|
||||
/// def show_group_share_success(
|
||||
/// *,
|
||||
/// lines: Iterable[str]
|
||||
/// ) -> int:
|
||||
/// """Shown after successfully finishing a group."""
|
||||
Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(),
|
||||
|
||||
/// def show_remaining_shares(
|
||||
/// *,
|
||||
/// pages: Iterable[tuple[str, str]],
|
||||
/// ) -> int:
|
||||
/// """Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
|
||||
Qstr::MP_QSTR_show_remaining_shares => obj_fn_kw!(0, new_show_remaining_shares).as_obj(),
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
BIN
core/embed/rust/src/ui/model_tt/res/warn-icon.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/warn-icon.toif
Normal file
Binary file not shown.
@ -57,6 +57,7 @@ pub const ICON_SPACE: &[u8] = include_res!("model_tt/res/space.toif");
|
||||
pub const ICON_BACK: &[u8] = include_res!("model_tt/res/back.toif");
|
||||
pub const ICON_CLICK: &[u8] = include_res!("model_tt/res/click.toif");
|
||||
pub const ICON_NEXT: &[u8] = include_res!("model_tt/res/next.toif");
|
||||
pub const ICON_WARN: &[u8] = include_res!("model_tt/res/warn-icon.toif");
|
||||
pub const ICON_LIST_CURRENT: &[u8] = include_res!("model_tt/res/current.toif");
|
||||
pub const ICON_LIST_CHECK: &[u8] = include_res!("model_tt/res/check.toif");
|
||||
|
||||
@ -151,6 +152,22 @@ pub fn label_warning_value() -> LabelStyle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label_recovery_title() -> LabelStyle {
|
||||
LabelStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: FG,
|
||||
background_color: BG,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label_recovery_description() -> LabelStyle {
|
||||
LabelStyle {
|
||||
font: FONT_NORMAL,
|
||||
text_color: OFF_WHITE,
|
||||
background_color: BG,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_default() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
@ -411,6 +428,8 @@ pub const KEYBOARD_SPACING: i32 = 8;
|
||||
pub const BUTTON_HEIGHT: i32 = 38;
|
||||
pub const BUTTON_SPACING: i32 = 6;
|
||||
pub const CHECKLIST_SPACING: i32 = 10;
|
||||
pub const RECOVERY_SPACING: i32 = 18;
|
||||
|
||||
/// Standard button height in pixels.
|
||||
pub const fn button_rows(count: usize) -> i32 {
|
||||
let count = count as i32;
|
||||
@ -439,3 +458,7 @@ pub const fn borders() -> Insets {
|
||||
pub const fn borders_scroll() -> Insets {
|
||||
Insets::new(13, 5, 14, 10)
|
||||
}
|
||||
|
||||
pub const fn borders_notification() -> Insets {
|
||||
Insets::new(6, 10, 14, 10)
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ pub fn u32_to_str(num: u32, buffer: &mut [u8]) -> Option<&str> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str;
|
||||
|
||||
#[test]
|
||||
fn u32_to_str_valid() {
|
||||
|
@ -159,9 +159,10 @@ def confirm_modify_fee(
|
||||
def show_error(
|
||||
*,
|
||||
title: str,
|
||||
button: str,
|
||||
button: str = "CONTINUE",
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
"""Error modal."""
|
||||
|
||||
@ -170,9 +171,10 @@ def show_error(
|
||||
def show_warning(
|
||||
*,
|
||||
title: str,
|
||||
button: str,
|
||||
button: str = "CONTINUE",
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
"""Warning modal."""
|
||||
|
||||
@ -181,9 +183,10 @@ def show_warning(
|
||||
def show_success(
|
||||
*,
|
||||
title: str,
|
||||
button: str,
|
||||
button: str = "CONTINUE",
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
"""Success modal."""
|
||||
|
||||
@ -192,9 +195,10 @@ def show_success(
|
||||
def show_info(
|
||||
*,
|
||||
title: str,
|
||||
button: str,
|
||||
button: str = "CONTINUE",
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
"""Info modal."""
|
||||
|
||||
@ -308,3 +312,39 @@ def show_checklist(
|
||||
) -> object:
|
||||
"""Checklist of backup steps. Active index is highlighted, previous items have check
|
||||
mark nex to them."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
def confirm_recovery(
|
||||
*,
|
||||
title: str,
|
||||
description: str,
|
||||
button: str,
|
||||
dry_run: bool,
|
||||
info_button: bool,
|
||||
) -> object:
|
||||
"""Device recovery homescreen."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
def select_word_count(
|
||||
*,
|
||||
dry_run: bool,
|
||||
) -> int | trezorui2.CANCELLED:
|
||||
"""Select mnemonic word count from (12, 18, 20, 24, 33)."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
def show_group_share_success(
|
||||
*,
|
||||
lines: Iterable[str]
|
||||
) -> int:
|
||||
"""Shown after successfully finishing a group."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
def show_remaining_shares(
|
||||
*,
|
||||
pages: Iterable[tuple[str, str]],
|
||||
) -> int:
|
||||
"""Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
|
||||
|
@ -148,11 +148,13 @@ async def homescreen_dialog(
|
||||
info_func: Callable | None = None,
|
||||
) -> None:
|
||||
while True:
|
||||
if await continue_recovery(ctx, button_label, text, subtext, info_func):
|
||||
dry_run = storage.recovery.is_dry_run()
|
||||
if await continue_recovery(
|
||||
ctx, button_label, text, subtext, info_func, dry_run
|
||||
):
|
||||
# go forward in the recovery process
|
||||
break
|
||||
# user has chosen to abort, confirm the choice
|
||||
dry_run = storage.recovery.is_dry_run()
|
||||
try:
|
||||
await confirm_abort(ctx, dry_run)
|
||||
except wire.ActionCancelled:
|
||||
|
@ -1,13 +1,12 @@
|
||||
import storage.recovery
|
||||
from trezor import ui
|
||||
|
||||
|
||||
class RecoveryHomescreen(ui.Component):
|
||||
def __init__(self, text: str, subtext: str | None = None):
|
||||
def __init__(self, dry_run: bool, text: str, subtext: str | None = None):
|
||||
super().__init__()
|
||||
self.text = text
|
||||
self.subtext = subtext
|
||||
self.dry_run = storage.recovery.is_dry_run()
|
||||
self.dry_run = dry_run
|
||||
|
||||
def on_render(self) -> None:
|
||||
if not self.repaint:
|
||||
|
@ -112,8 +112,9 @@ async def continue_recovery(
|
||||
text: str,
|
||||
subtext: str | None,
|
||||
info_func: Callable | None,
|
||||
dry_run: bool,
|
||||
) -> bool:
|
||||
homepage = RecoveryHomescreen(text, subtext)
|
||||
homepage = RecoveryHomescreen(dry_run, text, subtext)
|
||||
if info_func is not None:
|
||||
content = InfoConfirm(
|
||||
homepage,
|
||||
|
@ -277,7 +277,6 @@ async def confirm_path_warning(
|
||||
trezorui2.show_warning(
|
||||
title="Unknown path",
|
||||
description=path,
|
||||
button="CONTINUE",
|
||||
)
|
||||
),
|
||||
"path_warning",
|
||||
@ -758,13 +757,11 @@ async def confirm_metadata(
|
||||
layout = trezorui2.show_warning(
|
||||
title="Unusually high fee",
|
||||
description=param or "",
|
||||
button="CONTINUE",
|
||||
)
|
||||
elif br_type == "change_count_over_threshold":
|
||||
layout = trezorui2.show_warning(
|
||||
title="A lot of change-outputs",
|
||||
description=f"{param} outputs" if param is not None else "",
|
||||
button="CONTINUE",
|
||||
)
|
||||
else:
|
||||
if param is not None:
|
||||
@ -929,7 +926,16 @@ async def show_popup(
|
||||
description_param: str = "",
|
||||
timeout_ms: int = 3000,
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
if subtitle:
|
||||
title += f"\n{subtitle}".format(subtitle)
|
||||
await _RustLayout(
|
||||
trezorui2.show_error(
|
||||
title=title,
|
||||
description=description.format(description_param),
|
||||
button="",
|
||||
time_ms=timeout_ms,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def draw_simple_text(title: str, description: str = "") -> None:
|
||||
|
@ -1,9 +1,12 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor import wire
|
||||
from trezor import strings, wire
|
||||
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
||||
from trezor.enums import ButtonRequestType
|
||||
|
||||
import trezorui2
|
||||
|
||||
from ..common import button_request, interact
|
||||
from . import _RustLayout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -12,8 +15,26 @@ if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
async def _is_confirmed_info(
|
||||
ctx: wire.GenericContext,
|
||||
dialog: _RustLayout,
|
||||
info_func: Callable,
|
||||
) -> bool:
|
||||
while True:
|
||||
result = await ctx.wait(dialog)
|
||||
|
||||
if result is trezorui2.INFO:
|
||||
await info_func(ctx)
|
||||
else:
|
||||
return result is trezorui2.CONFIRMED
|
||||
|
||||
|
||||
async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
||||
raise NotImplementedError
|
||||
selector = _RustLayout(trezorui2.select_word_count(dry_run=dry_run))
|
||||
count = await interact(
|
||||
ctx, selector, "word_count", ButtonRequestType.MnemonicWordCount
|
||||
)
|
||||
return int(count)
|
||||
|
||||
|
||||
async def request_word(
|
||||
@ -42,13 +63,54 @@ async def show_remaining_shares(
|
||||
shares_remaining: list[int],
|
||||
group_threshold: int,
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
pages: list[tuple[str, str]] = []
|
||||
for remaining, group in groups:
|
||||
if 0 < remaining < MAX_SHARE_COUNT:
|
||||
title = strings.format_plural(
|
||||
"{count} more {plural} starting", remaining, "share"
|
||||
)
|
||||
words = "\n".join(group)
|
||||
pages.append((title, words))
|
||||
elif (
|
||||
remaining == MAX_SHARE_COUNT and shares_remaining.count(0) < group_threshold
|
||||
):
|
||||
groups_remaining = group_threshold - shares_remaining.count(0)
|
||||
title = strings.format_plural(
|
||||
"{count} more {plural} starting", groups_remaining, "group"
|
||||
)
|
||||
words = "\n".join(group)
|
||||
pages.append((title, words))
|
||||
|
||||
result = await interact(
|
||||
ctx,
|
||||
_RustLayout(trezorui2.show_remaining_shares(pages=pages)),
|
||||
"show_shares",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
if result is not trezorui2.CONFIRMED:
|
||||
raise wire.ActionCancelled
|
||||
|
||||
|
||||
async def show_group_share_success(
|
||||
ctx: wire.GenericContext, share_index: int, group_index: int
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
result = await interact(
|
||||
ctx,
|
||||
_RustLayout(
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
"You have entered",
|
||||
f"Share {share_index + 1}",
|
||||
"from",
|
||||
f"Group {group_index + 1}",
|
||||
],
|
||||
)
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
if result is not trezorui2.CONFIRMED:
|
||||
raise wire.ActionCancelled
|
||||
|
||||
|
||||
async def continue_recovery(
|
||||
@ -57,5 +119,41 @@ async def continue_recovery(
|
||||
text: str,
|
||||
subtext: str | None,
|
||||
info_func: Callable | None,
|
||||
dry_run: bool,
|
||||
) -> bool:
|
||||
return False
|
||||
title = text
|
||||
if subtext:
|
||||
title += "\n"
|
||||
title += subtext
|
||||
|
||||
description = "It is safe to eject Trezor\nand continue later"
|
||||
|
||||
if info_func is not None:
|
||||
homepage = _RustLayout(
|
||||
trezorui2.confirm_recovery(
|
||||
title=title,
|
||||
description=description,
|
||||
button=button_label.upper(),
|
||||
info_button=True,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
)
|
||||
await button_request(ctx, "recovery", ButtonRequestType.RecoveryHomepage)
|
||||
return await _is_confirmed_info(ctx, homepage, info_func)
|
||||
else:
|
||||
homepage = _RustLayout(
|
||||
trezorui2.confirm_recovery(
|
||||
title=text,
|
||||
description=description,
|
||||
button=button_label.upper(),
|
||||
info_button=False,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
)
|
||||
result = await interact(
|
||||
ctx,
|
||||
homepage,
|
||||
"recovery",
|
||||
ButtonRequestType.RecoveryHomepage,
|
||||
)
|
||||
return result is trezorui2.CONFIRMED
|
||||
|
@ -2485,37 +2485,37 @@
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[u2f_counter-1]": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "fb6d1ba5b6f8b0c28d7c114a1cad2170029800a50cea1dce1b222b56c53d4169",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "fb6d1ba5b6f8b0c28d7c114a1cad2170029800a50cea1dce1b222b56c53d4169",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "fb6d1ba5b6f8b0c28d7c114a1cad2170029800a50cea1dce1b222b56c53d4169",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "98f8d8961d364bd839ece099ef67576cdcd27529267619cadf963dec6cc7144d",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "98f8d8961d364bd839ece099ef67576cdcd27529267619cadf963dec6cc7144d",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "98f8d8961d364bd839ece099ef67576cdcd27529267619cadf963dec6cc7144d",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_dryrun.py::test_uninitialized": "8711e2fa6f7b301add7641e08ffb4bacf29bcd41530b1dd435fdbddb49b4bdf8",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_already_initialized": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_nopin_nopassphrase": "3919d9404e9f9a4880bd084edbfa02fbb04641008e04b83458633691e69bf239",
|
||||
"TTui2_reset_recovery-test_recovery_bip39_t2.py::test_tt_pin_passphrase": "3919d9404e9f9a4880bd084edbfa02fbb04641008e04b83458633691e69bf239",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_abort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_extra_share_entered": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_group_threshold_reached": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_noabort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_abort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_ask_word_number": "e53306364b3a4cc2d23da5adeafa6f02fd946dcf042c6c77efd1ce221a319ea8",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_noabort": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_recover_with_pin_passphrase": "ff0120b13a8ec8ecfe3a70d3dce62a9eaafa116632284d85983e7d1f040d6d4a",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_same_share": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39-a50896b7": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[0]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[1]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "4b94af756dc9288eca587760a32e66abcac622da498d6a9b5bfb5f965f295d2f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "7a5048ee96f76bb2e2a6d64fd89dfc22eb6fe792eaa769058249d0f552ee59d3",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_abort": "7717c45923e9f73efd1201a728e659db2cf3631c7d244418b77fc04301875a10",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_extra_share_entered": "b9575651cef23a6265294937ef7726475b1b3eaba418528b3116dee96b2dfe9e",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_group_threshold_reached": "101e7e7eee51ed188985046f5e01fbc4600ba263db9015217b916b5a3a6ce65c",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_noabort": "3bc2a8d4c8371e8fb799e22d21a9a497f13939208b38d2a85aead1b875952aa7",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "0ed614e0e794f9fa767686c79c704c78707d8e2bd3393206cd5bdc2fd7201759",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "43fc36511b94da979441e3683888b1aa5587d5d58d719b021539df17e2848fb6",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "10e782cd14802e4d5003619888a169f6c387c9a3b74e3a058482878b83e1dd3a",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "a41b5343855c5a4aae5a5e7b937623e0302fda752561a36fb34b3c5f95bd655a",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "566c82e28c238e2aeaa6e3a4b98bdfe6efa9aaf54a17098669ad5e2ab8682262",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "bb859924ef406c39a8ee48e959e781fb051c8cc9453b429e01eecd2a6ccea997",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "157eb87f8a2d7f89fdbfea064a111a3582c8e150f1dd6553fbc7ef393ab145d8",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "04fd1206841ecf3debd8c29a86a90f611714291e0dbd762e20d3caf4fb851268",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_abort": "7717c45923e9f73efd1201a728e659db2cf3631c7d244418b77fc04301875a10",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_ask_word_number": "0ed3d06281d5b16c9258f10c936f07d1664d0643f7f957edd79566fd605926e7",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_noabort": "55fa3f81be735f0dffa9cc3469885e7cae3c4721d8030d1b9da32a2b3e15784f",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_recover_with_pin_passphrase": "3d24011a6388d2e0d31138a7029cb5672da572a0aabc9322473ff9ca2f42bde0",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_same_share": "c94d6cfd3a3617de12b65be94eec59b0dfdeb4d5c91c04c04bd9db60632761d0",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "4b4670e1287dbb625c7a286d8c6a5c235d9ddb1d2a2b23824bfca16037eae082",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39-a50896b7": "3eb8b93b8a88287bbe846281569138b508be404f713c59672149dd8ff6fdb502",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[0]": "6f689a518dcc01536ad99107261c856e16344f1f414c42da0eb18fc441c6fe3e",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[1]": "1b01e0fe891dc6a3d390fa2c06e19d7255a5167a4d8bf5e53dda1ea2e68487e3",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "26e22c5bc293462e886522c972928c3c2dac5ccee9d8355484736e6df9d33a56",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "9548fcdd9e0790576135e805ca87e53f17b7c414e722659d71ece125e768c914",
|
||||
"TTui2_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "97b30d608c58c7831ceef9fd9127d9699766b1ae98843886d8f9b5220e536562",
|
||||
"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "e858239e4efffb8c185c098c8c7a0b9ca19d4b3c4836ee43b6db926abf7918bc",
|
||||
"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "bd1f14226b2b3b778dc146ab8e4d0b0535657649330ca5dd3efa4c00a70ecb24",
|
||||
"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "9f0568a1782a4d392cf64c2f4478b6fadc6ffcaea8a3c706c04ba798d2fa5195",
|
||||
@ -2523,14 +2523,14 @@
|
||||
"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "00ffcd324fa349282cd08524722e92b0b8469739259f66b8de30182dba6f6607",
|
||||
"TTui2_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "77797bb5a33f8ffafb778b85a1a5dd2f59ec651c03e71c681656118ea15ed0cc",
|
||||
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
|
||||
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "e34eb8420d9e74571c36e39352ba2308d90a021b2f5ef2e78afb167764ea931d",
|
||||
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "5b9750276d378866c33339e9bb644fe7be522b765d75d5f41dc955058a25760e",
|
||||
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "c9791eaa949c37a6996ec08677b1aff00dd294097c62cc51b905e115fd32cdb3",
|
||||
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "a21078e36aa4b49377ef3fc1ad6084f725eb3941608a03e1bc899dca828d07ad",
|
||||
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "6818c19bba26ef8acecd87cadbe5bf678449a519e49cc365311708d91abe92be",
|
||||
"TTui2_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "fad48c5d40c5df33cbf4abbb7695f6c22b490ab3f118cfdf3b64b2bc2936920f",
|
||||
"TTui2_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "1c463175327e8a0286464e8c7165de3a1434b3c48e7f7b6ed47edfaa992bb039",
|
||||
"TTui2_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "6dc1c0d4106d8a789a7eae973a3b768cb1a5383fdf42866e51516e67fd4a36ca",
|
||||
"TTui2_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "78cd86e1e473e5cdd541840a657723f663d9294fc4fdc74af97274ea1b3939b8",
|
||||
"TTui2_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "447d34b14feaffdca56fb48e2490c4a47a875edc7d7ad1952feb7d9df9934705",
|
||||
"TTui2_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "014bafb47680c0cf601e01fa82df249dbdb4c78a170d871683d878e11d428a3e",
|
||||
"TTui2_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "40ccc74d7bc2ea9d5a76e62c4867316a84625c373c0bae5998b518f8060604d7",
|
||||
"TTui2_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "16fdec338958b038ecb96614cb8b47d5dcdd61bf1422e9dff8bfd8022fef1536",
|
||||
"TTui2_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "e68ba714482d7d6239a4e4d43d890a3de340230c5618fc820b1c24027c87b72d",
|
||||
"TTui2_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "146b4d7880fd9bd325da46d675ec4ee4f88e27916eba3198d911c3c4c6a5e29f",
|
||||
@ -2626,13 +2626,13 @@
|
||||
"TTui2_test_msg_backup_device.py::test_no_backup_show_entropy_fails": "8711e2fa6f7b301add7641e08ffb4bacf29bcd41530b1dd435fdbddb49b4bdf8",
|
||||
"TTui2_test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "a6976555523e774fc1eb0ff1c192cdca6f6298cebc962a8d4b87d197a945af87",
|
||||
"TTui2_test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "e7a3858d2db160253ff3dbde450e5632fcc385ff529d586de28c49f0bf4ed059",
|
||||
"TTui2_test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "8fd746c535ec5add348b76002a7936cc85c3206edbb59f225ad075912329452d",
|
||||
"TTui2_test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "25eac0cb6ea45c0cb9cfcad3b4ac3ec33af9212a7b812370c8132ef9f14c7700",
|
||||
"TTui2_test_msg_changepin_t2.py::test_change_failed": "e207e2c62f6930e9e112d7a1a31b9a66c14580df8aac82ea40e2f243d987e878",
|
||||
"TTui2_test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "a8e165eb64558ee3f38adb334a123d20ae40088515d559e06e3dcc6ab960f865",
|
||||
"TTui2_test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "2b681988285d472e128edcb972cff8784ed34c9cba2f2a02e70a243d6561f86a",
|
||||
"TTui2_test_msg_changepin_t2.py::test_change_failed": "5409de461cc6264246e07a5393c7fba972453b1af329f1ea27121512cffda419",
|
||||
"TTui2_test_msg_changepin_t2.py::test_change_invalid_current": "5e04bc7ab716549d8aa70087cac37c8e1beafaad9929713a631e11845102d4e9",
|
||||
"TTui2_test_msg_changepin_t2.py::test_change_pin": "2b891a989548802893f1b6a486e9751704a460ce4f59b65b39315318e11171f2",
|
||||
"TTui2_test_msg_changepin_t2.py::test_remove_pin": "0483000d2760100596744b4270119860925f767028dfc6453141d4279fadb468",
|
||||
"TTui2_test_msg_changepin_t2.py::test_set_failed": "391b309cadaefcaab9086f7e003faec88b7e38c13f2738b5ad1aa4bfd5d89566",
|
||||
"TTui2_test_msg_changepin_t2.py::test_set_failed": "8870468b6656512de925b8c9a84bc3755dc39ae2b86503ff823f068efa38e5a8",
|
||||
"TTui2_test_msg_changepin_t2.py::test_set_pin": "9fa58d0b6e5dcaa581f7bbccc4e6a84c4de732200c2bc8465b83a79beceb55d5",
|
||||
"TTui2_test_msg_loaddevice.py::test_load_device_1": "eeb5afb34b4bbf42b8c635fdd34bae5c1e3693facb16e6d64e629746612a2c3f",
|
||||
"TTui2_test_msg_loaddevice.py::test_load_device_2": "a95020926a62b4078cb0034f6e7a772e49fc42121c9197b534437e26c306a994",
|
||||
|
Loading…
Reference in New Issue
Block a user