Vít Obrusník 2 weeks ago committed by GitHub
commit e5d288b1c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -213,6 +213,7 @@ static void _librust_qstrs(void) {
MP_QSTR_firmware_update__title_fingerprint;
MP_QSTR_flow_confirm_reset_create;
MP_QSTR_flow_confirm_reset_recover;
MP_QSTR_flow_confirm_set_new_pin;
MP_QSTR_flow_get_address;
MP_QSTR_flow_prompt_backup;
MP_QSTR_flow_show_share_words;
@ -306,6 +307,9 @@ static void _librust_qstrs(void) {
MP_QSTR_passphrase__turn_off;
MP_QSTR_passphrase__turn_on;
MP_QSTR_path;
MP_QSTR_pin__cancel_description;
MP_QSTR_pin__cancel_info;
MP_QSTR_pin__cancel_setup;
MP_QSTR_pin__change;
MP_QSTR_pin__changed;
MP_QSTR_pin__cursor_will_change;
@ -353,6 +357,7 @@ static void _librust_qstrs(void) {
MP_QSTR_progress__x_seconds_left_template;
MP_QSTR_progress_event;
MP_QSTR_prompt;
MP_QSTR_prompt_screen;
MP_QSTR_qr_title;
MP_QSTR_reboot_to_bootloader__just_a_moment;
MP_QSTR_reboot_to_bootloader__restart;

@ -1254,6 +1254,9 @@ pub enum TranslatedString {
instructions__continue_in_app = 859, // "Continue in the app"
words__cancel_and_exit = 860, // "Cancel and exit"
address__confirmed = 861, // "Receive address confirmed"
pin__cancel_description = 862, // "Continue without PIN"
pin__cancel_info = 863, // "Without a PIN, anyone can access this device."
pin__cancel_setup = 864, // "Cancel PIN setup"
}
impl TranslatedString {
@ -2503,6 +2506,9 @@ impl TranslatedString {
Self::instructions__continue_in_app => "Continue in the app",
Self::words__cancel_and_exit => "Cancel and exit",
Self::address__confirmed => "Receive address confirmed",
Self::pin__cancel_description => "Continue without PIN",
Self::pin__cancel_info => "Without a PIN, anyone can access this device.",
Self::pin__cancel_setup => "Cancel PIN setup",
}
}
@ -3753,6 +3759,9 @@ impl TranslatedString {
Qstr::MP_QSTR_instructions__continue_in_app => Some(Self::instructions__continue_in_app),
Qstr::MP_QSTR_words__cancel_and_exit => Some(Self::words__cancel_and_exit),
Qstr::MP_QSTR_address__confirmed => Some(Self::address__confirmed),
Qstr::MP_QSTR_pin__cancel_description => Some(Self::pin__cancel_description),
Qstr::MP_QSTR_pin__cancel_info => Some(Self::pin__cancel_info),
Qstr::MP_QSTR_pin__cancel_setup => Some(Self::pin__cancel_setup),
_ => None,
}
}

@ -301,7 +301,7 @@ impl Bip39Input {
// Auto-complete button.
self.button.enable(ctx);
self.button
.set_stylesheet(ctx, theme::button_pin_autocomplete());
.set_stylesheet(ctx, theme::button_bip39_autocomplete());
self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_AUTOFILL));
self.button_suggestion
@ -310,7 +310,7 @@ impl Bip39Input {
} else {
// Disabled button.
self.button.disable(ctx);
self.button.set_stylesheet(ctx, theme::button_pin());
self.button.set_stylesheet(ctx, theme::button_keyboard());
self.button.set_content(ctx, ButtonContent::Text("".into()));
}
}

@ -58,7 +58,7 @@ where
keys: T::keys()
.map(|t| {
Button::with_text(t.into())
.styled(theme::button_pin())
.styled(theme::button_keyboard())
.with_text_align(Alignment::Center)
})
.map(Child::new),

@ -67,7 +67,7 @@ impl PassphraseKeyboard {
.with_long_press(theme::ERASE_HOLD_DURATION)
.into_child(),
keys: KEYBOARD[STARTING_PAGE].map(|text| {
Child::new(Button::new(Self::key_content(text)).styled(theme::button_pin()))
Child::new(Button::new(Self::key_content(text)).styled(theme::button_keyboard()))
}),
scrollbar: ScrollBar::horizontal(),
fade: Cell::new(false),

@ -10,7 +10,7 @@ use crate::{
base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe,
Never, Pad, TimerToken,
},
display::{self, Font},
display::Font,
event::TouchEvent,
geometry::{Alignment, Alignment2D, Grid, Insets, Offset, Rect},
model_mercury::component::{
@ -28,15 +28,16 @@ pub enum PinKeyboardMsg {
}
const MAX_LENGTH: usize = 50;
const MAX_VISIBLE_DOTS: usize = 14;
const MAX_VISIBLE_DIGITS: usize = 16;
const MAX_VISIBLE_DOTS: usize = 18;
const MAX_VISIBLE_DIGITS: usize = 18;
const DIGIT_COUNT: usize = 10; // 0..10
const HEADER_PADDING_SIDE: i16 = 5;
const HEADER_PADDING_BOTTOM: i16 = 12;
const HEADER_PADDING_TOP: i16 = 4;
const HEADER_PADDING_SIDE: i16 = 2;
const HEADER_PADDING_BOTTOM: i16 = 4;
const HEADER_PADDING: Insets = Insets::new(
theme::borders().top,
HEADER_PADDING_TOP,
HEADER_PADDING_SIDE,
HEADER_PADDING_BOTTOM,
HEADER_PADDING_SIDE,
@ -57,10 +58,6 @@ pub struct PinKeyboard<'a> {
}
impl<'a> PinKeyboard<'a> {
// Label position fine-tuning.
const MAJOR_OFF: Offset = Offset::y(11);
const MINOR_OFF: Offset = Offset::y(11);
pub fn new(
major_prompt: TString<'a>,
minor_prompt: TString<'a>,
@ -68,17 +65,13 @@ impl<'a> PinKeyboard<'a> {
allow_cancel: bool,
) -> Self {
// Control buttons.
let erase_btn = Button::with_icon_blend(
theme::IMAGE_BG_BACK_BTN,
theme::ICON_BACK,
Offset::new(30, 12),
)
.styled(theme::button_reset())
.with_long_press(theme::ERASE_HOLD_DURATION)
.initially_enabled(false);
let erase_btn = Button::with_icon(theme::ICON_DELETE)
.styled(theme::button_pin_erase())
.with_long_press(theme::ERASE_HOLD_DURATION)
.initially_enabled(false);
let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child();
let cancel_btn = Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel());
let cancel_btn = Button::with_icon(theme::ICON_CLOSE).styled(theme::button_pin_cancel());
let cancel_btn = Maybe::new(theme::BG, cancel_btn, allow_cancel).into_child();
Self {
@ -94,7 +87,7 @@ impl<'a> PinKeyboard<'a> {
erase_btn,
cancel_btn,
confirm_btn: Button::with_icon(theme::ICON_CONFIRM)
.styled(theme::button_confirm())
.styled(theme::button_pin_confirm())
.initially_enabled(false)
.into_child(),
digit_btns: Self::generate_digit_buttons(),
@ -108,7 +101,10 @@ impl<'a> PinKeyboard<'a> {
random::shuffle(&mut digits);
digits
.map(|c| Button::with_text(c.into()))
.map(|b| b.styled(theme::button_pin()))
.map(|b| {
b.styled(theme::button_keyboard())
.with_text_align(Alignment::Center)
})
.map(Child::new)
}
@ -150,20 +146,10 @@ impl Component for PinKeyboard<'_> {
type Msg = PinKeyboardMsg;
fn place(&mut self, bounds: Rect) -> Rect {
// Ignore the top padding for now, we need it to reliably register textbox touch
// events.
let borders_no_top = Insets {
top: 0,
..theme::borders()
};
// Prompts and PIN dots display.
let (header, keypad) = bounds
.inset(borders_no_top)
.split_bottom(4 * theme::PIN_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING);
let (header, keypad) =
bounds.split_bottom(4 * theme::PIN_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING);
let prompt = header.inset(HEADER_PADDING);
// the inset -3 is a workaround for long text in "re-enter wipe code"
let major_area = prompt.translate(Self::MAJOR_OFF).inset(Insets::right(-3));
let minor_area = prompt.translate(Self::MINOR_OFF);
// Control buttons.
let grid = Grid::new(keypad, 4, 3).with_spacing(theme::BUTTON_SPACING);
@ -171,9 +157,9 @@ impl Component for PinKeyboard<'_> {
// Prompts and PIN dots display.
self.textbox_pad.place(header);
self.textbox.place(header);
self.major_prompt.place(major_area);
self.minor_prompt.place(minor_area);
self.major_warning.as_mut().map(|c| c.place(major_area));
self.major_prompt.place(prompt);
self.minor_prompt.place(prompt);
self.major_warning.as_mut().map(|c| c.place(prompt));
// Control buttons.
let erase_cancel_area = grid.row_col(3, 0);
@ -247,28 +233,13 @@ impl Component for PinKeyboard<'_> {
}
fn paint(&mut self) {
self.erase_btn.paint();
self.textbox_pad.paint();
if self.textbox.inner().is_empty() {
if let Some(ref mut w) = self.major_warning {
w.paint();
} else {
self.major_prompt.paint();
}
self.minor_prompt.paint();
self.cancel_btn.paint();
} else {
self.textbox.paint();
}
self.confirm_btn.paint();
for btn in &mut self.digit_btns {
btn.paint();
}
todo!("remove when ui-t3t1 done");
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.erase_btn.render(target);
self.textbox_pad.render(target);
if self.textbox.inner().is_empty() {
if let Some(ref w) = self.major_warning {
w.render(target);
@ -280,7 +251,9 @@ impl Component for PinKeyboard<'_> {
} else {
self.textbox.render(target);
}
self.confirm_btn.render(target);
for btn in &self.digit_btns {
btn.render(target);
}
@ -310,7 +283,7 @@ struct PinDots {
impl PinDots {
const DOT: i16 = 6;
const PADDING: i16 = 6;
const PADDING: i16 = 7;
const TWITCH: i16 = 4;
fn new(style: TextStyle) -> Self {
@ -361,128 +334,63 @@ impl PinDots {
&self.digits
}
fn paint_digits(&self, area: Rect) {
let center = area.center() + Offset::y(Font::MONO.text_height() / 2);
let right = center + Offset::x(Font::MONO.text_width("0") * (MAX_VISIBLE_DOTS as i16) / 2);
let digits = self.digits.len();
if digits <= MAX_VISIBLE_DOTS {
display::text_center(
center,
&self.digits,
Font::MONO,
self.style.text_color,
self.style.background_color,
);
} else {
let offset: usize = digits.saturating_sub(MAX_VISIBLE_DIGITS);
display::text_right(
right,
&self.digits[offset..],
Font::MONO,
self.style.text_color,
self.style.background_color,
);
}
}
fn render_digits<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) {
let center = area.center() + Offset::y(Font::MONO.text_height() / 2);
let right = center + Offset::x(Font::MONO.text_width("0") * (MAX_VISIBLE_DOTS as i16) / 2);
let left = area.left_center() + Offset::y(Font::MONO.visible_text_height("1") / 2);
let digits = self.digits.len();
if digits <= MAX_VISIBLE_DOTS {
shape::Text::new(center, &self.digits)
.with_align(Alignment::Center)
if digits <= MAX_VISIBLE_DIGITS {
shape::Text::new(left, &self.digits)
.with_align(Alignment::Start)
.with_font(Font::MONO)
.with_fg(self.style.text_color)
.render(target);
} else {
let offset: usize = digits.saturating_sub(MAX_VISIBLE_DIGITS);
shape::Text::new(right, &self.digits[offset..])
.with_align(Alignment::End)
shape::Text::new(left, &self.digits[offset..])
.with_align(Alignment::Start)
.with_font(Font::MONO)
.with_fg(self.style.text_color)
.render(target);
}
}
fn paint_dots(&self, area: Rect) {
let mut cursor = self.size().snap(area.center(), Alignment2D::CENTER);
let digits = self.digits.len();
let dots_visible = digits.min(MAX_VISIBLE_DOTS);
let step = Self::DOT + Self::PADDING;
// Jiggle when overflowed.
if digits > dots_visible && digits % 2 == 0 {
cursor.x += Self::TWITCH
}
// Small leftmost dot.
if digits > dots_visible + 1 {
theme::DOT_SMALL.draw(
cursor - Offset::x(2 * step),
Alignment2D::TOP_LEFT,
self.style.text_color,
self.style.background_color,
);
}
// Greyed out dot.
if digits > dots_visible {
theme::DOT_ACTIVE.draw(
cursor - Offset::x(step),
Alignment2D::TOP_LEFT,
theme::GREY_LIGHT,
self.style.background_color,
);
}
// Draw a dot for each PIN digit.
for _ in 0..dots_visible {
theme::DOT_ACTIVE.draw(
cursor,
Alignment2D::TOP_LEFT,
self.style.text_color,
self.style.background_color,
);
cursor.x += step;
}
}
fn render_dots<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) {
let mut cursor = self.size().snap(area.center(), Alignment2D::CENTER);
let mut cursor = area.left_center();
let digits = self.digits.len();
let dots_visible = digits.min(MAX_VISIBLE_DOTS);
let step = Self::DOT + Self::PADDING;
// Jiggle when overflowed.
if digits > dots_visible && digits % 2 == 0 {
if digits >= MAX_VISIBLE_DOTS + 2 && (digits + 1) % 2 == 0 {
cursor.x += Self::TWITCH
}
let mut digit_idx = 0;
// Small leftmost dot.
if digits > dots_visible + 1 {
shape::ToifImage::new(cursor - Offset::x(2 * step), theme::DOT_SMALL.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(self.style.text_color)
if digits >= MAX_VISIBLE_DOTS + 2 {
shape::ToifImage::new(cursor, theme::DOT_SMALL.toif)
.with_align(Alignment2D::CENTER_LEFT)
.with_fg(theme::GREY_DARK)
.render(target);
cursor.x += step;
digit_idx += 1;
}
// Greyed out dot.
if digits > dots_visible {
shape::ToifImage::new(cursor - Offset::x(step), theme::DOT_ACTIVE.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(theme::GREY_LIGHT)
if digits >= MAX_VISIBLE_DOTS + 1 {
shape::ToifImage::new(cursor, theme::DOT_ACTIVE.toif)
.with_align(Alignment2D::CENTER_LEFT)
.with_fg(theme::GREY)
.render(target);
cursor.x += step;
digit_idx += 1;
}
// Draw a dot for each PIN digit.
for _ in 0..dots_visible {
for _ in digit_idx..dots_visible {
shape::ToifImage::new(cursor, theme::DOT_ACTIVE.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_align(Alignment2D::CENTER_LEFT)
.with_fg(self.style.text_color)
.render(target);
cursor.x += step;
@ -521,13 +429,7 @@ impl Component for PinDots {
}
fn paint(&mut self) {
let dot_area = self.area.inset(HEADER_PADDING);
self.pad.paint();
if self.display_digits {
self.paint_digits(dot_area)
} else {
self.paint_dots(dot_area)
}
// TODO: remove when ui-t3t1 done
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {

@ -25,7 +25,7 @@ impl SelectWordCount {
SelectWordCount {
button: LABELS.map(|t| {
Button::with_text(t.into())
.styled(theme::button_pin())
.styled(theme::button_keyboard())
.with_text_align(Alignment::Center)
}),
}

@ -0,0 +1,210 @@
use crate::{
error,
strutil::TString,
translations::TR,
ui::{
component::{text::paragraphs::Paragraph, ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
},
};
use super::super::{
component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg},
theme,
};
// TODO: merge with code from https://github.com/trezor/trezor-firmware/pull/3805
// when ready
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmAction {
Intro,
Menu,
Confirm,
}
/// ConfirmAction flow without a separate "Tap to confirm" or "Hold to confirm"
/// screen. Swiping up directly from the intro screen confirms action.
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmActionSimple {
Intro,
Menu,
}
impl FlowState for ConfirmAction {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmAction::Intro, SwipeDirection::Left) => {
Decision::Goto(ConfirmAction::Menu, direction)
}
(ConfirmAction::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmAction::Intro, direction)
}
(ConfirmAction::Intro, SwipeDirection::Up) => {
Decision::Goto(ConfirmAction::Confirm, direction)
}
(ConfirmAction::Confirm, SwipeDirection::Down) => {
Decision::Goto(ConfirmAction::Intro, direction)
}
(ConfirmAction::Confirm, SwipeDirection::Left) => {
Decision::Goto(ConfirmAction::Menu, direction)
}
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(ConfirmAction::Intro, FlowMsg::Info) => {
Decision::Goto(ConfirmAction::Menu, SwipeDirection::Left)
}
(ConfirmAction::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmAction::Intro, SwipeDirection::Right)
}
(ConfirmAction::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
(ConfirmAction::Confirm, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed),
(ConfirmAction::Confirm, FlowMsg::Info) => {
Decision::Goto(ConfirmAction::Menu, SwipeDirection::Left)
}
_ => Decision::Nothing,
}
}
}
impl FlowState for ConfirmActionSimple {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmActionSimple::Intro, SwipeDirection::Left) => {
Decision::Goto(ConfirmActionSimple::Menu, direction)
}
(ConfirmActionSimple::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmActionSimple::Intro, direction)
}
(ConfirmActionSimple::Intro, SwipeDirection::Up) => {
Decision::Return(FlowMsg::Confirmed)
}
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(ConfirmActionSimple::Intro, FlowMsg::Info) => {
Decision::Goto(ConfirmActionSimple::Menu, SwipeDirection::Left)
}
(ConfirmActionSimple::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmActionSimple::Intro, SwipeDirection::Right)
}
(ConfirmActionSimple::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
ui::{
component::text::paragraphs::{ParagraphSource, ParagraphVecShort, VecExt},
layout::obj::LayoutObj,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, new_confirm_action_obj) }
}
fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let action: Option<TString> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
let description: Option<TString> = kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
// let verb: Option<TString> = kwargs
// .get(Qstr::MP_QSTR_verb)
// .unwrap_or_else(|_| Obj::const_none())
// .try_into_option()?;
let verb_cancel: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let reverse: bool = kwargs.get_or(Qstr::MP_QSTR_reverse, false)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
// let hold_danger: bool = kwargs.get_or(Qstr::MP_QSTR_hold_danger, false)?;
let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, false)?;
let paragraphs = {
let action = action.unwrap_or("".into());
let description = description.unwrap_or("".into());
let mut paragraphs = ParagraphVecShort::new();
if !reverse {
paragraphs
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, action))
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description));
} else {
paragraphs
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description))
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, action));
}
paragraphs.into_paragraphs()
};
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
let content_menu = if let Some(verb_cancel) = verb_cancel {
Frame::left_aligned(
"".into(),
VerticalMenu::empty().danger(theme::ICON_CANCEL, verb_cancel.into()),
)
} else {
Frame::left_aligned(
"".into(),
VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()),
)
}
.with_cancel_button()
.map(move |msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(_)) => Some(FlowMsg::Choice(0)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
if !prompt_screen {
let store = flow_store().add(content_intro)?.add(content_menu)?;
let res = SwipeFlow::new(ConfirmActionSimple::Intro, store)?;
return Ok(LayoutObj::new(res)?.into());
} else {
let (prompt, prompt_action) = if hold {
(
PromptScreen::new_hold_to_confirm(),
TR::instructions__hold_to_confirm.into(),
)
} else {
(
PromptScreen::new_tap_to_confirm(),
TR::instructions__tap_to_confirm.into(),
)
};
let content_confirm = Frame::left_aligned(title, prompt)
.with_footer(prompt_action, None)
.with_menu_button();
// .with_overlapping_content();
// if let Some(subtitle) = subtitle {
// content_confirm = content_confirm.with_subtitle(subtitle);
// }
let content_confirm = content_confirm.map(move |msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
FrameMsg::Button(_) => Some(FlowMsg::Info),
});
let store = flow_store()
.add(content_intro)?
.add(content_menu)?
.add(content_confirm)?;
let res = SwipeFlow::new(ConfirmAction::Intro, store)?;
return Ok(LayoutObj::new(res)?.into());
};
}

@ -0,0 +1,145 @@
use crate::{
error,
micropython::qstr::Qstr,
strutil::TString,
translations::TR,
ui::{
component::{
text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, SwipeDirection,
},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
},
};
use super::super::{
component::{
CancelInfoConfirmMsg, Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg,
},
theme,
};
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum SetNewPin {
Intro,
Menu,
CancelPinIntro,
CancelPinConfirm,
}
impl FlowState for SetNewPin {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(SetNewPin::Intro, SwipeDirection::Left) => Decision::Goto(SetNewPin::Menu, direction),
(SetNewPin::CancelPinIntro, SwipeDirection::Up) => {
Decision::Goto(SetNewPin::CancelPinConfirm, direction)
}
(SetNewPin::CancelPinConfirm, SwipeDirection::Down) => {
Decision::Goto(SetNewPin::CancelPinIntro, direction)
}
(SetNewPin::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(SetNewPin::Intro, FlowMsg::Info) => {
Decision::Goto(SetNewPin::Menu, SwipeDirection::Left)
}
(SetNewPin::Menu, FlowMsg::Choice(0)) => {
Decision::Goto(SetNewPin::CancelPinIntro, SwipeDirection::Left)
}
(SetNewPin::Menu, FlowMsg::Cancelled) => {
Decision::Goto(SetNewPin::Intro, SwipeDirection::Right)
}
(SetNewPin::CancelPinIntro, FlowMsg::Cancelled) => {
Decision::Goto(SetNewPin::Menu, SwipeDirection::Right)
}
(SetNewPin::CancelPinConfirm, FlowMsg::Cancelled) => {
Decision::Goto(SetNewPin::CancelPinIntro, SwipeDirection::Right)
}
(SetNewPin::CancelPinConfirm, FlowMsg::Confirmed) => {
Decision::Return(FlowMsg::Cancelled)
}
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::layout::obj::LayoutObj,
};
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_set_new_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, SetNewPin::new_obj) }
}
impl SetNewPin {
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
// TODO: supply more arguments for Wipe code setting when figma done
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let par_array: [Paragraph<'static>; 1] =
[Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description)];
let paragraphs = Paragraphs::new(par_array);
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| {
matches!(msg, FrameMsg::Button(CancelInfoConfirmMsg::Info)).then_some(FlowMsg::Info)
});
let content_menu = Frame::left_aligned(
"".into(),
VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::pin__cancel_setup.into()),
)
.with_cancel_button()
.map(|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
FrameMsg::Button(_) => None,
});
let par_array_cancel_intro: [Paragraph<'static>; 2] = [
Paragraph::new(&theme::TEXT_WARNING, TR::words__not_recommended),
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, TR::pin__cancel_info),
];
let paragraphs_cancel_intro = Paragraphs::new(par_array_cancel_intro);
let content_cancel_intro = Frame::left_aligned(
TR::pin__cancel_setup.into(),
SwipePage::vertical(paragraphs_cancel_intro),
)
.with_cancel_button()
.with_footer(
TR::instructions__swipe_up.into(),
Some(TR::pin__cancel_description.into()),
)
.map(|msg| match msg {
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
_ => None,
});
let content_cancel_confirm = Frame::left_aligned(
TR::pin__cancel_setup.into(),
PromptScreen::new_tap_to_cancel(),
)
.with_footer(TR::instructions__tap_to_confirm.into(), None)
.map(|msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
_ => None,
});
let store = flow_store()
.add(content_intro)?
.add(content_menu)?
.add(content_cancel_intro)?
.add(content_cancel_confirm)?;
let res = SwipeFlow::new(SetNewPin::Intro, store)?;
Ok(LayoutObj::new(res)?.into())
}
}

