1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-22 22:38:08 +00:00

feat(core/mercury): passphrase confirm empty btn

[no changelog]
This commit is contained in:
obrusvit 2024-08-06 12:25:58 +02:00 committed by Vít Obrusník
parent 15526343c9
commit 2b4d6eb798
5 changed files with 161 additions and 43 deletions

View File

@ -79,7 +79,8 @@ pub struct PassphraseKeyboard {
input_prompt: Label<'static>,
erase_btn: Maybe<Button>,
cancel_btn: Maybe<Button>,
confirm_btn: Button,
confirm_btn: Maybe<Button>,
confirm_empty_btn: Maybe<Button>,
next_btn: Button,
keypad_area: Rect,
keys: [Button; KEY_COUNT],
@ -101,13 +102,26 @@ const KEYBOARD: [[&str; KEY_COUNT]; PAGE_COUNT] = [
const MAX_LENGTH: usize = 50;
const CONFIRM_BTN_INSETS: Insets = Insets::new(5, 0, 5, 0);
const CONFIRM_EMPTY_BTN_MARGIN_RIGHT: i16 = 7;
const CONFIRM_EMPTY_BTN_INSETS: Insets = Insets::new(5, CONFIRM_EMPTY_BTN_MARGIN_RIGHT, 5, 0);
impl PassphraseKeyboard {
pub fn new() -> Self {
let active_layout = KeyboardLayout::LettersLower;
let confirm_btn = Button::with_icon(theme::ICON_SIMPLE_CHECKMARK24)
.styled(theme::button_passphrase_confirm())
.with_radius(15);
.with_radius(14)
.with_expanded_touch_area(CONFIRM_BTN_INSETS)
.initially_enabled(false);
let confirm_btn = Maybe::hidden(theme::BG, confirm_btn);
let confirm_empty_btn = Button::with_icon(theme::ICON_SIMPLE_CHECKMARK24)
.styled(theme::button_passphrase_confirm_empty())
.with_radius(14)
.with_expanded_touch_area(CONFIRM_EMPTY_BTN_INSETS);
let confirm_empty_btn = Maybe::visible(theme::BG, confirm_empty_btn);
let next_btn = Button::new(active_layout.next().into())
.styled(theme::button_passphrase_next())
@ -133,6 +147,7 @@ impl PassphraseKeyboard {
erase_btn,
cancel_btn,
confirm_btn,
confirm_empty_btn,
next_btn,
keypad_area: Rect::zero(),
keys: KEYBOARD[active_layout.to_usize().unwrap()].map(|text| {
@ -199,6 +214,10 @@ impl PassphraseKeyboard {
// When the input is empty, enable cancel button. Otherwise show erase and
// confirm button.
let is_empty = self.input.textbox.is_empty();
self.confirm_btn.show_if(ctx, !is_empty);
self.confirm_btn.inner_mut().enable_if(ctx, !is_empty);
self.confirm_empty_btn.show_if(ctx, is_empty);
self.confirm_empty_btn.inner_mut().enable_if(ctx, is_empty);
self.erase_btn.show_if(ctx, !is_empty);
self.erase_btn.inner_mut().enable_if(ctx, !is_empty);
self.cancel_btn.show_if(ctx, is_empty);
@ -253,18 +272,22 @@ impl Component for PassphraseKeyboard {
fn place(&mut self, bounds: Rect) -> Rect {
const CONFIRM_BTN_WIDTH: i16 = 78;
const CONFIRM_EMPTY_BTN_WIDTH: i16 = 32;
const INPUT_INSETS: Insets = Insets::new(10, 2, 10, 4);
const CONFIRM_BTN_INSETS: Insets = Insets::new(5, 0, 5, 0);
let bounds = bounds.inset(theme::borders());
let (top_area, keypad_area) =
bounds.split_bottom(4 * theme::PASSPHRASE_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING);
self.keypad_area = keypad_area;
let (input_area, confirm_btn_area) = top_area.split_right(CONFIRM_BTN_WIDTH);
let confirm_empty_btn_area = confirm_btn_area
.split_right(CONFIRM_EMPTY_BTN_WIDTH + CONFIRM_EMPTY_BTN_MARGIN_RIGHT)
.1;
let top_area = top_area.inset(INPUT_INSETS);
let input_area = input_area.inset(INPUT_INSETS);
let confirm_btn_area = confirm_btn_area.inset(CONFIRM_BTN_INSETS);
let confirm_empty_btn_area = confirm_empty_btn_area.inset(CONFIRM_EMPTY_BTN_INSETS);
let key_grid = Grid::new(keypad_area, 4, 3).with_spacing(theme::BUTTON_SPACING);
let next_btn_area = key_grid.cell(11);
@ -276,6 +299,7 @@ impl Component for PassphraseKeyboard {
// control buttons
self.confirm_btn.place(confirm_btn_area);
self.confirm_empty_btn.place(confirm_empty_btn_area);
self.next_btn.place(next_btn_area);
self.erase_btn.place(erase_cancel_area);
self.cancel_btn.place(erase_cancel_area);
@ -310,9 +334,17 @@ impl Component for PassphraseKeyboard {
if let Some(ButtonMsg::Clicked) = self.next_btn.event(ctx, event) {
self.on_page_change(ctx, SwipeDirection::Left);
}
// Confirm button was clicked, we're done.
if let Some(ButtonMsg::Clicked) = self.confirm_empty_btn.event(ctx, event) {
return Some(PassphraseKeyboardMsg::Confirmed(unwrap!(
ShortString::try_from(self.passphrase())
)));
}
if let Some(ButtonMsg::Clicked) = self.confirm_btn.event(ctx, event) {
// Confirm button was clicked, we're done.
return Some(PassphraseKeyboardMsg::Confirmed);
return Some(PassphraseKeyboardMsg::Confirmed(unwrap!(
ShortString::try_from(self.passphrase())
)));
}
if let Some(ButtonMsg::Clicked) = self.cancel_btn.event(ctx, event) {
// Cancel button is visible and clicked, cancel
@ -365,12 +397,13 @@ impl Component for PassphraseKeyboard {
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.input.render(target);
self.next_btn.render(target);
self.erase_btn.render(target);
self.confirm_btn.render(target);
if self.input.textbox.is_empty() {
self.confirm_empty_btn.render(target);
self.cancel_btn.render(target);
// FIXME: when prompt fixed in Figma
// self.input_prompt.render(target);
self.input_prompt.render(target);
} else {
self.confirm_btn.render(target);
self.erase_btn.render(target);
}
for btn in &self.keys {
btn.render(target);

View File

@ -30,7 +30,7 @@ impl PromptScreen {
theme::GREEN,
theme::GREY_EXTRA_DARK,
theme::GREEN_LIGHT,
theme::ICON_SIMPLE_CHECKMARK,
theme::ICON_SIMPLE_CHECKMARK30,
))
}
@ -40,7 +40,7 @@ impl PromptScreen {
theme::ORANGE_LIGHT,
theme::GREY_EXTRA_DARK,
theme::ORANGE_DIMMED,
theme::ICON_SIMPLE_CHECKMARK,
theme::ICON_SIMPLE_CHECKMARK30,
))
}

View File

@ -167,7 +167,7 @@ impl StatusScreen {
pub fn new_success(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::ICON_SIMPLE_CHECKMARK30,
theme::GREEN_LIME,
theme::GREEN_LIGHT,
DismissType::SwipeUp,
@ -177,7 +177,7 @@ impl StatusScreen {
pub fn new_success_timeout(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::ICON_SIMPLE_CHECKMARK30,
theme::GREEN_LIME,
theme::GREEN_LIGHT,
DismissType::Timeout(Timeout::new(TIMEOUT_MS)),
@ -187,7 +187,7 @@ impl StatusScreen {
pub fn new_neutral(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::ICON_SIMPLE_CHECKMARK30,
theme::GREY_EXTRA_LIGHT,
theme::GREY_DARK,
DismissType::SwipeUp,
@ -197,7 +197,7 @@ impl StatusScreen {
pub fn new_neutral_timeout(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::ICON_SIMPLE_CHECKMARK30,
theme::GREY_EXTRA_LIGHT,
theme::GREY_DARK,
DismissType::Timeout(Timeout::new(TIMEOUT_MS)),

View File

@ -0,0 +1,85 @@
use crate::{
error,
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
strutil::{ShortString, TString},
translations::TR,
ui::{
component::{ComponentExt, SwipeDirection},
flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
},
};
use super::super::component::{
Frame, FrameMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PromptMsg, PromptScreen,
};
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum RequestPassphrase {
Keypad,
ConfirmEmpty,
}
impl FlowState for RequestPassphrase {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, _direction: SwipeDirection) -> StateChange {
self.do_nothing()
}
fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) {
(Self::Keypad, FlowMsg::Text(s)) => {
if s.is_empty() {
Self::ConfirmEmpty.transit()
} else {
self.return_msg(FlowMsg::Text(s))
}
}
(Self::Keypad, FlowMsg::Cancelled) => self.return_msg(FlowMsg::Cancelled),
(Self::ConfirmEmpty, FlowMsg::Cancelled) => Self::Keypad.transit(),
(Self::ConfirmEmpty, FlowMsg::Confirmed) => {
self.return_msg(FlowMsg::Text(ShortString::new()))
}
_ => self.do_nothing(),
}
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, RequestPassphrase::new_obj) }
}
impl RequestPassphrase {
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let _prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let _max_len: u32 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?;
let content_confirm_empty = Frame::left_aligned(
TR::passphrase__continue_with_empty_passphrase.into(),
PromptScreen::new_yes_or_no(),
)
.map(|msg| match msg {
FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed),
FrameMsg::Content(PromptMsg::Cancelled) => Some(FlowMsg::Cancelled),
_ => None,
});
let content_keypad = PassphraseKeyboard::new().map(|msg| match msg {
PassphraseKeyboardMsg::Confirmed(s) => Some(FlowMsg::Text(s)),
PassphraseKeyboardMsg::Cancelled => Some(FlowMsg::Cancelled),
});
let res = SwipeFlow::new(&RequestPassphrase::Keypad)?
.with_page(&RequestPassphrase::Keypad, content_keypad)?
.with_page(&RequestPassphrase::ConfirmEmpty, content_confirm_empty)?;
Ok(LayoutObj::new(res)?.into())
}
}

View File

@ -78,7 +78,7 @@ include_icon!(ICON_CONFIRM_INPUT, "model_mercury/res/confirm_input30.toif");
include_icon!(ICON_DELETE, "model_mercury/res/delete30.toif");
include_icon!(ICON_MENU, "model_mercury/res/menu30.toif");
include_icon!(
ICON_SIMPLE_CHECKMARK,
ICON_SIMPLE_CHECKMARK30,
"model_mercury/res/simple_checkmark30.toif"
);
include_icon!(ICON_SIGN, "model_mercury/res/sign30.toif");
@ -301,33 +301,6 @@ pub const fn button_confirm() -> ButtonStyleSheet {
}
}
// TODO: delete
pub const fn button_cancel() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::BOLD,
text_color: FG,
button_color: ORANGE_LIGHT,
icon_color: GREY_LIGHT,
background_color: BG,
},
active: &ButtonStyle {
font: Font::BOLD,
text_color: FG,
button_color: ORANGE_DIMMED,
icon_color: GREY_LIGHT,
background_color: BG,
},
disabled: &ButtonStyle {
font: Font::BOLD,
text_color: GREY_LIGHT,
button_color: ORANGE_DIMMED,
icon_color: GREY_LIGHT,
background_color: BG,
},
}
}
pub const fn button_danger() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
@ -490,6 +463,33 @@ pub const fn button_passphrase_confirm() -> ButtonStyleSheet {
}
}
pub const fn button_passphrase_confirm_empty() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: GREY,
button_color: GREY_EXTRA_DARK,
icon_color: GREY,
background_color: BG,
},
active: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: BG,
button_color: GREY_LIGHT,
icon_color: BG,
background_color: GREY_LIGHT,
},
// not used
disabled: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: BG,
button_color: BG,
icon_color: BG,
background_color: BG,
},
}
}
pub const fn button_passphrase_next() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {