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:
parent
15526343c9
commit
2b4d6eb798
@ -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);
|
||||
}
|
||||
if let Some(ButtonMsg::Clicked) = self.confirm_btn.event(ctx, event) {
|
||||
|
||||
// Confirm button was clicked, we're done.
|
||||
return Some(PassphraseKeyboardMsg::Confirmed);
|
||||
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) {
|
||||
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);
|
||||
|
@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user