@ -1,12 +1,16 @@
pub mod confirm_action;
pub mod confirm_reset_create;
pub mod confirm_reset_recover;
pub mod confirm_set_new_pin;
pub mod get_address;
pub mod prompt_backup;
pub mod show_share_words;
pub mod warning_hi_prio;
pub use confirm_action::new_confirm_action;
pub use confirm_reset_create::ConfirmResetCreate;
pub use confirm_reset_recover::ConfirmResetRecover;
pub use confirm_set_new_pin::SetNewPin;
pub use get_address::GetAddress;
pub use prompt_backup::PromptBackup;
pub use show_share_words::ShowShareWords;

@ -331,54 +331,6 @@ impl ComponentMsgObj for super::component::bl_confirm::Confirm<'_> {
}
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let action: Option<TString> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
let description: Option<TString> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let verb: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb_cancel: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let reverse: bool = kwargs.get_or(Qstr::MP_QSTR_reverse, false)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let hold_danger: bool = kwargs.get_or(Qstr::MP_QSTR_hold_danger, false)?;
let paragraphs = {
let action = action.unwrap_or("".into());
let description = description.unwrap_or("".into());
let mut paragraphs = ParagraphVecShort::new();
if !reverse {
paragraphs
.add(Paragraph::new(&theme::TEXT_DEMIBOLD, action))
.add(Paragraph::new(&theme::TEXT_NORMAL, description));
} else {
paragraphs
.add(Paragraph::new(&theme::TEXT_NORMAL, description))
.add(Paragraph::new(&theme::TEXT_DEMIBOLD, action));
}
paragraphs.into_paragraphs()
};
let mut page = if hold {
ButtonPage::new(paragraphs, theme::BG).with_hold()?
} else {
ButtonPage::new(paragraphs, theme::BG).with_cancel_confirm(verb_cancel, verb)
};
if hold && hold_danger {
page = page.with_confirm_style(theme::button_danger())
}
let obj = LayoutObj::new(Frame::left_aligned(title, page))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -882,14 +834,26 @@ fn new_show_modal(
extern "C" fn new_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_ERROR,
theme::ERROR_COLOR,
theme::FG,
theme::BG,
);
new_show_modal(kwargs, icon, theme::button_default())
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let allow_cancel: bool = kwargs.get(Qstr::MP_QSTR_allow_cancel)?.try_into()?;
let content = SwipeUpScreen::new(Paragraphs::new([Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
description,
)]));
let frame = if allow_cancel {
Frame::left_aligned(title, content)
.with_cancel_button()
.with_danger()
.with_footer(TR::instructions__swipe_up.into(), None)
} else {
Frame::left_aligned(title, content)
.with_danger()
.with_footer(TR::instructions__swipe_up.into(), None)
};
let obj = LayoutObj::new(frame)?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
@ -1598,14 +1562,16 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str,
/// action: str | None,
/// description: str | None,
/// subtitle: str | None = None,
/// verb: str | None = None,
/// verb_cancel: str | None = None,
/// hold: bool = False,
/// hold_danger: bool = False,
/// reverse: bool = False,
/// prompt_screen: bool = False,
/// ) -> LayoutObj[UiResult]:
/// """Confirm action."""
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, flow::confirm_action::new_confirm_action).as_obj(),
/// def confirm_emphasized(
/// *,
@ -1670,6 +1636,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Confirm TOS before creating a wallet and have a user hold to confirm creation."""
Qstr::MP_QSTR_flow_confirm_reset_create => obj_fn_kw!(0, flow::confirm_reset_create::new_confirm_reset_create).as_obj(),
// TODO: supply more arguments for Wipe code setting when figma done
/// def flow_confirm_set_new_pin(
/// *,
/// title: str,
/// description: str,
/// ) -> LayoutObj[UiResult]:
/// """Confirm new PIN setup with an option to cancel action."""
Qstr::MP_QSTR_flow_confirm_set_new_pin => obj_fn_kw!(0, flow::confirm_set_new_pin::new_set_new_pin).as_obj(),
/// def show_info_with_cancel(
/// *,
/// title: str,

@ -35,9 +35,11 @@ pub const GREY_DARK: Color = Color::rgb(0x46, 0x48, 0x4A);
pub const GREY: Color = Color::rgb(0x8B, 0x8F, 0x93); // secondary text, subtitle, instructions
pub const GREY_LIGHT: Color = Color::rgb(0xC7, 0xCD, 0xD3); // content
pub const GREY_EXTRA_LIGHT: Color = Color::rgb(0xF0, 0xF0, 0xF0); // primary text, header
pub const GREEN_DARK: Color = Color::rgb(0x06, 0x1E, 0x19);
pub const GREEN: Color = Color::rgb(0x08, 0x74, 0x48);
pub const GREEN_LIGHT: Color = Color::rgb(0x0B, 0xA5, 0x67);
pub const GREEN_LIME: Color = Color::rgb(0x9B, 0xE8, 0x87);
pub const ORANGE_DARK: Color = Color::rgb(0x18, 0x0C, 0x0A);
pub const ORANGE_DIMMED: Color = Color::rgb(0x9E, 0x57, 0x42);
pub const ORANGE_LIGHT: Color = Color::rgb(0xFF, 0x8D, 0x6A); // cancel button
@ -46,7 +48,6 @@ pub const RED: Color = Color::rgb(0xE7, 0x0E, 0x0E); // button
pub const RED_DARK: Color = Color::rgb(0xAE, 0x09, 0x09); // button pressed
pub const YELLOW: Color = Color::rgb(0xD9, 0x9E, 0x00); // button
pub const YELLOW_DARK: Color = Color::rgb(0x7A, 0x58, 0x00); // button pressed
pub const GREEN_DARK: Color = Color::rgb(0x00, 0x55, 0x1D); // button pressed
pub const BLUE: Color = Color::rgb(0x06, 0x1E, 0xAD); // button
pub const BLUE_DARK: Color = Color::rgb(0x04, 0x10, 0x58); // button pressed
pub const OFF_WHITE: Color = Color::rgb(0xDE, 0xDE, 0xDE); // very light grey
@ -109,6 +110,19 @@ include_icon!(
);
include_icon!(ICON_CENTRAL_CIRCLE, "model_mercury/res/central_circle.toif");
// Scrollbar/PIN dots - taken from model T
include_icon!(DOT_ACTIVE, "model_mercury/res/scroll-active.toif");
include_icon!(DOT_INACTIVE, "model_mercury/res/scroll-inactive.toif");
include_icon!(
DOT_INACTIVE_HALF,
"model_mercury/res/scroll-inactive-half.toif"
);
include_icon!(
DOT_INACTIVE_QUARTER,
"model_mercury/res/scroll-inactive-quarter.toif"
);
include_icon!(DOT_SMALL, "model_mercury/res/scroll-small.toif");
// TODO remove TT icons:
// Button icons.
@ -163,16 +177,6 @@ include_icon!(ICON_LOGO_EMPTY, "model_tt/res/lock_empty.toif");
// Default homescreen
pub const IMAGE_HOMESCREEN: &[u8] = include_res!("model_tt/res/bg.jpg");
// Scrollbar/PIN dots.
include_icon!(DOT_ACTIVE, "model_tt/res/scroll-active.toif");
include_icon!(DOT_INACTIVE, "model_tt/res/scroll-inactive.toif");
include_icon!(DOT_INACTIVE_HALF, "model_tt/res/scroll-inactive-half.toif");
include_icon!(
DOT_INACTIVE_QUARTER,
"model_tt/res/scroll-inactive-quarter.toif"
);
include_icon!(DOT_SMALL, "model_tt/res/scroll-small.toif");
pub const fn label_default() -> TextStyle {
TEXT_NORMAL
}
@ -399,6 +403,7 @@ pub const fn button_danger() -> ButtonStyleSheet {
}
}
// TODO: delete
pub const fn button_reset() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
@ -425,7 +430,8 @@ pub const fn button_reset() -> ButtonStyleSheet {
}
}
pub const fn button_pin() -> ButtonStyleSheet {
// used for PIN digit keys and passphrase/recovery letter keys
pub const fn button_keyboard() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::NORMAL,
@ -436,9 +442,9 @@ pub const fn button_pin() -> ButtonStyleSheet {
},
active: &ButtonStyle {
font: Font::NORMAL,
text_color: GREY_LIGHT,
button_color: GREY_EXTRA_DARK,
icon_color: GREY_LIGHT,
text_color: BG,
button_color: GREY_LIGHT,
icon_color: BG,
background_color: BG,
},
disabled: &ButtonStyle {
@ -451,24 +457,76 @@ pub const fn button_pin() -> ButtonStyleSheet {
}
}
// TODO: will button_pin_xyz styles be the same for PIN and Mnemonic keyboard?
// Wait for Figma
pub const fn button_pin_confirm() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREEN_DARK,
icon_color: GREEN_LIME,
background_color: BG,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREEN_LIGHT,
icon_color: GREEN_DARK,
background_color: BG,
},
disabled: &ButtonStyle {
font: Font::MONO,
text_color: GREY_DARK,
button_color: BG,
icon_color: GREEN_LIGHT,
icon_color: GREY_DARK,
background_color: BG,
},
}
}
pub const fn button_pin_cancel() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: BG, // TODO: gradient
icon_color: ORANGE_LIGHT,
background_color: BG,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREY_DARK,
button_color: ORANGE_LIGHT,
icon_color: BG,
background_color: BG,
},
// not used
disabled: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: BG,
icon_color: GREEN_LIGHT,
background_color: BG,
},
}
}
pub const fn button_pin_erase() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: BG, // TODO: gradient
icon_color: GREY,
background_color: BG,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREY_LIGHT,
icon_color: BG,
background_color: BG,
},
// not used
disabled: &ButtonStyle {
font: Font::MONO,
text_color: FG,
@ -479,7 +537,7 @@ pub const fn button_pin_confirm() -> ButtonStyleSheet {
}
}
pub const fn button_pin_autocomplete() -> ButtonStyleSheet {
pub const fn button_bip39_autocomplete() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
@ -691,7 +749,7 @@ pub const RECOVERY_SPACING: i16 = 18;
pub const CORNER_BUTTON_SIDE: i16 = 44;
pub const CORNER_BUTTON_SPACING: i16 = BUTTON_SPACING;
pub const INFO_BUTTON_HEIGHT: i16 = 44;
pub const PIN_BUTTON_HEIGHT: i16 = 40;
pub const PIN_BUTTON_HEIGHT: i16 = 52;
pub const MNEMONIC_BUTTON_HEIGHT: i16 = 52;
pub const RESULT_PADDING: i16 = 6;
pub const RESULT_FOOTER_START: i16 = 171;

@ -76,11 +76,13 @@ def confirm_action(
title: str,
action: str | None,
description: str | None,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
hold: bool = False,
hold_danger: bool = False,
reverse: bool = False,
prompt_screen: bool = False,
) -> LayoutObj[UiResult]:
"""Confirm action."""
@ -155,6 +157,15 @@ def flow_confirm_reset_create() -> LayoutObj[UiResult]:
"""Confirm TOS before creating a wallet and have a user hold to confirm creation."""
# rust/src/ui/model_mercury/layout.rs
def flow_confirm_set_new_pin(
*,
title: str,
description: str,
) -> LayoutObj[UiResult]:
"""Confirm new PIN setup with an option to cancel action."""
# rust/src/ui/model_mercury/layout.rs
def show_info_with_cancel(
*,

@ -469,6 +469,9 @@ class TR:
passphrase__title_source: str = "PASSPHRASE SOURCE"
passphrase__turn_off: str = "Turn off passphrase protection?"
passphrase__turn_on: str = "Turn on passphrase protection?"
pin__cancel_description: str = "Continue without PIN"
pin__cancel_info: str = "Without a PIN, anyone can access this device."
pin__cancel_setup: str = "Cancel PIN setup"
pin__change: str = "Change PIN?"
pin__changed: str = "PIN changed."
pin__cursor_will_change: str = "Position of the cursor will change between entries for enhanced security."

@ -1415,8 +1415,9 @@ async def pin_mismatch_popup(
is_wipe_code: bool = False,
) -> None:
await button_request("pin_mismatch", code=BR_TYPE_OTHER)
title = TR.wipe_code__wipe_code_mismatch if is_wipe_code else TR.pin__pin_mismatch
description = TR.wipe_code__mismatch if is_wipe_code else TR.pin__mismatch
title = TR.wipe_code__mismatch if is_wipe_code else TR.pin__mismatch
description = TR.wipe_code__enter_new if is_wipe_code else TR.pin__reenter_new
return await show_error_popup(
title,
description,
@ -1443,14 +1444,7 @@ async def confirm_set_new_pin(
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.confirm_emphasized(
title=title.upper(),
items=(
(True, description + "\n\n"),
information,
),
verb=TR.buttons__turn_on,
)
trezorui2.flow_confirm_set_new_pin(title=title, description=description)
),
br_type,
br_code,

@ -471,8 +471,11 @@
"passphrase__title_source": "PASSPHRASE SOURCE",
"passphrase__turn_off": "Turn off passphrase protection?",
"passphrase__turn_on": "Turn on passphrase protection?",
"pin__cancel_info": "Without a PIN, anyone can access this device.",
"pin__cancel_setup": "Cancel PIN setup",
"pin__change": "Change PIN?",
"pin__changed": "PIN changed.",
"pin__cancel_description": "Continue without PIN",
"pin__cursor_will_change": "Position of the cursor will change between entries for enhanced security.",
"pin__diff_from_wipe_code": "The new PIN must be different from your wipe code.",
"pin__disabled": "PIN protection\nturned off.",

@ -860,5 +860,8 @@
"858": "address_details__derivation_path",
"859": "instructions__continue_in_app",
"860": "words__cancel_and_exit",
"861": "address__confirmed"
"861": "address__confirmed",
"862": "pin__cancel_description",
"863": "pin__cancel_info",
"864": "pin__cancel_setup"
}

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "87891f7bef0164d655882be5c86631c413e7d9dce3492725080930c69c82a122",
"datetime": "2024-05-01T21:20:31.907021",
"commit": "4b3e1a08258dbf09fa153db1bf2b8a419b1d84e6"
"merkle_root": "522ec2af48343bcf4528469e821dfc3852d14dae290239556222cbcc4356dc98",
"datetime": "2024-05-09T13:12:46.955119",
"commit": "e3e8db19dbff80bc0281e1304f41fa8ae74d9650"
},
"history": [
{

@ -17,8 +17,8 @@ LEFT = grid(DISPLAY_WIDTH, 3, 0)
MID = grid(DISPLAY_WIDTH, 3, 1)
RIGHT = grid(DISPLAY_WIDTH, 3, 2)
TOP = grid(DISPLAY_HEIGHT, 4, 0)
BOTTOM = grid(DISPLAY_HEIGHT, 4, 3)
TOP = grid(DISPLAY_HEIGHT, 6, 0)
BOTTOM = grid(DISPLAY_HEIGHT, 6, 5)
OK = (RIGHT, BOTTOM)
CANCEL = (LEFT, BOTTOM)

@ -46,10 +46,12 @@ def get_char_category(char: str) -> PassphraseCategory:
def go_next(debug: "DebugLink", wait: bool = False) -> "LayoutContent" | None:
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
return debug.click(buttons.OK, wait=wait) # type: ignore
elif debug.model in (models.T2B1,):
return debug.press_right(wait=wait) # type: ignore
elif debug.model in (models.T3T1,):
return debug.swipe_up(wait=wait)
else:
raise RuntimeError("Unknown model")
@ -111,6 +113,17 @@ def navigate_to_action_and_press(
debug.press_middle(wait=True)
def unlock_gesture(debug: "DebugLink", wait: bool = False) -> "LayoutContent" | None:
if debug.model in (models.T2T1,):
return debug.click(buttons.OK, wait=wait) # type: ignore
elif debug.model in (models.T2B1,):
return debug.press_right(wait=wait) # type: ignore
elif debug.model in (models.T3T1,):
return debug.click(buttons.TAP_TO_CONFIRM, wait=wait) # type: ignore
else:
raise RuntimeError("Unknown model")
def _get_action_index(wanted_action: str, all_actions: list[str]) -> int:
"""Get index of the action in the list of all actions"""
if wanted_action in all_actions:

@ -62,7 +62,7 @@ def select_number_of_words(
if wait:
debug.wait_layout()
TR.assert_equals(debug.read_layout().text_content(), "recovery__num_of_words")
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
# click the number
word_option_offset = 6
word_options = (12, 18, 20, 24, 33)
@ -81,6 +81,20 @@ def select_number_of_words(
for _ in range(index):
debug.press_right(wait=True)
layout = debug.press_middle(wait=True)
elif debug.model in (models.T3T1,):
if num_of_words == 12:
coords = buttons.grid34(0, 1)
elif num_of_words == 18:
coords = buttons.grid34(2, 1)
elif num_of_words == 20:
coords = buttons.grid34(0, 2)
elif num_of_words == 24:
coords = buttons.grid34(2, 2)
elif num_of_words == 33:
coords = buttons.grid34(1, 3)
else:
raise ValueError("Invalid num_of_words")
layout = debug.click(coords, wait=True)
else:
raise ValueError("Unknown model")

@ -29,7 +29,7 @@ from .. import translations as TR
from ..device_tests.bitcoin.payment_req import make_coinjoin_request
from ..tx_cache import TxCache
from . import recovery
from .common import go_next
from .common import go_next, unlock_gesture
if TYPE_CHECKING:
from trezorlib.debuglink import DebugLink, LayoutContent
@ -279,7 +279,8 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle
# unlock
# lockscreen triggered automatically
debug.wait_layout(wait_for_external_change=True)
layout = go_next(debug, wait=True)
layout = unlock_gesture(debug, wait=True)
assert "PinKeyboard" in layout.all_components()
layout = debug.input(PIN4, wait=True)
assert layout is not None
@ -301,7 +302,7 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"):
recovery.select_number_of_words(debug, 20)
if debug.model in (models.T2T1, models.T3T1):
layout = debug.click(buttons.OK, wait=True)
layout = go_next(debug, wait=True)
assert layout.main_component() == "MnemonicKeyboard"
elif debug.model in (models.T2B1,):
layout = debug.press_right(wait=True)

@ -95,7 +95,9 @@ def prepare(
elif situation == Situation.PIN_SETUP:
# Set new PIN
device_handler.run(device.change_pin) # type: ignore
TR.assert_in(debug.wait_layout().text_content(), "pin__turn_on")
TR.assert_in_multiple(
debug.wait_layout().text_content(), ["pin__turn_on", "pin__info"]
)
if debug.model in (models.T2T1, models.T3T1):
go_next(debug)
elif debug.model in (models.T2B1,):
@ -306,12 +308,15 @@ def test_pin_setup(device_handler: "BackgroundDeviceHandler"):
def test_pin_setup_mismatch(device_handler: "BackgroundDeviceHandler"):
with PIN_CANCELLED, prepare(device_handler, Situation.PIN_SETUP) as debug:
_enter_two_times(debug, "1", "2")
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
go_next(debug)
_cancel_pin(debug)
elif debug.model in (models.T2B1,):
debug.press_middle()
debug.press_no()
elif debug.model in (models.T3T1,):
go_next(debug, wait=True)
_cancel_pin(debug)
@pytest.mark.setup_client(pin="1")

@ -421,12 +421,11 @@ def pytest_configure(config: "Config") -> None:
def pytest_runtest_setup(item: pytest.Item) -> None:
"""Called for each test item (class, individual tests).
Ensures that altcoin tests are skipped, and that no test is skipped on
both T1 and TT.
Ensures that altcoin tests are skipped, and that no test is skipped for all models.
"""
if all(
item.get_closest_marker(marker)
for marker in ("skip_t1b1", "skip_t2t1", "skip_t2b1")
for marker in ("skip_t1b1", "skip_t2t1", "skip_t2b1", "skip_t3t1")
):
raise RuntimeError("Don't skip tests for all trezor models!")

@ -21,6 +21,7 @@ from trezorlib.client import MAX_PIN_LENGTH, PASSPHRASE_TEST_PATH
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import Cancelled, TrezorFailure
from .. import buttons
from ..input_flows import (
InputFlowCodeChangeFail,
InputFlowNewCodeMismatch,
@ -180,3 +181,27 @@ def test_change_invalid_current(client: Client):
client.init_device()
assert client.features.pin_protection is True
_check_pin(client, PIN4)
@pytest.mark.skip_t2b1()
@pytest.mark.skip_t2t1()
@pytest.mark.setup_client(pin=None)
def test_pin_menu_cancel_setup(client: Client):
def cancel_pin_setup_input_flow():
yield
# enter context menu
client.debug.click(buttons.CORNER_BUTTON)
client.debug.synchronize_at("VerticalMenu")
# click "Cancel PIN setup"
client.debug.click(buttons.VERTICAL_MENU[0])
client.debug.synchronize_at("Paragraphs")
# swipe through info screen
client.debug.swipe_up()
client.debug.synchronize_at("PromptScreen")
# tap to confirm
client.debug.click(buttons.TAP_TO_CONFIRM)
with client, pytest.raises(Cancelled):
client.set_input_flow(cancel_pin_setup_input_flow)
client.call(messages.ChangePin())
_check_no_pin(client)

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save