1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-20 06:28:09 +00:00

feat(core/rust): show WRONG PIN header in PIN entry after bad previous PIN input

[no changelog]
This commit is contained in:
grdddj 2023-09-04 10:38:03 +02:00 committed by Jiří Musil
parent 49ce5eb05f
commit 84659dc904
3 changed files with 81 additions and 11 deletions

View File

@ -19,6 +19,8 @@ pub struct ChangingTextLine<T> {
/// What to show in front of the text if it doesn't fit. /// What to show in front of the text if it doesn't fit.
ellipsis: &'static str, ellipsis: &'static str,
alignment: Alignment, alignment: Alignment,
/// Whether to show the text completely aligned to the top of the bounds
text_at_the_top: bool,
} }
impl<T> ChangingTextLine<T> impl<T> ChangingTextLine<T>
@ -33,6 +35,7 @@ where
show_content: true, show_content: true,
ellipsis: "...", ellipsis: "...",
alignment, alignment,
text_at_the_top: false,
} }
} }
@ -50,6 +53,12 @@ where
self self
} }
/// Showing text at the very top
pub fn with_text_at_the_top(mut self) -> Self {
self.text_at_the_top = true;
self
}
/// Update the text to be displayed in the line. /// Update the text to be displayed in the line.
pub fn update_text(&mut self, text: T) { pub fn update_text(&mut self, text: T) {
self.text = text; self.text = text;
@ -60,6 +69,11 @@ where
&self.text &self.text
} }
/// Changing the current font
pub fn update_font(&mut self, font: Font) {
self.font = font;
}
/// Whether we should display the text content. /// Whether we should display the text content.
/// If not, the whole area (Pad) will still be cleared. /// If not, the whole area (Pad) will still be cleared.
/// Is valid until this function is called again. /// Is valid until this function is called again.
@ -76,7 +90,13 @@ where
/// Y coordinate of text baseline, is the same for all paints. /// Y coordinate of text baseline, is the same for all paints.
fn y_baseline(&self) -> i16 { fn y_baseline(&self) -> i16 {
self.pad.area.y0 + self.font.line_height() let y_coord = self.pad.area.y0 + self.font.line_height();
if self.text_at_the_top {
// Shifting the text up by 2 pixels.
y_coord - 2
} else {
y_coord
}
} }
/// Whether the whole text can be painted in the available space /// Whether the whole text can be painted in the available space

View File

