mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-19 05:58:09 +00:00
refactor(core/mercury): self-updating footer
This commit enables registering function for updating footer and header based on the content. This eliminates the need to create wrappers around Frame to update them. [no changelog]
This commit is contained in:
parent
7a992a593d
commit
d15ecfb859
@ -259,7 +259,6 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_haptic_feedback__enable;
|
MP_QSTR_haptic_feedback__enable;
|
||||||
MP_QSTR_haptic_feedback__subtitle;
|
MP_QSTR_haptic_feedback__subtitle;
|
||||||
MP_QSTR_haptic_feedback__title;
|
MP_QSTR_haptic_feedback__title;
|
||||||
MP_QSTR_highlight_repeated;
|
|
||||||
MP_QSTR_hold;
|
MP_QSTR_hold;
|
||||||
MP_QSTR_hold_danger;
|
MP_QSTR_hold_danger;
|
||||||
MP_QSTR_homescreen__click_to_connect;
|
MP_QSTR_homescreen__click_to_connect;
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
strutil::TString,
|
|
||||||
translations::TR,
|
|
||||||
ui::{
|
|
||||||
component::{swipe_detect::SwipeSettings, Component, SwipeDirection},
|
|
||||||
flow::{Swipable, SwipePage},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
Frame, FrameMsg, InternallySwipable as _, PagedVerticalMenu, SwipeContent,
|
|
||||||
VerticalMenuChoiceMsg,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum ConfirmFido {
|
|
||||||
Intro,
|
|
||||||
ChooseCredential,
|
|
||||||
Details,
|
|
||||||
Tap,
|
|
||||||
Menu,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper that updates `Footer` content whenever page is changed.
|
|
||||||
pub struct ChooseCredential<F: Fn(usize) -> TString<'static>>(
|
|
||||||
Frame<SwipeContent<SwipePage<PagedVerticalMenu<F>>>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<F: Fn(usize) -> TString<'static>> ChooseCredential<F> {
|
|
||||||
pub fn new(label_fn: F, num_accounts: usize) -> Self {
|
|
||||||
let content_choose_credential = Frame::left_aligned(
|
|
||||||
TR::fido__title_select_credential.into(),
|
|
||||||
SwipeContent::new(SwipePage::vertical(PagedVerticalMenu::new(
|
|
||||||
num_accounts,
|
|
||||||
label_fn,
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
.with_subtitle(TR::fido__title_for_authentication.into())
|
|
||||||
.with_menu_button()
|
|
||||||
.with_footer_page_hint(
|
|
||||||
TR::fido__more_credentials.into(),
|
|
||||||
TR::buttons__go_back.into(),
|
|
||||||
TR::instructions__swipe_up.into(),
|
|
||||||
TR::instructions__swipe_down.into(),
|
|
||||||
)
|
|
||||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
|
||||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
|
||||||
.with_vertical_pages();
|
|
||||||
|
|
||||||
Self(content_choose_credential)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F: Fn(usize) -> TString<'static>> Component for ChooseCredential<F> {
|
|
||||||
type Msg = FrameMsg<VerticalMenuChoiceMsg>;
|
|
||||||
|
|
||||||
fn place(&mut self, bounds: crate::ui::geometry::Rect) -> crate::ui::geometry::Rect {
|
|
||||||
self.0.place(bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut crate::ui::component::EventCtx,
|
|
||||||
event: crate::ui::component::Event,
|
|
||||||
) -> Option<Self::Msg> {
|
|
||||||
let msg = self.0.event(ctx, event);
|
|
||||||
let current_page = self.0.inner().inner().inner().current_page();
|
|
||||||
|
|
||||||
self.0.update_footer_counter(
|
|
||||||
ctx,
|
|
||||||
current_page,
|
|
||||||
Some(self.0.inner().inner().inner().num_pages()),
|
|
||||||
);
|
|
||||||
msg
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(&mut self) {
|
|
||||||
self.0.paint()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl crate::ui::shape::Renderer<'s>) {
|
|
||||||
self.0.render(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F: Fn(usize) -> TString<'static>> Swipable for ChooseCredential<F> {
|
|
||||||
fn get_swipe_config(&self) -> crate::ui::component::swipe_detect::SwipeConfig {
|
|
||||||
self.0.get_swipe_config()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_internal_page_count(&self) -> usize {
|
|
||||||
self.0.get_internal_page_count()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
|
||||||
impl<F: Fn(usize) -> TString<'static>> crate::trace::Trace for ChooseCredential<F> {
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
|
||||||
self.0.trace(t)
|
|
||||||
}
|
|
||||||
}
|
|
@ -85,7 +85,9 @@ pub struct Frame<T> {
|
|||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
content: T,
|
content: T,
|
||||||
header: Header,
|
header: Header,
|
||||||
|
header_update_fn: Option<fn(&T, &mut EventCtx, &mut Header)>,
|
||||||
footer: Option<Footer<'static>>,
|
footer: Option<Footer<'static>>,
|
||||||
|
footer_update_fn: Option<fn(&T, &mut EventCtx, &mut Footer)>,
|
||||||
swipe: SwipeConfig,
|
swipe: SwipeConfig,
|
||||||
internal_page_cnt: usize,
|
internal_page_cnt: usize,
|
||||||
horizontal_swipe: HorizontalSwipe,
|
horizontal_swipe: HorizontalSwipe,
|
||||||
@ -106,7 +108,9 @@ where
|
|||||||
border: theme::borders(),
|
border: theme::borders(),
|
||||||
content,
|
content,
|
||||||
header: Header::new(alignment, title),
|
header: Header::new(alignment, title),
|
||||||
|
header_update_fn: None,
|
||||||
footer: None,
|
footer: None,
|
||||||
|
footer_update_fn: None,
|
||||||
swipe: SwipeConfig::new(),
|
swipe: SwipeConfig::new(),
|
||||||
internal_page_cnt: 1,
|
internal_page_cnt: 1,
|
||||||
horizontal_swipe: HorizontalSwipe::new(),
|
horizontal_swipe: HorizontalSwipe::new(),
|
||||||
@ -217,6 +221,16 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_header_update_fn(mut self, f: fn(&T, &mut EventCtx, &mut Header)) -> Self {
|
||||||
|
self.header_update_fn = Some(f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_footer_update_fn(mut self, f: fn(&T, &mut EventCtx, &mut Footer)) -> Self {
|
||||||
|
self.footer_update_fn = Some(f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_danger(self) -> Self {
|
pub fn with_danger(self) -> Self {
|
||||||
self.button_styled(theme::button_danger())
|
self.button_styled(theme::button_danger())
|
||||||
.title_styled(theme::label_title_danger())
|
.title_styled(theme::label_title_danger())
|
||||||
@ -230,15 +244,6 @@ where
|
|||||||
self.header.update_title(ctx, new_title);
|
self.header.update_title(ctx, new_title);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_subtitle(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut EventCtx,
|
|
||||||
new_subtitle: TString<'static>,
|
|
||||||
new_style: Option<TextStyle>,
|
|
||||||
) {
|
|
||||||
self.header.update_subtitle(ctx, new_subtitle, new_style);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_content<F, R>(&mut self, ctx: &mut EventCtx, update_fn: F) -> R
|
pub fn update_content<F, R>(&mut self, ctx: &mut EventCtx, update_fn: F) -> R
|
||||||
where
|
where
|
||||||
F: Fn(&mut EventCtx, &mut T) -> R,
|
F: Fn(&mut EventCtx, &mut T) -> R,
|
||||||
@ -248,17 +253,6 @@ where
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_footer_counter(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut EventCtx,
|
|
||||||
current: usize,
|
|
||||||
max: Option<usize>,
|
|
||||||
) {
|
|
||||||
if let Some(footer) = &mut self.footer {
|
|
||||||
footer.update_page_counter(ctx, current, max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn with_swipe(mut self, dir: SwipeDirection, settings: SwipeSettings) -> Self {
|
pub fn with_swipe(mut self, dir: SwipeDirection, settings: SwipeSettings) -> Self {
|
||||||
self.footer = self.footer.map(|f| f.with_swipe(dir));
|
self.footer = self.footer.map(|f| f.with_swipe(dir));
|
||||||
@ -316,6 +310,16 @@ where
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(header_update_fn) = self.header_update_fn {
|
||||||
|
header_update_fn(&self.content, ctx, &mut self.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(footer_update_fn) = self.footer_update_fn {
|
||||||
|
if let Some(footer) = &mut self.footer {
|
||||||
|
footer_update_fn(&self.content, ctx, footer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,6 @@ mod address_details;
|
|||||||
mod binary_selection;
|
mod binary_selection;
|
||||||
pub mod bl_confirm;
|
pub mod bl_confirm;
|
||||||
mod button;
|
mod button;
|
||||||
#[cfg(feature = "universal_fw")]
|
|
||||||
mod choose_credential;
|
|
||||||
#[cfg(feature = "translations")]
|
#[cfg(feature = "translations")]
|
||||||
mod coinjoin_progress;
|
mod coinjoin_progress;
|
||||||
mod fido;
|
mod fido;
|
||||||
@ -48,8 +46,6 @@ pub use address_details::AddressDetails;
|
|||||||
#[cfg(feature = "ui_overlay")]
|
#[cfg(feature = "ui_overlay")]
|
||||||
pub use binary_selection::{BinarySelection, BinarySelectionMsg};
|
pub use binary_selection::{BinarySelection, BinarySelectionMsg};
|
||||||
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, IconText};
|
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, IconText};
|
||||||
#[cfg(feature = "universal_fw")]
|
|
||||||
pub use choose_credential::ChooseCredential;
|
|
||||||
#[cfg(feature = "translations")]
|
#[cfg(feature = "translations")]
|
||||||
pub use coinjoin_progress::CoinJoinProgress;
|
pub use coinjoin_progress::CoinJoinProgress;
|
||||||
pub use error::ErrorScreen;
|
pub use error::ErrorScreen;
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
use super::{theme, InternallySwipableContent};
|
use super::theme;
|
||||||
use crate::{
|
use crate::{
|
||||||
strutil::TString,
|
strutil::TString,
|
||||||
translations::TR,
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
base::AttachType,
|
base::AttachType, text::TextStyle, Component, Event, EventCtx, Never, SwipeDirection,
|
||||||
swipe_detect::{SwipeConfig, SwipeSettings},
|
|
||||||
Component, Event, EventCtx, Never, SwipeDirection,
|
|
||||||
},
|
},
|
||||||
event::SwipeEvent,
|
event::SwipeEvent,
|
||||||
geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
|
geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
|
||||||
model_mercury::component::{
|
model_mercury::component::{swipe_content::SwipeAttachAnimation, InternallySwipable},
|
||||||
swipe_content::SwipeAttachAnimation, Frame, FrameMsg, InternallySwipable,
|
|
||||||
},
|
|
||||||
shape::{self, Renderer},
|
shape::{self, Renderer},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -25,43 +21,45 @@ type IndexVec = Vec<u8, MAX_WORDS>;
|
|||||||
/// Component showing mnemonic/share words during backup procedure. Model T3T1
|
/// Component showing mnemonic/share words during backup procedure. Model T3T1
|
||||||
/// contains one word per screen. A user is instructed to swipe up/down to see
|
/// contains one word per screen. A user is instructed to swipe up/down to see
|
||||||
/// next/previous word.
|
/// next/previous word.
|
||||||
/// This is a wrapper around a Frame so that the subtitle and Footer of the
|
|
||||||
/// Frame can be updated based on the index of the word shown. Actual share
|
|
||||||
/// words are rendered within `ShareWordsInner` component,
|
|
||||||
pub struct ShareWords<'a> {
|
pub struct ShareWords<'a> {
|
||||||
|
share_words: Vec<TString<'a>, MAX_WORDS>,
|
||||||
subtitle: TString<'static>,
|
subtitle: TString<'static>,
|
||||||
frame: Frame<InternallySwipableContent<ShareWordsInner<'a>>>,
|
page_index: i16,
|
||||||
repeated_indices: Option<IndexVec>,
|
next_index: i16,
|
||||||
|
/// Area reserved for a shown word from mnemonic/share
|
||||||
|
area_word: Rect,
|
||||||
|
progress: i16,
|
||||||
|
attach_animation: SwipeAttachAnimation,
|
||||||
|
wait_for_attach: bool,
|
||||||
|
repeated_indices: IndexVec,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ShareWords<'a> {
|
impl<'a> ShareWords<'a> {
|
||||||
pub fn new(
|
const AREA_WORD_HEIGHT: i16 = 91;
|
||||||
title: TString<'static>,
|
|
||||||
subtitle: TString<'static>,
|
pub fn new(share_words: Vec<TString<'a>, MAX_WORDS>, subtitle: TString<'static>) -> Self {
|
||||||
share_words: Vec<TString<'a>, MAX_WORDS>,
|
let repeated_indices = Self::find_repeated(share_words.as_slice());
|
||||||
highlight_repeated: bool,
|
|
||||||
) -> Self {
|
|
||||||
let repeated_indices = if highlight_repeated {
|
|
||||||
Some(Self::find_repeated(share_words.as_slice()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let n_words = share_words.len();
|
|
||||||
Self {
|
Self {
|
||||||
|
share_words,
|
||||||
subtitle,
|
subtitle,
|
||||||
frame: Frame::left_aligned(
|
page_index: 0,
|
||||||
title,
|
next_index: 0,
|
||||||
InternallySwipableContent::new(ShareWordsInner::new(share_words)),
|
area_word: Rect::zero(),
|
||||||
)
|
progress: 0,
|
||||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
attach_animation: SwipeAttachAnimation::new(),
|
||||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
wait_for_attach: false,
|
||||||
.with_vertical_pages()
|
|
||||||
.with_subtitle(subtitle)
|
|
||||||
.with_footer_counter(TR::instructions__swipe_up.into(), n_words as u8),
|
|
||||||
repeated_indices,
|
repeated_indices,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_first_page(&self) -> bool {
|
||||||
|
self.page_index == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_final_page(&self) -> bool {
|
||||||
|
self.page_index == self.share_words.len() as i16 - 1
|
||||||
|
}
|
||||||
|
|
||||||
fn find_repeated(share_words: &[TString]) -> IndexVec {
|
fn find_repeated(share_words: &[TString]) -> IndexVec {
|
||||||
let mut repeated_indices = IndexVec::new();
|
let mut repeated_indices = IndexVec::new();
|
||||||
for i in (0..share_words.len()).rev() {
|
for i in (0..share_words.len()).rev() {
|
||||||
@ -73,93 +71,16 @@ impl<'a> ShareWords<'a> {
|
|||||||
repeated_indices.reverse();
|
repeated_indices.reverse();
|
||||||
repeated_indices
|
repeated_indices
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Component for ShareWords<'a> {
|
pub fn subtitle(&self) -> (TString<'static>, &'static TextStyle) {
|
||||||
type Msg = FrameMsg<Never>;
|
if self.repeated_indices.contains(&(self.page_index as u8)) {
|
||||||
|
return (
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
TString::from_translation(TR::reset__the_word_is_repeated),
|
||||||
self.frame.place(bounds);
|
&theme::TEXT_SUB_GREEN_LIME,
|
||||||
bounds
|
);
|
||||||
}
|
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
|
||||||
let page_index = self.frame.inner().inner().page_index;
|
|
||||||
if let Some(repeated_indices) = &self.repeated_indices {
|
|
||||||
if repeated_indices.contains(&(page_index as u8)) {
|
|
||||||
let updated_subtitle = TString::from_translation(TR::reset__the_word_is_repeated);
|
|
||||||
self.frame
|
|
||||||
.update_subtitle(ctx, updated_subtitle, Some(theme::TEXT_SUB_GREEN_LIME));
|
|
||||||
} else {
|
|
||||||
self.frame
|
|
||||||
.update_subtitle(ctx, self.subtitle, Some(theme::TEXT_SUB_GREY));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.frame
|
|
||||||
.update_footer_counter(ctx, page_index as usize, None);
|
|
||||||
self.frame.event(ctx, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(&mut self) {
|
(self.subtitle, &theme::TEXT_SUB_GREY)
|
||||||
// TODO: remove when ui-t3t1 done
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
|
||||||
self.frame.render(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "micropython")]
|
|
||||||
impl<'a> crate::ui::flow::Swipable for ShareWords<'a> {
|
|
||||||
fn get_swipe_config(&self) -> SwipeConfig {
|
|
||||||
self.frame.get_swipe_config()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_internal_page_count(&self) -> usize {
|
|
||||||
self.frame.get_internal_page_count()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
|
||||||
impl<'a> crate::trace::Trace for ShareWords<'a> {
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
|
||||||
t.component("ShareWords");
|
|
||||||
t.child("inner", &self.frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShareWordsInner<'a> {
|
|
||||||
share_words: Vec<TString<'a>, MAX_WORDS>,
|
|
||||||
page_index: i16,
|
|
||||||
next_index: i16,
|
|
||||||
/// Area reserved for a shown word from mnemonic/share
|
|
||||||
area_word: Rect,
|
|
||||||
progress: i16,
|
|
||||||
attach_animation: SwipeAttachAnimation,
|
|
||||||
wait_for_attach: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ShareWordsInner<'a> {
|
|
||||||
const AREA_WORD_HEIGHT: i16 = 91;
|
|
||||||
|
|
||||||
fn new(share_words: Vec<TString<'a>, MAX_WORDS>) -> Self {
|
|
||||||
Self {
|
|
||||||
share_words,
|
|
||||||
page_index: 0,
|
|
||||||
next_index: 0,
|
|
||||||
area_word: Rect::zero(),
|
|
||||||
progress: 0,
|
|
||||||
attach_animation: SwipeAttachAnimation::new(),
|
|
||||||
wait_for_attach: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_first_page(&self) -> bool {
|
|
||||||
self.page_index == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_final_page(&self) -> bool {
|
|
||||||
self.page_index == self.share_words.len() as i16 - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_word<'s>(&self, word_index: i16, target: &mut impl Renderer<'s>, area: Rect) {
|
fn render_word<'s>(&self, word_index: i16, target: &mut impl Renderer<'s>, area: Rect) {
|
||||||
@ -200,7 +121,7 @@ impl<'a> ShareWordsInner<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Component for ShareWordsInner<'a> {
|
impl<'a> Component for ShareWords<'a> {
|
||||||
type Msg = Never;
|
type Msg = Never;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -210,7 +131,7 @@ impl<'a> Component for ShareWordsInner<'a> {
|
|||||||
|
|
||||||
self.area_word = Rect::snap(
|
self.area_word = Rect::snap(
|
||||||
used_area.center(),
|
used_area.center(),
|
||||||
Offset::new(used_area.width(), ShareWordsInner::AREA_WORD_HEIGHT),
|
Offset::new(used_area.width(), ShareWords::AREA_WORD_HEIGHT),
|
||||||
Alignment2D::CENTER,
|
Alignment2D::CENTER,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -311,7 +232,7 @@ impl<'a> Component for ShareWordsInner<'a> {
|
|||||||
|
|
||||||
let offset = self
|
let offset = self
|
||||||
.attach_animation
|
.attach_animation
|
||||||
.get_offset(t, ShareWordsInner::AREA_WORD_HEIGHT);
|
.get_offset(t, ShareWords::AREA_WORD_HEIGHT);
|
||||||
|
|
||||||
target.in_clip(self.area_word, &|target| {
|
target.in_clip(self.area_word, &|target| {
|
||||||
target.with_origin(offset, &|target| {
|
target.with_origin(offset, &|target| {
|
||||||
@ -324,7 +245,7 @@ impl<'a> Component for ShareWordsInner<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InternallySwipable for ShareWordsInner<'_> {
|
impl InternallySwipable for ShareWords<'_> {
|
||||||
fn current_page(&self) -> usize {
|
fn current_page(&self) -> usize {
|
||||||
self.page_index as usize
|
self.page_index as usize
|
||||||
}
|
}
|
||||||
@ -335,7 +256,7 @@ impl InternallySwipable for ShareWordsInner<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<'a> crate::trace::Trace for ShareWordsInner<'a> {
|
impl<'a> crate::trace::Trace for ShareWords<'a> {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("ShareWordsInner");
|
t.component("ShareWordsInner");
|
||||||
let word = &self.share_words[self.page_index as usize];
|
let word = &self.share_words[self.page_index as usize];
|
||||||
|
@ -7,11 +7,11 @@ use crate::{
|
|||||||
component::{
|
component::{
|
||||||
swipe_detect::SwipeSettings,
|
swipe_detect::SwipeSettings,
|
||||||
text::paragraphs::{Paragraph, Paragraphs},
|
text::paragraphs::{Paragraph, Paragraphs},
|
||||||
ComponentExt, SwipeDirection,
|
ComponentExt, EventCtx, SwipeDirection,
|
||||||
},
|
},
|
||||||
flow::{
|
flow::{
|
||||||
base::{DecisionBuilder as _, StateChange},
|
base::{DecisionBuilder as _, StateChange},
|
||||||
FlowMsg, FlowState, SwipeFlow,
|
FlowMsg, FlowState, SwipeFlow, SwipePage,
|
||||||
},
|
},
|
||||||
layout::obj::LayoutObj,
|
layout::obj::LayoutObj,
|
||||||
},
|
},
|
||||||
@ -19,8 +19,8 @@ use crate::{
|
|||||||
|
|
||||||
use super::super::{
|
use super::super::{
|
||||||
component::{
|
component::{
|
||||||
ChooseCredential, FidoCredential, Frame, FrameMsg, PromptMsg, PromptScreen, SwipeContent,
|
FidoCredential, Footer, Frame, FrameMsg, InternallySwipable, PagedVerticalMenu, PromptMsg,
|
||||||
VerticalMenu, VerticalMenuChoiceMsg,
|
PromptScreen, SwipeContent, VerticalMenu, VerticalMenuChoiceMsg,
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
};
|
};
|
||||||
@ -85,6 +85,16 @@ pub extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmFido::new_obj) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmFido::new_obj) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn footer_update_fn(
|
||||||
|
content: &SwipeContent<SwipePage<PagedVerticalMenu<impl Fn(usize) -> TString<'static>>>>,
|
||||||
|
ctx: &mut EventCtx,
|
||||||
|
footer: &mut Footer,
|
||||||
|
) {
|
||||||
|
let current_page = content.inner().inner().current_page();
|
||||||
|
let total_pages = content.inner().inner().num_pages();
|
||||||
|
footer.update_page_counter(ctx, current_page, Some(total_pages));
|
||||||
|
}
|
||||||
|
|
||||||
impl ConfirmFido {
|
impl ConfirmFido {
|
||||||
const EXTRA_PADDING: i16 = 6;
|
const EXTRA_PADDING: i16 = 6;
|
||||||
|
|
||||||
@ -123,11 +133,30 @@ impl ConfirmFido {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap_or_else(|_| TString::from_str("-"))
|
.unwrap_or_else(|_| TString::from_str("-"))
|
||||||
};
|
};
|
||||||
let content_choose_credential =
|
|
||||||
ChooseCredential::new(label_fn, num_accounts).map(|msg| match msg {
|
let content_choose_credential = Frame::left_aligned(
|
||||||
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
TR::fido__title_select_credential.into(),
|
||||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
SwipeContent::new(SwipePage::vertical(PagedVerticalMenu::new(
|
||||||
});
|
num_accounts,
|
||||||
|
label_fn,
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
.with_subtitle(TR::fido__title_for_authentication.into())
|
||||||
|
.with_menu_button()
|
||||||
|
.with_footer_page_hint(
|
||||||
|
TR::fido__more_credentials.into(),
|
||||||
|
TR::buttons__go_back.into(),
|
||||||
|
TR::instructions__swipe_up.into(),
|
||||||
|
TR::instructions__swipe_down.into(),
|
||||||
|
)
|
||||||
|
.register_footer_update_fn(footer_update_fn)
|
||||||
|
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||||
|
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||||
|
.with_vertical_pages()
|
||||||
|
.map(|msg| match msg {
|
||||||
|
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
||||||
|
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||||
|
});
|
||||||
|
|
||||||
let get_account = move || {
|
let get_account = move || {
|
||||||
let current = CRED_SELECTED.load(Ordering::Relaxed);
|
let current = CRED_SELECTED.load(Ordering::Relaxed);
|
||||||
|
@ -8,20 +8,20 @@ use crate::{
|
|||||||
component::{
|
component::{
|
||||||
swipe_detect::SwipeSettings,
|
swipe_detect::SwipeSettings,
|
||||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||||
ButtonRequestExt, ComponentExt, SwipeDirection,
|
ButtonRequestExt, ComponentExt, EventCtx, SwipeDirection,
|
||||||
},
|
},
|
||||||
flow::{
|
flow::{
|
||||||
base::{DecisionBuilder as _, StateChange},
|
base::{DecisionBuilder as _, StateChange},
|
||||||
FlowMsg, FlowState, SwipeFlow,
|
FlowMsg, FlowState, SwipeFlow,
|
||||||
},
|
},
|
||||||
layout::obj::LayoutObj,
|
layout::obj::LayoutObj,
|
||||||
model_mercury::component::SwipeContent,
|
model_mercury::component::{InternallySwipable, InternallySwipableContent, SwipeContent},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
|
|
||||||
use super::super::{
|
use super::super::{
|
||||||
component::{Frame, FrameMsg, PromptScreen, ShareWords},
|
component::{Footer, Frame, FrameMsg, Header, PromptScreen, ShareWords},
|
||||||
theme,
|
theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,6 +65,24 @@ pub extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs:
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ShowShareWords::new_obj) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ShowShareWords::new_obj) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header_updating_func(
|
||||||
|
content: &InternallySwipableContent<ShareWords>,
|
||||||
|
ctx: &mut EventCtx,
|
||||||
|
header: &mut Header,
|
||||||
|
) {
|
||||||
|
let (subtitle, subtitle_style) = content.inner().subtitle();
|
||||||
|
header.update_subtitle(ctx, subtitle, Some(*subtitle_style));
|
||||||
|
}
|
||||||
|
fn footer_updating_func(
|
||||||
|
content: &InternallySwipableContent<ShareWords>,
|
||||||
|
ctx: &mut EventCtx,
|
||||||
|
footer: &mut Footer,
|
||||||
|
) {
|
||||||
|
let current_page = content.inner().current_page();
|
||||||
|
let total_pages = content.inner().num_pages();
|
||||||
|
footer.update_page_counter(ctx, current_page, Some(total_pages));
|
||||||
|
}
|
||||||
|
|
||||||
impl ShowShareWords {
|
impl ShowShareWords {
|
||||||
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
|
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
|
||||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
@ -77,7 +95,6 @@ impl ShowShareWords {
|
|||||||
.and_then(|desc: TString| if desc.is_empty() { None } else { Some(desc) });
|
.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_info: Obj = kwargs.get(Qstr::MP_QSTR_text_info)?;
|
||||||
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
|
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
|
||||||
let highlight_repeated: bool = kwargs.get(Qstr::MP_QSTR_highlight_repeated)?.try_into()?;
|
|
||||||
let nwords = share_words_vec.len();
|
let nwords = share_words_vec.len();
|
||||||
|
|
||||||
let mut instructions_paragraphs = ParagraphVecShort::new();
|
let mut instructions_paragraphs = ParagraphVecShort::new();
|
||||||
@ -101,8 +118,19 @@ impl ShowShareWords {
|
|||||||
.one_button_request(ButtonRequestCode::ResetDevice.with_name("share_words"))
|
.one_button_request(ButtonRequestCode::ResetDevice.with_name("share_words"))
|
||||||
.with_pages(move |_| nwords + 2);
|
.with_pages(move |_| nwords + 2);
|
||||||
|
|
||||||
let content_words =
|
let n_words = share_words_vec.len();
|
||||||
ShareWords::new(title, subtitle, share_words_vec, highlight_repeated).map(|_| None);
|
let content_words = Frame::left_aligned(
|
||||||
|
title,
|
||||||
|
InternallySwipableContent::new(ShareWords::new(share_words_vec, subtitle)),
|
||||||
|
)
|
||||||
|
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||||
|
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||||
|
.with_vertical_pages()
|
||||||
|
.with_subtitle(subtitle)
|
||||||
|
.register_header_update_fn(header_updating_func)
|
||||||
|
.with_footer_counter(TR::instructions__swipe_up.into(), n_words as u8)
|
||||||
|
.register_footer_update_fn(footer_updating_func)
|
||||||
|
.map(|_| None);
|
||||||
|
|
||||||
let content_confirm = Frame::left_aligned(
|
let content_confirm = Frame::left_aligned(
|
||||||
text_confirm,
|
text_confirm,
|
||||||
|
@ -1524,7 +1524,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// description: str,
|
/// description: str,
|
||||||
/// text_info: Iterable[str],
|
/// text_info: Iterable[str],
|
||||||
/// text_confirm: str,
|
/// text_confirm: str,
|
||||||
/// highlight_repeated: bool,
|
|
||||||
/// ) -> LayoutObj[UiResult]:
|
/// ) -> LayoutObj[UiResult]:
|
||||||
/// """Show wallet backup words preceded by an instruction screen and followed by
|
/// """Show wallet backup words preceded by an instruction screen and followed by
|
||||||
/// confirmation."""
|
/// confirmation."""
|
||||||
|
@ -403,7 +403,6 @@ def flow_show_share_words(
|
|||||||
description: str,
|
description: str,
|
||||||
text_info: Iterable[str],
|
text_info: Iterable[str],
|
||||||
text_confirm: str,
|
text_confirm: str,
|
||||||
highlight_repeated: bool,
|
|
||||||
) -> LayoutObj[UiResult]:
|
) -> LayoutObj[UiResult]:
|
||||||
"""Show wallet backup words preceded by an instruction screen and followed by
|
"""Show wallet backup words preceded by an instruction screen and followed by
|
||||||
confirmation."""
|
confirmation."""
|
||||||
|
@ -23,7 +23,6 @@ async def show_share_words(
|
|||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
title = TR.reset__recovery_wallet_backup_title
|
title = TR.reset__recovery_wallet_backup_title
|
||||||
highlight_repeated = True
|
|
||||||
if share_index is None:
|
if share_index is None:
|
||||||
subtitle = ""
|
subtitle = ""
|
||||||
elif group_index is None:
|
elif group_index is None:
|
||||||
@ -52,7 +51,6 @@ async def show_share_words(
|
|||||||
description=description,
|
description=description,
|
||||||
text_info=text_info,
|
text_info=text_info,
|
||||||
text_confirm=text_confirm,
|
text_confirm=text_confirm,
|
||||||
highlight_repeated=highlight_repeated,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user