1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 15:38:11 +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:
obrusvit 2024-11-09 18:15:05 +01:00
parent a7a12cf898
commit 9bff86142d
18 changed files with 356 additions and 204 deletions

View File

@ -254,7 +254,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;
@ -291,6 +290,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;
@ -641,7 +641,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;
@ -658,6 +657,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;
@ -683,7 +683,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;

View File

@ -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,

View File

@ -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"))

View File

@ -658,38 +658,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()?;
@ -932,19 +900,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,

View File

@ -540,6 +540,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> {

View File

@ -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);
@ -171,13 +172,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
};

View File

@ -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
@ -1016,11 +997,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(),
};

View File

@ -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> {

View File

@ -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;

View File

@ -0,0 +1,115 @@
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(&mut self) -> usize {
// Not defining the logic here, as we do not want it to be `&mut`.
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());
}
}

View File

@ -767,29 +767,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
@ -919,14 +896,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)]

View File

@ -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> {

View File

@ -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>>,

View File

@ -136,20 +136,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(
*,
@ -386,14 +372,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 *
@ -530,12 +508,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."""

View File

@ -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(
*,

View File

@ -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,

View File

@ -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,

View File

@ -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,