@ -3,7 +3,7 @@ use crate::{
trezorhal::random, trezorhal::random,
ui::{ ui::{
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx}, component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
display::Icon, display::{Font, Icon},
geometry::Rect, geometry::Rect,
}, },
}; };
@ -80,8 +80,12 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
/// Component for entering a PIN. /// Component for entering a PIN.
pub struct PinEntry<T: StringType + Clone> { pub struct PinEntry<T: StringType + Clone> {
choice_page: ChoicePage<ChoiceFactoryPIN, T, PinAction>, choice_page: ChoicePage<ChoiceFactoryPIN, T, PinAction>,
header_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>, pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
prompt: T,
subprompt: T, subprompt: T,
/// Whether we already show the "real" prompt (not the warning).
showing_real_prompt: bool,
show_real_pin: bool, show_real_pin: bool,
show_last_digit: bool, show_last_digit: bool,
textbox: TextBox<MAX_PIN_LENGTH>, textbox: TextBox<MAX_PIN_LENGTH>,
@ -91,22 +95,45 @@ impl<T> PinEntry<T>
where where
T: StringType + Clone, T: StringType + Clone,
{ {
pub fn new(subprompt: T) -> Self { pub fn new(prompt: T, subprompt: T) -> Self {
let pin_line_content = if !subprompt.as_ref().is_empty() { // When subprompt is not empty, it means that the user has entered bad PIN
String::from(subprompt.as_ref()) // before. In this case we show the warning together with the subprompt
// at the beginning. (WRONG PIN will be replaced by real prompt after
// any button click.)
let show_subprompt = !subprompt.as_ref().is_empty();
let (showing_real_prompt, header_line_content, pin_line_content) = if show_subprompt {
(
false,
String::from("WRONG PIN"),
String::from(subprompt.as_ref()),
)
} else { } else {
String::from(EMPTY_PIN_STR) (
true,
String::from(prompt.as_ref()),
String::from(EMPTY_PIN_STR),
)
}; };
let mut pin_line = ChangingTextLine::center_bold(pin_line_content).without_ellipsis();
if show_subprompt {
pin_line.update_font(Font::NORMAL);
}
Self { Self {
// Starting at a random digit. // Starting at a random digit.
choice_page: ChoicePage::new(ChoiceFactoryPIN) choice_page: ChoicePage::new(ChoiceFactoryPIN)
.with_initial_page_counter(get_random_digit_position()) .with_initial_page_counter(get_random_digit_position())
.with_carousel(true), .with_carousel(true),
pin_line: Child::new( header_line: Child::new(
ChangingTextLine::center_bold(pin_line_content).without_ellipsis(), ChangingTextLine::center_bold(header_line_content)
.without_ellipsis()
.with_text_at_the_top(),
), ),
pin_line: Child::new(pin_line),
subprompt, subprompt,
prompt,
showing_real_prompt,
show_real_pin: false, show_real_pin: false,
show_last_digit: false, show_last_digit: false,
textbox: TextBox::empty(), textbox: TextBox::empty(),
@ -122,7 +149,10 @@ where
/// Show updated content in the changing line. /// Show updated content in the changing line.
/// Many possibilities, according to the PIN state. /// Many possibilities, according to the PIN state.
fn update_pin_line(&mut self, ctx: &mut EventCtx) { fn update_pin_line(&mut self, ctx: &mut EventCtx) {
let mut used_font = Font::BOLD;
let pin_line_text = if self.is_empty() && !self.subprompt.as_ref().is_empty() { let pin_line_text = if self.is_empty() && !self.subprompt.as_ref().is_empty() {
// Showing the subprompt in NORMAL font
used_font = Font::NORMAL;
String::from(self.subprompt.as_ref()) String::from(self.subprompt.as_ref())
} else if self.is_empty() { } else if self.is_empty() {
String::from(EMPTY_PIN_STR) String::from(EMPTY_PIN_STR)
@ -144,11 +174,20 @@ where
}; };
self.pin_line.mutate(ctx, |ctx, pin_line| { self.pin_line.mutate(ctx, |ctx, pin_line| {
pin_line.update_font(used_font);
pin_line.update_text(pin_line_text); pin_line.update_text(pin_line_text);
pin_line.request_complete_repaint(ctx); pin_line.request_complete_repaint(ctx);
}); });
} }
/// Showing the real prompt instead of WRONG PIN
fn show_prompt(&mut self, ctx: &mut EventCtx) {
self.header_line.mutate(ctx, |ctx, header_line| {
header_line.update_text(String::from(self.prompt.as_ref()));
header_line.request_complete_repaint(ctx);
});
}
pub fn pin(&self) -> &str { pub fn pin(&self) -> &str {
self.textbox.content() self.textbox.content()
} }
@ -169,8 +208,11 @@ where
type Msg = CancelConfirmMsg; type Msg = CancelConfirmMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let header_height = self.header_line.inner().needed_height();
let (header_area, rest) = bounds.split_top(header_height);
let pin_height = self.pin_line.inner().needed_height(); let pin_height = self.pin_line.inner().needed_height();
let (pin_area, choice_area) = bounds.split_top(pin_height); let (pin_area, choice_area) = rest.split_top(pin_height);
self.header_line.place(header_area);
self.pin_line.place(pin_area); self.pin_line.place(pin_area);
self.choice_page.place(choice_area); self.choice_page.place(choice_area);
bounds bounds
@ -188,6 +230,14 @@ where
self.update(ctx) self.update(ctx)
} }
// Any button event will show the "real" prompt
if !self.showing_real_prompt {
if let Event::Button(_) = event {
self.show_prompt(ctx);
self.showing_real_prompt = true;
}
}
match self.choice_page.event(ctx, event) { match self.choice_page.event(ctx, event) {
Some(PinAction::Delete) => { Some(PinAction::Delete) => {
self.textbox.delete_last(ctx); self.textbox.delete_last(ctx);
@ -214,6 +264,7 @@ where
} }
fn paint(&mut self) { fn paint(&mut self) {
self.header_line.paint();
self.pin_line.paint(); self.pin_line.paint();
self.choice_page.paint(); self.choice_page.paint();
} }

View File

@ -1207,8 +1207,7 @@ extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map)
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?; let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
let obj = let obj = LayoutObj::new(PinEntry::new(prompt, subprompt))?;
LayoutObj::new(Frame::new(prompt, PinEntry::new(subprompt)).with_title_centered())?;
Ok(obj.into()) Ok(obj.into())
}; };