mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-01 11:58:28 +00:00
refactor(core): move and cleanup show_share_words
- model_t version was moved from using plain Paragraph to a dedicated component `ShareWords` so that it's consistent with other models. This allowed to move formatting to Rust and allowed the trait function to have `words` parameter of type `Vec<TString, 33>` - model_r ShareWords::render slightly refactored to be consistent with the new model_t version - mercury uses a unique version. The reason is that mercury SwipeFlow contains also the initial screen with instructions and prompt screen at the end.
This commit is contained in:
parent
66473aa594
commit
1653c7d250
@ -256,7 +256,6 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_flow_confirm_summary;
|
||||
MP_QSTR_flow_get_address;
|
||||
MP_QSTR_flow_prompt_backup;
|
||||
MP_QSTR_flow_show_share_words;
|
||||
MP_QSTR_flow_warning_hi_prio;
|
||||
MP_QSTR_get_language;
|
||||
MP_QSTR_get_transition_out;
|
||||
@ -293,6 +292,7 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_inputs__return;
|
||||
MP_QSTR_inputs__show;
|
||||
MP_QSTR_inputs__space;
|
||||
MP_QSTR_instructions;
|
||||
MP_QSTR_instructions__continue_holding;
|
||||
MP_QSTR_instructions__continue_in_app;
|
||||
MP_QSTR_instructions__enter_next_share;
|
||||
@ -643,7 +643,6 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_set_brightness;
|
||||
MP_QSTR_setting__adjust;
|
||||
MP_QSTR_setting__apply;
|
||||
MP_QSTR_share_words;
|
||||
MP_QSTR_share_words__words_in_order;
|
||||
MP_QSTR_share_words__wrote_down_all;
|
||||
MP_QSTR_show_address_details;
|
||||
@ -660,6 +659,7 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_show_progress_coinjoin;
|
||||
MP_QSTR_show_remaining_shares;
|
||||
MP_QSTR_show_share_words;
|
||||
MP_QSTR_show_share_words_mercury;
|
||||
MP_QSTR_show_simple;
|
||||
MP_QSTR_show_success;
|
||||
MP_QSTR_show_wait_text;
|
||||
@ -685,7 +685,6 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_summary_title;
|
||||
MP_QSTR_text;
|
||||
MP_QSTR_text_confirm;
|
||||
MP_QSTR_text_info;
|
||||
MP_QSTR_text_mono;
|
||||
MP_QSTR_time_ms;
|
||||
MP_QSTR_timer;
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
io::BinaryData,
|
||||
micropython::{
|
||||
gc::Gc,
|
||||
iter::IterBuf,
|
||||
list::List,
|
||||
macros::{obj_fn_1, obj_fn_kw, obj_module},
|
||||
map::Map,
|
||||
@ -25,6 +26,7 @@ use crate::{
|
||||
ui_features_fw::UIFeaturesFirmware,
|
||||
},
|
||||
};
|
||||
use heapless::Vec;
|
||||
|
||||
/// Dummy implementation so that we can use `Empty` in a return type of unimplemented trait
|
||||
/// function
|
||||
@ -478,6 +480,54 @@ extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs:
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let words: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
|
||||
let title: Option<TString> = kwargs
|
||||
.get(Qstr::MP_QSTR_title)
|
||||
.and_then(Obj::try_into_option)
|
||||
.unwrap_or(None);
|
||||
|
||||
let words: Vec<TString, 33> = util::iter_into_vec(words)?;
|
||||
|
||||
let layout = ModelUI::show_share_words(words, title)?;
|
||||
Ok(LayoutObj::new_root(layout)?.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_share_words_mercury(
|
||||
n_args: usize,
|
||||
args: *const Obj,
|
||||
kwargs: *mut Map,
|
||||
) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let words: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
|
||||
let subtitle: Option<TString> = kwargs
|
||||
.get(Qstr::MP_QSTR_subtitle)
|
||||
.and_then(Obj::try_into_option)
|
||||
.unwrap_or(None);
|
||||
let instructions: Obj = kwargs.get(Qstr::MP_QSTR_instructions)?;
|
||||
let text_footer: Option<TString> = kwargs
|
||||
.get(Qstr::MP_QSTR_description)
|
||||
.and_then(Obj::try_into_option)
|
||||
.unwrap_or(None);
|
||||
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
|
||||
|
||||
let words: Vec<TString, 33> = util::iter_into_vec(words)?;
|
||||
|
||||
let layout = ModelUI::show_share_words_mercury(
|
||||
words,
|
||||
subtitle,
|
||||
instructions,
|
||||
text_footer,
|
||||
text_confirm,
|
||||
)?;
|
||||
Ok(LayoutObj::new_root(layout)?.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let text: TString = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?;
|
||||
@ -923,6 +973,26 @@ pub static mp_module_trezorui_api: Module = obj_module! {
|
||||
/// """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(),
|
||||
|
||||
/// def show_share_words(
|
||||
/// *,
|
||||
/// words: Iterable[str],
|
||||
/// title: str | None = None,
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show mnemonic for backup."""
|
||||
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
|
||||
|
||||
/// def show_share_words_mercury(
|
||||
/// *,
|
||||
/// words: Iterable[str],
|
||||
/// subtitle: str | None,
|
||||
/// instructions: Iterable[str],
|
||||
/// text_footer: str | None,
|
||||
/// text_confirm: str,
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show mnemonic for wallet backup preceded by an instruction screen and followed by a
|
||||
/// confirmation screen."""
|
||||
Qstr::MP_QSTR_show_share_words_mercury => obj_fn_kw!(0, new_show_share_words_mercury).as_obj(),
|
||||
|
||||
/// def show_simple(
|
||||
/// *,
|
||||
/// text: str,
|
||||
|
@ -78,15 +78,15 @@ fn footer_updating_func(
|
||||
}
|
||||
|
||||
pub fn new_show_share_words(
|
||||
title: TString<'static>,
|
||||
subtitle: TString<'static>,
|
||||
share_words_vec: Vec<TString<'static>, 33>,
|
||||
description: Option<TString<'static>>,
|
||||
subtitle: TString<'static>,
|
||||
instructions_paragraphs: ParagraphVecShort<'static>,
|
||||
text_footer: Option<TString<'static>>,
|
||||
text_confirm: TString<'static>,
|
||||
) -> Result<SwipeFlow, error::Error> {
|
||||
let nwords = share_words_vec.len();
|
||||
let paragraphs_spacing = 8;
|
||||
let title = TR::reset__recovery_wallet_backup_title.into();
|
||||
|
||||
let content_instruction = Frame::left_aligned(
|
||||
title,
|
||||
@ -97,7 +97,7 @@ pub fn new_show_share_words(
|
||||
),
|
||||
)
|
||||
.with_subtitle(TR::words__instructions.into())
|
||||
.with_footer(TR::instructions__swipe_up.into(), description)
|
||||
.with_footer(TR::instructions__swipe_up.into(), text_footer)
|
||||
.with_swipe(Direction::Up, SwipeSettings::default())
|
||||
.map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed))
|
||||
.one_button_request(ButtonRequestCode::ResetDevice.with_name("share_words"))
|
||||
|
@ -695,38 +695,6 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let subtitle: TString = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into()?;
|
||||
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
|
||||
let share_words_vec: Vec<TString, 33> = util::iter_into_vec(share_words_obj)?;
|
||||
let description: Option<TString> = kwargs
|
||||
.get(Qstr::MP_QSTR_description)?
|
||||
.try_into_option()?
|
||||
.and_then(|desc: TString| if desc.is_empty() { None } else { Some(desc) });
|
||||
let text_info: Obj = kwargs.get(Qstr::MP_QSTR_text_info)?;
|
||||
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
|
||||
|
||||
let mut instructions_paragraphs = ParagraphVecShort::new();
|
||||
for item in IterBuf::new().try_iterate(text_info)? {
|
||||
let text: TString = item.try_into()?;
|
||||
instructions_paragraphs.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text));
|
||||
}
|
||||
|
||||
let flow = flow::show_share_words::new_show_share_words(
|
||||
title,
|
||||
subtitle,
|
||||
share_words_vec,
|
||||
description,
|
||||
instructions_paragraphs,
|
||||
text_confirm,
|
||||
)?;
|
||||
Ok(LayoutObj::new_root(flow)?.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_confirm_with_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
@ -948,19 +916,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// """Prompt a user to create backup with an option to skip."""
|
||||
Qstr::MP_QSTR_flow_prompt_backup => obj_fn_0!(new_prompt_backup).as_obj(),
|
||||
|
||||
/// def flow_show_share_words(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// subtitle: str,
|
||||
/// words: Iterable[str],
|
||||
/// description: str,
|
||||
/// text_info: Iterable[str],
|
||||
/// text_confirm: str,
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show wallet backup words preceded by an instruction screen and followed by
|
||||
/// confirmation."""
|
||||
Qstr::MP_QSTR_flow_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
|
||||
|
||||
/// def flow_get_address(
|
||||
/// *,
|
||||
/// address: str | bytes,
|
||||
|
@ -545,6 +545,38 @@ impl UIFeaturesFirmware for ModelMercuryFeatures {
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn show_share_words(
|
||||
words: heapless::Vec<TString<'static>, 33>,
|
||||
_title: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelMercuryFeatures>, Error>(Error::ValueError(
|
||||
c"use flow_share_words instead",
|
||||
))
|
||||
}
|
||||
|
||||
fn show_share_words_mercury(
|
||||
words: heapless::Vec<TString<'static>, 33>,
|
||||
subtitle: Option<TString<'static>>,
|
||||
instructions: crate::micropython::obj::Obj,
|
||||
text_footer: Option<TString<'static>>,
|
||||
text_confirm: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
let mut instructions_paragraphs = ParagraphVecShort::new();
|
||||
for item in crate::micropython::iter::IterBuf::new().try_iterate(instructions)? {
|
||||
let text: TString = item.try_into()?;
|
||||
instructions_paragraphs.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text));
|
||||
}
|
||||
|
||||
let flow = flow::show_share_words::new_show_share_words(
|
||||
words,
|
||||
subtitle.unwrap_or(TString::empty()),
|
||||
instructions_paragraphs,
|
||||
text_footer,
|
||||
text_confirm,
|
||||
)?;
|
||||
Ok(flow)
|
||||
}
|
||||
|
||||
fn show_remaining_shares(
|
||||
pages_iterable: crate::micropython::obj::Obj, // TODO: replace Obj
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
|
@ -92,23 +92,24 @@ impl<'a> ShareWords<'a> {
|
||||
fn render_words<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let mut y_offset = 0;
|
||||
// Showing the word index and the words itself
|
||||
for i in 0..WORDS_PER_PAGE {
|
||||
for (word_idx, word) in self
|
||||
.share_words
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(self.page_index * WORDS_PER_PAGE)
|
||||
.take(WORDS_PER_PAGE)
|
||||
{
|
||||
let ordinal = word_idx + 1;
|
||||
y_offset += NUMBER_FONT.line_height() + EXTRA_LINE_HEIGHT;
|
||||
let index = self.word_index() + i;
|
||||
if index >= self.share_words.len() {
|
||||
break;
|
||||
}
|
||||
let word = &self.share_words[index];
|
||||
let baseline = self.area.top_left() + Offset::y(y_offset);
|
||||
let ordinal = uformat!("{}.", index + 1);
|
||||
let base = self.area.top_left() + Offset::y(y_offset);
|
||||
|
||||
shape::Text::new(baseline + Offset::x(NUMBER_X_OFFSET), &ordinal)
|
||||
let ordinal_txt = uformat!("{}.", ordinal);
|
||||
shape::Text::new(base + Offset::x(NUMBER_X_OFFSET), &ordinal_txt)
|
||||
.with_font(NUMBER_FONT)
|
||||
.with_fg(theme::FG)
|
||||
.render(target);
|
||||
|
||||
word.map(|w| {
|
||||
shape::Text::new(baseline + Offset::x(WORD_X_OFFSET), w)
|
||||
shape::Text::new(base + Offset::x(WORD_X_OFFSET), w)
|
||||
.with_font(WORD_FONT)
|
||||
.with_fg(theme::FG)
|
||||
.render(target);
|
||||
@ -170,13 +171,15 @@ impl<'a> crate::trace::Trace for ShareWords<'a> {
|
||||
self.get_final_text()
|
||||
} else {
|
||||
let mut content = ShortString::new();
|
||||
for i in 0..WORDS_PER_PAGE {
|
||||
let index = self.word_index() + i;
|
||||
if index >= self.share_words.len() {
|
||||
break;
|
||||
}
|
||||
self.share_words[index]
|
||||
.map(|word| unwrap!(uwrite!(content, "{}. {}\n", index + 1, word)));
|
||||
for (word_idx, word) in self
|
||||
.share_words
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(self.page_index * WORDS_PER_PAGE)
|
||||
.take(WORDS_PER_PAGE)
|
||||
{
|
||||
let ordinal = word_idx + 1;
|
||||
word.map(|w| unwrap!(uwrite!(content, "{}. {}\n", ordinal, w)));
|
||||
}
|
||||
content
|
||||
};
|
||||
|
@ -839,25 +839,6 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = |_args: &[Obj], kwargs: &Map| {
|
||||
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
|
||||
let share_words: Vec<TString, 33> = util::iter_into_vec(share_words_obj)?;
|
||||
|
||||
let cancel_btn = Some(ButtonDetails::up_arrow_icon());
|
||||
let confirm_btn =
|
||||
Some(ButtonDetails::text(TR::buttons__hold_to_confirm.into()).with_default_duration());
|
||||
|
||||
let obj = LayoutObj::new(
|
||||
ButtonPage::new(ShareWords::new(share_words), theme::BG)
|
||||
.with_cancel_btn(cancel_btn)
|
||||
.with_confirm_btn(confirm_btn),
|
||||
)?;
|
||||
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! {
|
||||
/// from trezor import utils
|
||||
@ -1017,11 +998,4 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// """Confirm long content with the possibility to go back from any page.
|
||||
/// Meant to be used with confirm_with_info."""
|
||||
Qstr::MP_QSTR_confirm_more => obj_fn_kw!(0, new_confirm_more).as_obj(),
|
||||
|
||||
/// def show_share_words(
|
||||
/// *,
|
||||
/// share_words: Iterable[str],
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Shows a backup seed."""
|
||||
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
|
||||
};
|
||||
|
@ -36,7 +36,7 @@ use super::{
|
||||
component::{
|
||||
ButtonDetails, ButtonPage, CoinJoinProgress, ConfirmHomescreen, Flow, FlowPages, Frame,
|
||||
Homescreen, Lockscreen, NumberInput, PassphraseEntry, PinEntry, Progress, ScrollableFrame,
|
||||
SimpleChoice, WordlistEntry, WordlistType,
|
||||
ShareWords, SimpleChoice, WordlistEntry, WordlistType,
|
||||
},
|
||||
theme, ModelTRFeatures,
|
||||
};
|
||||
@ -603,6 +603,34 @@ impl UIFeaturesFirmware for ModelTRFeatures {
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn show_share_words(
|
||||
words: heapless::Vec<TString<'static>, 33>,
|
||||
_title: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
let cancel_btn = Some(ButtonDetails::up_arrow_icon());
|
||||
let confirm_btn =
|
||||
Some(ButtonDetails::text(TR::buttons__hold_to_confirm.into()).with_default_duration());
|
||||
|
||||
let layout = RootComponent::new(
|
||||
ButtonPage::new(ShareWords::new(words), theme::BG)
|
||||
.with_cancel_btn(cancel_btn)
|
||||
.with_confirm_btn(confirm_btn),
|
||||
);
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
fn show_share_words_mercury(
|
||||
_words: heapless::Vec<TString<'static>, 33>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_instructions: crate::micropython::obj::Obj,
|
||||
_text_footer: Option<TString<'static>>,
|
||||
_text_confirm: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelTRFeatures>, Error>(Error::ValueError(
|
||||
c"use show_share_words",
|
||||
))
|
||||
}
|
||||
|
||||
fn show_remaining_shares(
|
||||
pages_iterable: crate::micropython::obj::Obj, // TODO: replace Obj
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
|
@ -23,6 +23,8 @@ mod progress;
|
||||
mod result;
|
||||
mod scroll;
|
||||
mod set_brightness;
|
||||
#[cfg(feature = "translations")]
|
||||
mod share_words;
|
||||
mod simple_page;
|
||||
mod swipe;
|
||||
mod welcome_screen;
|
||||
@ -59,6 +61,8 @@ pub use progress::Progress;
|
||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
||||
pub use scroll::ScrollBar;
|
||||
pub use set_brightness::SetBrightnessDialog;
|
||||
#[cfg(feature = "translations")]
|
||||
pub use share_words::ShareWords;
|
||||
pub use simple_page::SimplePage;
|
||||
pub use swipe::{Swipe, SwipeDirection};
|
||||
pub use welcome_screen::WelcomeScreen;
|
||||
|
114
core/embed/rust/src/ui/model_tt/component/share_words.rs
Normal file
114
core/embed/rust/src/ui/model_tt/component/share_words.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Never, Paginate},
|
||||
display::Font,
|
||||
geometry::{Offset, Rect},
|
||||
model_tt::theme,
|
||||
shape::{self, Renderer},
|
||||
},
|
||||
};
|
||||
use heapless::Vec;
|
||||
use ufmt::uwrite;
|
||||
|
||||
const WORDS_PER_PAGE: usize = 4;
|
||||
const TOP_PADDING_OFFSET: i16 = 13;
|
||||
const WORD_FONT: Font = Font::MONO;
|
||||
const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less
|
||||
|
||||
/// Showing the given share words.
|
||||
pub struct ShareWords<'a> {
|
||||
area: Rect,
|
||||
share_words: Vec<TString<'a>, MAX_WORDS>,
|
||||
page_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> ShareWords<'a> {
|
||||
pub fn new(share_words: Vec<TString<'a>, MAX_WORDS>) -> Self {
|
||||
Self {
|
||||
area: Rect::zero(),
|
||||
share_words,
|
||||
page_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn total_page_count(&self) -> usize {
|
||||
(self.share_words.len() + WORDS_PER_PAGE - 1) / WORDS_PER_PAGE
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Component for ShareWords<'a> {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.area = bounds;
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let line_height = WORD_FONT.line_height();
|
||||
let ordinal_largest_on_this_page =
|
||||
(WORDS_PER_PAGE * (self.page_index + 1)).min(self.share_words.len());
|
||||
let is_largest_double_digit = ordinal_largest_on_this_page >= 10;
|
||||
let mut y_offset = self.area.top_left().y + TOP_PADDING_OFFSET;
|
||||
|
||||
for (word_idx, word) in self
|
||||
.share_words
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(self.page_index * WORDS_PER_PAGE)
|
||||
.take(WORDS_PER_PAGE)
|
||||
{
|
||||
let ordinal = word_idx + 1;
|
||||
let base = self.area.top_left() + Offset::y(y_offset);
|
||||
word.map(|w| {
|
||||
let double_digit = ordinal >= 10;
|
||||
let text_fmt = if double_digit || !is_largest_double_digit {
|
||||
uformat!("{}. {}", ordinal, w)
|
||||
} else {
|
||||
uformat!(" {}. {}", ordinal, w)
|
||||
};
|
||||
shape::Text::new(base, &text_fmt)
|
||||
.with_font(WORD_FONT)
|
||||
.with_fg(theme::FG)
|
||||
.render(target);
|
||||
});
|
||||
y_offset += line_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Paginate for ShareWords<'a> {
|
||||
fn page_count(&self) -> usize {
|
||||
self.total_page_count()
|
||||
}
|
||||
|
||||
fn change_page(&mut self, active_page: usize) {
|
||||
self.page_index = active_page;
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<'a> crate::trace::Trace for ShareWords<'a> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ShareWords");
|
||||
let mut content = heapless::String::<64>::new();
|
||||
for (word_idx, word) in self
|
||||
.share_words
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(self.page_index * WORDS_PER_PAGE)
|
||||
.take(WORDS_PER_PAGE)
|
||||
{
|
||||
let ordinal = word_idx + 1;
|
||||
word.map(|w| unwrap!(uwrite!(content, "{}. {}\n", ordinal, w)));
|
||||
}
|
||||
t.string("screen_content", content.as_str().into());
|
||||
}
|
||||
}
|
@ -754,29 +754,6 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let pages: Obj = kwargs.get(Qstr::MP_QSTR_pages)?;
|
||||
|
||||
let mut paragraphs = ParagraphVecLong::new();
|
||||
for page in IterBuf::new().try_iterate(pages)? {
|
||||
let text: TString = page.try_into()?;
|
||||
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, text).break_after());
|
||||
}
|
||||
|
||||
let obj = LayoutObj::new(Frame::left_aligned(
|
||||
theme::label_title(),
|
||||
title,
|
||||
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
|
||||
.with_hold()?
|
||||
.without_cancel(),
|
||||
))?;
|
||||
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! {
|
||||
/// from trezor import utils
|
||||
@ -907,14 +884,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// """Confirm long content with the possibility to go back from any page.
|
||||
/// Meant to be used with confirm_with_info."""
|
||||
Qstr::MP_QSTR_confirm_more => obj_fn_kw!(0, new_confirm_more).as_obj(),
|
||||
|
||||
/// def show_share_words(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// pages: Iterable[str],
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show mnemonic for backup. Expects the words pre-divided into individual pages."""
|
||||
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -29,7 +29,7 @@ use super::{
|
||||
check_homescreen_format, Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet,
|
||||
CancelConfirmMsg, CoinJoinProgress, Dialog, FidoConfirm, Frame, Homescreen, IconDialog,
|
||||
Lockscreen, MnemonicKeyboard, NumberInputDialog, PassphraseKeyboard, PinKeyboard, Progress,
|
||||
SelectWordCount, SetBrightnessDialog, Slip39Input,
|
||||
SelectWordCount, SetBrightnessDialog, ShareWords, Slip39Input,
|
||||
},
|
||||
theme, ModelTTFeatures,
|
||||
};
|
||||
@ -635,6 +635,32 @@ impl UIFeaturesFirmware for ModelTTFeatures {
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn show_share_words(
|
||||
words: heapless::Vec<TString<'static>, 33>,
|
||||
title: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
let layout = RootComponent::new(Frame::left_aligned(
|
||||
theme::label_title(),
|
||||
title.unwrap_or(TString::empty()),
|
||||
ButtonPage::new(ShareWords::new(words), theme::BG)
|
||||
.with_hold()?
|
||||
.without_cancel(),
|
||||
));
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
fn show_share_words_mercury(
|
||||
_words: heapless::Vec<TString<'static>, 33>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_instructions: crate::micropython::obj::Obj,
|
||||
_text_footer: Option<TString<'static>>,
|
||||
_text_confirm: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelTTFeatures>, Error>(Error::ValueError(
|
||||
c"use show_share_words",
|
||||
))
|
||||
}
|
||||
|
||||
fn show_remaining_shares(
|
||||
pages_iterable: crate::micropython::obj::Obj, // TODO: replace Obj
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
|
@ -4,6 +4,7 @@ use crate::{
|
||||
micropython::{gc::Gc, list::List, obj::Obj},
|
||||
strutil::TString,
|
||||
};
|
||||
use heapless::Vec;
|
||||
|
||||
use super::layout::{
|
||||
obj::{LayoutMaybeTrace, LayoutObj},
|
||||
@ -175,6 +176,20 @@ pub trait UIFeaturesFirmware {
|
||||
pages_iterable: Obj, // TODO: replace Obj
|
||||
) -> Result<impl LayoutMaybeTrace, Error>;
|
||||
|
||||
fn show_share_words(
|
||||
words: Vec<TString<'static>, 33>,
|
||||
title: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error>;
|
||||
|
||||
// TODO: merge with `show_share_words` instead of having specific version for mercury
|
||||
fn show_share_words_mercury(
|
||||
words: Vec<TString<'static>, 33>,
|
||||
subtitle: Option<TString<'static>>,
|
||||
instructions: Obj, // TODO: replace Obj
|
||||
text_footer: Option<TString<'static>>, // footer description at instruction screen
|
||||
text_confirm: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error>;
|
||||
|
||||
fn show_simple(
|
||||
text: TString<'static>,
|
||||
title: Option<TString<'static>>,
|
||||
|
@ -141,20 +141,6 @@ def flow_prompt_backup() -> LayoutObj[UiResult]:
|
||||
"""Prompt a user to create backup with an option to skip."""
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
def flow_show_share_words(
|
||||
*,
|
||||
title: str,
|
||||
subtitle: str,
|
||||
words: Iterable[str],
|
||||
description: str,
|
||||
text_info: Iterable[str],
|
||||
text_confirm: str,
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show wallet backup words preceded by an instruction screen and followed by
|
||||
confirmation."""
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
def flow_get_address(
|
||||
*,
|
||||
@ -392,14 +378,6 @@ def confirm_more(
|
||||
) -> object:
|
||||
"""Confirm long content with the possibility to go back from any page.
|
||||
Meant to be used with confirm_with_info."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tr/layout.rs
|
||||
def show_share_words(
|
||||
*,
|
||||
share_words: Iterable[str],
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Shows a backup seed."""
|
||||
from trezor import utils
|
||||
from trezorui_api import *
|
||||
|
||||
@ -537,12 +515,3 @@ def confirm_more(
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm long content with the possibility to go back from any page.
|
||||
Meant to be used with confirm_with_info."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
def show_share_words(
|
||||
*,
|
||||
title: str,
|
||||
pages: Iterable[str],
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show mnemonic for backup. Expects the words pre-divided into individual pages."""
|
||||
|
@ -361,6 +361,28 @@ def show_remaining_shares(
|
||||
"""Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
|
||||
|
||||
|
||||
# rust/src/ui/api/firmware_upy.rs
|
||||
def show_share_words(
|
||||
*,
|
||||
words: Iterable[str],
|
||||
title: str | None = None,
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show mnemonic for backup."""
|
||||
|
||||
|
||||
# rust/src/ui/api/firmware_upy.rs
|
||||
def show_share_words_mercury(
|
||||
*,
|
||||
words: Iterable[str],
|
||||
subtitle: str | None,
|
||||
instructions: Iterable[str],
|
||||
text_footer: str | None,
|
||||
text_confirm: str,
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show mnemonic for wallet backup preceded by an instruction screen and followed by a
|
||||
confirmation screen."""
|
||||
|
||||
|
||||
# rust/src/ui/api/firmware_upy.rs
|
||||
def show_simple(
|
||||
*,
|
||||
|
@ -1,6 +1,5 @@
|
||||
from typing import Awaitable, Callable, Sequence
|
||||
|
||||
import trezorui2
|
||||
import trezorui_api
|
||||
from trezor import TR, ui
|
||||
from trezor.enums import ButtonRequestType
|
||||
@ -17,9 +16,8 @@ def show_share_words(
|
||||
share_index: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> Awaitable[None]:
|
||||
title = TR.reset__recovery_wallet_backup_title
|
||||
if share_index is None:
|
||||
subtitle = ""
|
||||
subtitle = None
|
||||
elif group_index is None:
|
||||
subtitle = TR.reset__recovery_share_title_template.format(share_index + 1)
|
||||
else:
|
||||
@ -27,24 +25,24 @@ def show_share_words(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
words_count = len(share_words)
|
||||
description = ""
|
||||
text_info = [TR.reset__write_down_words_template.format(words_count)]
|
||||
description = None
|
||||
instructions = [TR.reset__write_down_words_template.format(words_count)]
|
||||
if words_count == 20 and share_index is None:
|
||||
# 1-of-1 SLIP39: inform the user about repeated words
|
||||
text_info.append(TR.reset__words_may_repeat)
|
||||
instructions.append(TR.reset__words_may_repeat)
|
||||
if share_index == 0:
|
||||
# regular SLIP39, 1st share
|
||||
description = TR.instructions__shares_start_with_1
|
||||
text_info.append(TR.reset__repeat_for_all_shares)
|
||||
instructions.append(TR.reset__repeat_for_all_shares)
|
||||
assert len(instructions) < 3
|
||||
text_confirm = TR.reset__words_written_down_template.format(words_count)
|
||||
|
||||
return raise_if_not_confirmed(
|
||||
trezorui2.flow_show_share_words(
|
||||
title=title,
|
||||
subtitle=subtitle,
|
||||
trezorui_api.show_share_words_mercury(
|
||||
words=share_words,
|
||||
description=description,
|
||||
text_info=text_info,
|
||||
subtitle=subtitle,
|
||||
instructions=instructions,
|
||||
text_footer=description,
|
||||
text_confirm=text_confirm,
|
||||
),
|
||||
None,
|
||||
|
@ -1,6 +1,5 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import trezorui2
|
||||
from trezor import TR
|
||||
from trezor.enums import ButtonRequestType
|
||||
import trezorui_api
|
||||
@ -50,8 +49,9 @@ async def show_share_words(
|
||||
)
|
||||
|
||||
result = await interact(
|
||||
trezorui2.show_share_words( # type: ignore [Arguments missing for parameters]
|
||||
share_words=share_words, # type: ignore [No parameter named "share_words"]
|
||||
trezorui_api.show_share_words(
|
||||
words=share_words,
|
||||
title=None,
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
|
@ -1,6 +1,5 @@
|
||||
from typing import Awaitable, Callable, Sequence
|
||||
|
||||
import trezorui2
|
||||
import trezorui_api
|
||||
from trezor import TR
|
||||
from trezor.enums import ButtonRequestType
|
||||
@ -10,30 +9,6 @@ from ..common import interact, raise_if_not_confirmed
|
||||
CONFIRMED = trezorui_api.CONFIRMED # global_import_cache
|
||||
|
||||
|
||||
def _split_share_into_pages(share_words: Sequence[str], per_page: int = 4) -> list[str]:
|
||||
pages: list[str] = []
|
||||
current = ""
|
||||
fill = 2
|
||||
|
||||
for i, word in enumerate(share_words):
|
||||
if i % per_page == 0:
|
||||
if i != 0:
|
||||
pages.append(current)
|
||||
current = ""
|
||||
|
||||
# Align numbers to the right.
|
||||
lastnum = i + per_page + 1
|
||||
fill = 1 if lastnum < 10 else 2
|
||||
else:
|
||||
current += "\n"
|
||||
current += f"{i + 1:>{fill}}. {word}"
|
||||
|
||||
if current:
|
||||
pages.append(current)
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
def show_share_words(
|
||||
share_words: Sequence[str],
|
||||
share_index: int | None = None,
|
||||
@ -48,12 +23,10 @@ def show_share_words(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
|
||||
pages = _split_share_into_pages(share_words)
|
||||
|
||||
return raise_if_not_confirmed(
|
||||
trezorui2.show_share_words(
|
||||
trezorui_api.show_share_words(
|
||||
words=share_words,
|
||||
title=title,
|
||||
pages=pages,
|
||||
),
|
||||
"backup_words",
|
||||
ButtonRequestType.ResetDevice,
|
||||
|
Loading…
Reference in New Issue
Block a user