mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 22:38:08 +00:00
refactor(core/ui): alternative multipage footer
This commit is contained in:
parent
2572705d1f
commit
7db1529533
@ -35,6 +35,10 @@ impl<T: Component + Paginate> SwipePage<T> {
|
||||
current: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component + Paginate> Component for SwipePage<T> {
|
||||
|
@ -0,0 +1,101 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -22,8 +22,7 @@ use crate::{
|
||||
#[derive(Clone)]
|
||||
pub struct Footer<'a> {
|
||||
area: Rect,
|
||||
instruction: TString<'a>,
|
||||
content: Option<FooterContent<'a>>,
|
||||
content: FooterContent<'a>,
|
||||
swipe_allow_up: bool,
|
||||
swipe_allow_down: bool,
|
||||
progress: i16,
|
||||
@ -32,8 +31,10 @@ pub struct Footer<'a> {
|
||||
|
||||
#[derive(Clone)]
|
||||
enum FooterContent<'a> {
|
||||
Description(TString<'a>),
|
||||
Instruction(TString<'a>),
|
||||
InstructionDescription(TString<'a>, TString<'a>),
|
||||
PageCounter(PageCounter),
|
||||
PageHint(PageHint),
|
||||
}
|
||||
|
||||
impl<'a> Footer<'a> {
|
||||
@ -45,14 +46,10 @@ impl<'a> Footer<'a> {
|
||||
const STYLE_INSTRUCTION: &'static TextStyle = &theme::TEXT_SUB_GREY;
|
||||
const STYLE_DESCRIPTION: &'static TextStyle = &theme::TEXT_SUB_GREY_LIGHT;
|
||||
|
||||
pub fn new<T: Into<TString<'a>>>(
|
||||
instruction: T,
|
||||
description: Option<TString<'static>>,
|
||||
) -> Self {
|
||||
fn from_content(content: FooterContent<'a>) -> Self {
|
||||
Self {
|
||||
area: Rect::zero(),
|
||||
instruction: instruction.into(),
|
||||
content: description.map(FooterContent::Description),
|
||||
content,
|
||||
swipe_allow_down: false,
|
||||
swipe_allow_up: false,
|
||||
progress: 0,
|
||||
@ -60,38 +57,79 @@ impl<'a> Footer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_page_counter(self, max_pages: u8) -> Self {
|
||||
Self {
|
||||
content: Some(FooterContent::PageCounter(PageCounter::new(max_pages))),
|
||||
..self
|
||||
}
|
||||
pub fn new<T: Into<TString<'a>>>(
|
||||
instruction: T,
|
||||
description: Option<TString<'static>>,
|
||||
) -> Self {
|
||||
let instruction = instruction.into();
|
||||
Self::from_content(
|
||||
description
|
||||
.map(|d| FooterContent::InstructionDescription(instruction, d))
|
||||
.unwrap_or(FooterContent::Instruction(instruction)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_instruction<T: Into<TString<'a>>>(&mut self, ctx: &mut EventCtx, s: T) {
|
||||
self.instruction = s.into();
|
||||
pub fn with_page_counter(max_pages: u8, instruction: TString<'static>) -> Self {
|
||||
Self::from_content(FooterContent::PageCounter(PageCounter::new(
|
||||
max_pages,
|
||||
instruction,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn with_page_hint(
|
||||
description: TString<'static>,
|
||||
description_last: TString<'static>,
|
||||
instruction: TString<'static>,
|
||||
instruction_last: TString<'static>,
|
||||
) -> Self {
|
||||
Self::from_content(FooterContent::PageHint(PageHint {
|
||||
description,
|
||||
description_last,
|
||||
instruction,
|
||||
instruction_last,
|
||||
page_curr: 0,
|
||||
page_num: 1,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn update_instruction<T: Into<TString<'static>>>(&mut self, ctx: &mut EventCtx, s: T) {
|
||||
match &mut self.content {
|
||||
FooterContent::Instruction(i) => *i = s.into(),
|
||||
FooterContent::InstructionDescription(i, _d) => *i = s.into(),
|
||||
FooterContent::PageCounter(page_counter) => page_counter.instruction = s.into(),
|
||||
_ => {
|
||||
#[cfg(feature = "ui_debug")]
|
||||
panic!("not supported")
|
||||
}
|
||||
}
|
||||
ctx.request_paint();
|
||||
}
|
||||
|
||||
pub fn update_description<T: Into<TString<'a>>>(&mut self, ctx: &mut EventCtx, s: T) {
|
||||
if let Some(ref mut content) = self.content {
|
||||
if let Some(text) = content.as_description_mut() {
|
||||
*text = s.into();
|
||||
ctx.request_paint();
|
||||
} else {
|
||||
#[cfg(feature = "ui_debug")]
|
||||
panic!("footer does not have description")
|
||||
}
|
||||
if let FooterContent::InstructionDescription(_i, d) = &mut self.content {
|
||||
*d = s.into();
|
||||
ctx.request_paint();
|
||||
} else {
|
||||
#[cfg(feature = "ui_debug")]
|
||||
panic!("footer does not have description")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_page_counter(&mut self, ctx: &mut EventCtx, n: u8) {
|
||||
if let Some(ref mut content) = self.content {
|
||||
if let Some(counter) = content.as_page_counter_mut() {
|
||||
counter.update_current_page(n);
|
||||
pub fn update_page_counter(&mut self, ctx: &mut EventCtx, current: usize, max: Option<usize>) {
|
||||
match &mut self.content {
|
||||
FooterContent::PageCounter(counter) => {
|
||||
counter.update_current_page(current);
|
||||
self.swipe_allow_down = counter.is_first_page();
|
||||
self.swipe_allow_up = counter.is_last_page();
|
||||
ctx.request_paint();
|
||||
} else {
|
||||
}
|
||||
FooterContent::PageHint(page_hint) => {
|
||||
page_hint.update_current_page(current, max);
|
||||
self.swipe_allow_down = page_hint.is_first_page();
|
||||
self.swipe_allow_up = page_hint.is_last_page();
|
||||
ctx.request_paint();
|
||||
}
|
||||
_ => {
|
||||
#[cfg(feature = "ui_debug")]
|
||||
panic!("footer does not have counter")
|
||||
}
|
||||
@ -99,11 +137,7 @@ impl<'a> Footer<'a> {
|
||||
}
|
||||
|
||||
pub fn height(&self) -> i16 {
|
||||
if self.content.is_some() {
|
||||
Footer::HEIGHT_DEFAULT
|
||||
} else {
|
||||
Footer::HEIGHT_SIMPLE
|
||||
}
|
||||
self.content.height()
|
||||
}
|
||||
|
||||
pub fn with_swipe(self, swipe_direction: SwipeDirection) -> Self {
|
||||
@ -125,15 +159,7 @@ impl<'a> Component for Footer<'a> {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
let h = bounds.height();
|
||||
assert!(h == Footer::HEIGHT_SIMPLE || h == Footer::HEIGHT_DEFAULT);
|
||||
if let Some(content) = &mut self.content {
|
||||
if let Some(counter) = content.as_page_counter_mut() {
|
||||
if h == Footer::HEIGHT_DEFAULT {
|
||||
counter.place(bounds.split_top(Footer::HEIGHT_SIMPLE).0);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(bounds.height() == self.content.height());
|
||||
self.area = bounds;
|
||||
self.area
|
||||
}
|
||||
@ -189,49 +215,7 @@ impl<'a> Component for Footer<'a> {
|
||||
};
|
||||
|
||||
target.with_origin(offset, &|target| {
|
||||
// show description/counter only if there is space for it
|
||||
if self.area.height() == Footer::HEIGHT_DEFAULT {
|
||||
if let Some(content) = &self.content {
|
||||
match content {
|
||||
FooterContent::Description(text) => {
|
||||
let area_description = self.area.split_top(Footer::HEIGHT_SIMPLE).0;
|
||||
let text_description_font_descent = Footer::STYLE_DESCRIPTION
|
||||
.text_font
|
||||
.visible_text_height_ex("Ay")
|
||||
.1;
|
||||
let text_description_baseline = area_description.bottom_center()
|
||||
- Offset::y(text_description_font_descent);
|
||||
|
||||
text.map(|t| {
|
||||
Text::new(text_description_baseline, t)
|
||||
.with_font(Footer::STYLE_DESCRIPTION.text_font)
|
||||
.with_fg(Footer::STYLE_DESCRIPTION.text_color)
|
||||
.with_align(Alignment::Center)
|
||||
.render(target);
|
||||
});
|
||||
}
|
||||
FooterContent::PageCounter(counter) => {
|
||||
counter.render(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let area_instruction = self.area.split_bottom(Footer::HEIGHT_SIMPLE).1;
|
||||
let text_instruction_font_descent = Footer::STYLE_INSTRUCTION
|
||||
.text_font
|
||||
.visible_text_height_ex("Ay")
|
||||
.1;
|
||||
let text_instruction_baseline =
|
||||
area_instruction.bottom_center() - Offset::y(text_instruction_font_descent);
|
||||
self.instruction.map(|t| {
|
||||
Text::new(text_instruction_baseline, t)
|
||||
.with_font(Footer::STYLE_INSTRUCTION.text_font)
|
||||
.with_fg(Footer::STYLE_INSTRUCTION.text_color)
|
||||
.with_align(Alignment::Center)
|
||||
.render(target);
|
||||
});
|
||||
|
||||
self.content.render(self.area, target);
|
||||
shape::Bar::new(self.area)
|
||||
.with_alpha(mask)
|
||||
.with_fg(Color::black())
|
||||
@ -245,89 +229,138 @@ impl<'a> Component for Footer<'a> {
|
||||
impl crate::trace::Trace for Footer<'_> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Footer");
|
||||
if let Some(content) = &self.content {
|
||||
match content {
|
||||
FooterContent::Description(text) => t.string("description", *text),
|
||||
FooterContent::PageCounter(counter) => counter.trace(t),
|
||||
match &self.content {
|
||||
FooterContent::Instruction(i) => {
|
||||
t.string("instruction", *i);
|
||||
}
|
||||
FooterContent::InstructionDescription(i, d) => {
|
||||
t.string("description", *d);
|
||||
t.string("instruction", *i);
|
||||
}
|
||||
FooterContent::PageCounter(counter) => counter.trace(t),
|
||||
FooterContent::PageHint(page_hint) => {
|
||||
t.string("description", page_hint.description());
|
||||
t.string("instruction", page_hint.instruction());
|
||||
}
|
||||
}
|
||||
t.string("instruction", self.instruction);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FooterContent<'a> {
|
||||
pub fn as_description_mut(&mut self) -> Option<&mut TString<'a>> {
|
||||
match self {
|
||||
FooterContent::Description(ref mut text) => Some(text),
|
||||
_ => None,
|
||||
fn height(&self) -> i16 {
|
||||
if matches!(self, FooterContent::Instruction(_)) {
|
||||
Footer::HEIGHT_SIMPLE
|
||||
} else {
|
||||
Footer::HEIGHT_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_page_counter_mut(&mut self) -> Option<&mut PageCounter> {
|
||||
fn render<'s>(&'s self, area: Rect, target: &mut impl Renderer<'s>)
|
||||
where
|
||||
's: 'a,
|
||||
{
|
||||
match self {
|
||||
FooterContent::PageCounter(ref mut counter) => Some(counter),
|
||||
_ => None,
|
||||
FooterContent::Instruction(instruction) => {
|
||||
Self::render_instruction(target, area, instruction);
|
||||
}
|
||||
FooterContent::InstructionDescription(instruction, description) => {
|
||||
Self::render_description(target, area, description);
|
||||
Self::render_instruction(target, area, instruction);
|
||||
}
|
||||
FooterContent::PageCounter(page_counter) => page_counter.render(target, area),
|
||||
FooterContent::PageHint(page_hint) => {
|
||||
Self::render_description(target, area, &page_hint.description());
|
||||
Self::render_instruction(target, area, &page_hint.instruction());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_description<'s>(
|
||||
target: &mut impl Renderer<'s>,
|
||||
area: Rect,
|
||||
description: &TString<'a>,
|
||||
) {
|
||||
let area_description = area.split_top(Footer::HEIGHT_SIMPLE).0;
|
||||
let text_description_font_descent = Footer::STYLE_DESCRIPTION
|
||||
.text_font
|
||||
.visible_text_height_ex("Ay")
|
||||
.1;
|
||||
let text_description_baseline =
|
||||
area_description.bottom_center() - Offset::y(text_description_font_descent);
|
||||
|
||||
description.map(|t| {
|
||||
Text::new(text_description_baseline, t)
|
||||
.with_font(Footer::STYLE_DESCRIPTION.text_font)
|
||||
.with_fg(Footer::STYLE_DESCRIPTION.text_color)
|
||||
.with_align(Alignment::Center)
|
||||
.render(target)
|
||||
});
|
||||
}
|
||||
|
||||
fn render_instruction<'s>(
|
||||
target: &mut impl Renderer<'s>,
|
||||
area: Rect,
|
||||
instruction: &TString<'a>,
|
||||
) {
|
||||
let area_instruction = area.split_bottom(Footer::HEIGHT_SIMPLE).1;
|
||||
let text_instruction_font_descent = Footer::STYLE_INSTRUCTION
|
||||
.text_font
|
||||
.visible_text_height_ex("Ay")
|
||||
.1;
|
||||
let text_instruction_baseline =
|
||||
area_instruction.bottom_center() - Offset::y(text_instruction_font_descent);
|
||||
instruction.map(|t| {
|
||||
Text::new(text_instruction_baseline, t)
|
||||
.with_font(Footer::STYLE_INSTRUCTION.text_font)
|
||||
.with_fg(Footer::STYLE_INSTRUCTION.text_color)
|
||||
.with_align(Alignment::Center)
|
||||
.render(target)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper component used within Footer instead of description for page count
|
||||
/// indication, rendered e.g. as: '1 / 20'.
|
||||
#[derive(Clone)]
|
||||
struct PageCounter {
|
||||
area: Rect,
|
||||
pub instruction: TString<'static>,
|
||||
font: Font,
|
||||
page_curr: u8,
|
||||
page_max: u8,
|
||||
}
|
||||
|
||||
impl PageCounter {
|
||||
fn new(page_max: u8) -> Self {
|
||||
fn new(page_max: u8, instruction: TString<'static>) -> Self {
|
||||
Self {
|
||||
area: Rect::zero(),
|
||||
page_curr: 1,
|
||||
instruction,
|
||||
page_curr: 0,
|
||||
page_max,
|
||||
font: Font::SUB,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_current_page(&mut self, new_value: u8) {
|
||||
self.page_curr = new_value.clamp(1, self.page_max);
|
||||
fn update_current_page(&mut self, new_value: usize) {
|
||||
self.page_curr = (new_value as u8).clamp(0, self.page_max.saturating_sub(1));
|
||||
}
|
||||
|
||||
fn is_first_page(&self) -> bool {
|
||||
self.page_curr == 1
|
||||
self.page_curr == 0
|
||||
}
|
||||
|
||||
fn is_last_page(&self) -> bool {
|
||||
self.page_curr == self.page_max
|
||||
self.page_curr + 1 == self.page_max
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for PageCounter {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.area = bounds;
|
||||
self.area
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
todo!("remove when ui-t3t1 done")
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let color = if self.page_curr == self.page_max {
|
||||
impl PageCounter {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) {
|
||||
let color = if self.is_last_page() {
|
||||
theme::GREEN_LIGHT
|
||||
} else {
|
||||
theme::GREY_LIGHT
|
||||
};
|
||||
|
||||
let string_curr = uformat!("{}", self.page_curr);
|
||||
let string_curr = uformat!("{}", self.page_curr + 1);
|
||||
let string_max = uformat!("{}", self.page_max);
|
||||
|
||||
// center the whole counter "x / yz"
|
||||
@ -337,8 +370,9 @@ impl Component for PageCounter {
|
||||
let width_num_max = self.font.text_width(&string_max);
|
||||
let width_total = width_num_curr + width_foreslash + width_num_max + 2 * offset_x.x;
|
||||
|
||||
let center_x = self.area.center().x;
|
||||
let counter_y = self.font.vert_center(self.area.y0, self.area.y1, "0");
|
||||
let counter_area = area.split_top(Footer::HEIGHT_SIMPLE).0;
|
||||
let center_x = counter_area.center().x;
|
||||
let counter_y = self.font.vert_center(counter_area.y0, counter_area.y1, "0");
|
||||
let counter_start_x = center_x - width_total / 2;
|
||||
let counter_end_x = center_x + width_total / 2;
|
||||
let base_num_curr = Point::new(counter_start_x, counter_y);
|
||||
@ -359,6 +393,8 @@ impl Component for PageCounter {
|
||||
.with_fg(color)
|
||||
.with_font(self.font)
|
||||
.render(target);
|
||||
|
||||
FooterContent::render_instruction(target, area, &self.instruction);
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,3 +406,58 @@ impl crate::trace::Trace for PageCounter {
|
||||
t.int("page max", self.page_max.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PageHint {
|
||||
pub description: TString<'static>,
|
||||
pub description_last: TString<'static>,
|
||||
pub instruction: TString<'static>,
|
||||
pub instruction_last: TString<'static>,
|
||||
pub page_curr: u8,
|
||||
pub page_num: u8,
|
||||
}
|
||||
|
||||
impl PageHint {
|
||||
fn update_current_page(&mut self, current: usize, max: Option<usize>) {
|
||||
self.page_curr = (current as u8).clamp(0, self.page_num.saturating_sub(1));
|
||||
if let Some(max) = max {
|
||||
self.page_num = max as u8;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_max_page(&mut self, max: usize) {
|
||||
self.page_num = max as u8;
|
||||
}
|
||||
|
||||
fn is_single_page(&self) -> bool {
|
||||
self.page_num <= 1
|
||||
}
|
||||
|
||||
fn is_first_page(&self) -> bool {
|
||||
self.page_curr == 0
|
||||
}
|
||||
|
||||
fn is_last_page(&self) -> bool {
|
||||
self.page_curr + 1 == self.page_num
|
||||
}
|
||||
|
||||
fn description(&self) -> TString<'static> {
|
||||
if self.is_single_page() {
|
||||
TString::empty()
|
||||
} else if self.is_last_page() {
|
||||
self.description_last
|
||||
} else {
|
||||
self.description
|
||||
}
|
||||
}
|
||||
|
||||
fn instruction(&self) -> TString<'static> {
|
||||
if self.is_single_page() {
|
||||
TString::empty()
|
||||
} else if self.is_last_page() {
|
||||
self.instruction_last
|
||||
} else {
|
||||
self.instruction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,7 +196,24 @@ where
|
||||
|
||||
#[inline(never)]
|
||||
pub fn with_footer_counter(mut self, instruction: TString<'static>, max_value: u8) -> Self {
|
||||
self.footer = Some(Footer::new(instruction, None).with_page_counter(max_value));
|
||||
self.footer = Some(Footer::with_page_counter(max_value, instruction));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn with_footer_page_hint(
|
||||
mut self,
|
||||
description: TString<'static>,
|
||||
description_last: TString<'static>,
|
||||
instruction: TString<'static>,
|
||||
instruction_last: TString<'static>,
|
||||
) -> Self {
|
||||
self.footer = Some(Footer::with_page_hint(
|
||||
description,
|
||||
description_last,
|
||||
instruction,
|
||||
instruction_last,
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
@ -231,9 +248,14 @@ where
|
||||
res
|
||||
}
|
||||
|
||||
pub fn update_footer_counter(&mut self, ctx: &mut EventCtx, new_val: u8) {
|
||||
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, new_val);
|
||||
footer.update_page_counter(ctx, current, max);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ mod address_details;
|
||||
mod binary_selection;
|
||||
pub mod bl_confirm;
|
||||
mod button;
|
||||
#[cfg(feature = "universal_fw")]
|
||||
mod choose_credential;
|
||||
#[cfg(feature = "translations")]
|
||||
mod coinjoin_progress;
|
||||
mod fido;
|
||||
@ -46,6 +48,8 @@ pub use address_details::AddressDetails;
|
||||
#[cfg(feature = "ui_overlay")]
|
||||
pub use binary_selection::{BinarySelection, BinarySelectionMsg};
|
||||
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, IconText};
|
||||
#[cfg(feature = "universal_fw")]
|
||||
pub use choose_credential::ChooseCredential;
|
||||
#[cfg(feature = "translations")]
|
||||
pub use coinjoin_progress::CoinJoinProgress;
|
||||
pub use error::ErrorScreen;
|
||||
|
@ -84,9 +84,9 @@ impl<'a> Component for ShareWords<'a> {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let page_index = self.frame.inner().inner().page_index as u8;
|
||||
let page_index = self.frame.inner().inner().page_index;
|
||||
if let Some(repeated_indices) = &self.repeated_indices {
|
||||
if repeated_indices.contains(&page_index) {
|
||||
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));
|
||||
@ -95,7 +95,8 @@ impl<'a> Component for ShareWords<'a> {
|
||||
.update_subtitle(ctx, self.subtitle, Some(theme::TEXT_SUB_GREY));
|
||||
}
|
||||
}
|
||||
self.frame.update_footer_counter(ctx, page_index + 1);
|
||||
self.frame
|
||||
.update_footer_counter(ctx, page_index as usize, None);
|
||||
self.frame.event(ctx, event)
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,7 @@ use crate::{
|
||||
event::SwipeEvent,
|
||||
geometry::{Offset, Rect},
|
||||
lerp::Lerp,
|
||||
shape,
|
||||
shape::Renderer,
|
||||
shape::{self, Renderer},
|
||||
util::animation_disabled,
|
||||
},
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
use heapless::Vec;
|
||||
|
||||
use super::theme;
|
||||
use super::{theme, InternallySwipable};
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
time::{Duration, Stopwatch},
|
||||
@ -266,7 +266,7 @@ impl Component for VerticalMenu {
|
||||
|
||||
let opacities = [item1_opacity, item2_opacity, item3_opacity];
|
||||
|
||||
target.with_origin(offset, &|target| {
|
||||
target.with_translate(offset, &|target| {
|
||||
// render buttons separated by thin bars
|
||||
for (i, button) in (&self.buttons).into_iter().take(self.n_items).enumerate() {
|
||||
button.render(target);
|
||||
|
@ -11,16 +11,16 @@ use crate::{
|
||||
},
|
||||
flow::{
|
||||
base::{DecisionBuilder as _, StateChange},
|
||||
FlowMsg, FlowState, SwipeFlow, SwipePage,
|
||||
FlowMsg, FlowState, SwipeFlow,
|
||||
},
|
||||
layout::obj::LayoutObj,
|
||||
model_mercury::component::{FidoCredential, SwipeContent},
|
||||
},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::{
|
||||
Frame, FrameMsg, PagedVerticalMenu, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg,
|
||||
ChooseCredential, FidoCredential, Frame, FrameMsg, PromptMsg, PromptScreen, SwipeContent,
|
||||
VerticalMenu, VerticalMenuChoiceMsg,
|
||||
},
|
||||
theme,
|
||||
};
|
||||
@ -49,6 +49,7 @@ impl FlowState for ConfirmFido {
|
||||
match (self, direction) {
|
||||
(Self::Intro, SwipeDirection::Left) => Self::Menu.swipe(direction),
|
||||
(Self::Intro, SwipeDirection::Up) => Self::ChooseCredential.swipe(direction),
|
||||
(Self::ChooseCredential, SwipeDirection::Down) => Self::Intro.swipe(direction),
|
||||
(Self::Details, SwipeDirection::Up) => Self::Tap.swipe(direction),
|
||||
(Self::Tap, SwipeDirection::Down) => Self::Details.swipe(direction),
|
||||
_ => self.do_nothing(),
|
||||
@ -122,26 +123,11 @@ impl ConfirmFido {
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| TString::from_str("-"))
|
||||
};
|
||||
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(
|
||||
TR::instructions__swipe_up.into(),
|
||||
(num_accounts > 2).then_some(TR::fido__more_credentials.into()),
|
||||
)
|
||||
.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 content_choose_credential =
|
||||
ChooseCredential::new(label_fn, num_accounts).map(|msg| match msg {
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
});
|
||||
|
||||
let get_account = move || {
|
||||
let current = CRED_SELECTED.load(Ordering::Relaxed);
|
||||
|
@ -51,6 +51,13 @@ pub trait Renderer<'a> {
|
||||
inner(self);
|
||||
self.set_viewport(original);
|
||||
}
|
||||
|
||||
fn with_translate(&mut self, offset: Offset, inner: &dyn Fn(&mut Self)) {
|
||||
let original = self.viewport();
|
||||
self.set_viewport(self.viewport().translate(offset));
|
||||
inner(self);
|
||||
self.set_viewport(original);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
|
@ -18,6 +18,7 @@ async def list_resident_credentials(
|
||||
TR.fido__title_list_credentials,
|
||||
description=TR.fido__export_credentials,
|
||||
verb=TR.buttons__export,
|
||||
prompt_screen=True,
|
||||
)
|
||||
creds = [
|
||||
WebAuthnCredential(
|
||||
|
Loading…
Reference in New Issue
Block a user