1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-24 07:18:09 +00:00

feat(core/ui): T3T1 receive flow

[no changelog]
This commit is contained in:
Martin Milata 2024-04-30 13:32:48 +02:00
parent 1028c3500f
commit 5020868c2c
32 changed files with 583 additions and 320 deletions

View File

@ -30,14 +30,21 @@ static void _librust_qstrs(void) {
MP_QSTR_addr_mismatch__wrong_derivation_path; MP_QSTR_addr_mismatch__wrong_derivation_path;
MP_QSTR_addr_mismatch__xpub_mismatch; MP_QSTR_addr_mismatch__xpub_mismatch;
MP_QSTR_address; MP_QSTR_address;
MP_QSTR_address__cancel_contact_support;
MP_QSTR_address__cancel_receive;
MP_QSTR_address__confirmed;
MP_QSTR_address__public_key; MP_QSTR_address__public_key;
MP_QSTR_address__qr_code;
MP_QSTR_address__title_cosigner; MP_QSTR_address__title_cosigner;
MP_QSTR_address__title_receive_address; MP_QSTR_address__title_receive_address;
MP_QSTR_address__title_yours; MP_QSTR_address__title_yours;
MP_QSTR_address_details__account_info;
MP_QSTR_address_details__derivation_path; MP_QSTR_address_details__derivation_path;
MP_QSTR_address_details__derivation_path_colon;
MP_QSTR_address_details__title_receive_address; MP_QSTR_address_details__title_receive_address;
MP_QSTR_address_details__title_receiving_to; MP_QSTR_address_details__title_receiving_to;
MP_QSTR_address_label; MP_QSTR_address_label;
MP_QSTR_address_qr;
MP_QSTR_address_title; MP_QSTR_address_title;
MP_QSTR_allow_cancel; MP_QSTR_allow_cancel;
MP_QSTR_altcoin_tx_summary; MP_QSTR_altcoin_tx_summary;
@ -210,6 +217,7 @@ static void _librust_qstrs(void) {
MP_QSTR_flow_get_address; MP_QSTR_flow_get_address;
MP_QSTR_flow_prompt_backup; MP_QSTR_flow_prompt_backup;
MP_QSTR_flow_show_share_words; MP_QSTR_flow_show_share_words;
MP_QSTR_flow_warning_hi_prio;
MP_QSTR_get_language; MP_QSTR_get_language;
MP_QSTR_hold; MP_QSTR_hold;
MP_QSTR_hold_danger; MP_QSTR_hold_danger;
@ -237,6 +245,7 @@ static void _librust_qstrs(void) {
MP_QSTR_inputs__return; MP_QSTR_inputs__return;
MP_QSTR_inputs__show; MP_QSTR_inputs__show;
MP_QSTR_inputs__space; MP_QSTR_inputs__space;
MP_QSTR_instructions__continue_in_app;
MP_QSTR_instructions__hold_to_confirm; MP_QSTR_instructions__hold_to_confirm;
MP_QSTR_instructions__swipe_up; MP_QSTR_instructions__swipe_up;
MP_QSTR_instructions__tap_to_confirm; MP_QSTR_instructions__tap_to_confirm;
@ -623,6 +632,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__array_of; MP_QSTR_words__array_of;
MP_QSTR_words__blockhash; MP_QSTR_words__blockhash;
MP_QSTR_words__buying; MP_QSTR_words__buying;
MP_QSTR_words__cancel_and_exit;
MP_QSTR_words__confirm; MP_QSTR_words__confirm;
MP_QSTR_words__confirm_fee; MP_QSTR_words__confirm_fee;
MP_QSTR_words__contains; MP_QSTR_words__contains;

View File

@ -20,7 +20,7 @@ pub enum TranslatedString {
address__title_cosigner = 7, // "Cosigner" address__title_cosigner = 7, // "Cosigner"
address__title_receive_address = 8, // "Receive address" address__title_receive_address = 8, // "Receive address"
address__title_yours = 9, // "Yours" address__title_yours = 9, // "Yours"
address_details__derivation_path = 10, // "Derivation path:" address_details__derivation_path_colon = 10, // "Derivation path:"
address_details__title_receive_address = 11, // "Receive address" address_details__title_receive_address = 11, // "Receive address"
address_details__title_receiving_to = 12, // "Receiving to" address_details__title_receiving_to = 12, // "Receiving to"
authenticate__confirm_template = 13, // "Allow connected computer to confirm your {0} is genuine?" authenticate__confirm_template = 13, // "Allow connected computer to confirm your {0} is genuine?"
@ -1260,6 +1260,14 @@ pub enum TranslatedString {
reset__check_backup_instructions = 859, // "Let's do a quick check of your backup." reset__check_backup_instructions = 859, // "Let's do a quick check of your backup."
words__instructions = 860, // "Instructions" words__instructions = 860, // "Instructions"
words__not_recommended = 861, // "Not recommended!" words__not_recommended = 861, // "Not recommended!"
address_details__account_info = 862, // "Account info"
address__cancel_contact_support = 863, // "If receive address doesn't match, contact Trezor Support at trezor.io/support."
address__cancel_receive = 864, // "Cancel receive"
address__qr_code = 865, // "QR code"
address_details__derivation_path = 866, // "Derivation path"
instructions__continue_in_app = 867, // "Continue in the app"
words__cancel_and_exit = 868, // "Cancel and exit"
address__confirmed = 869, // "Receive address confirmed"
} }
impl TranslatedString { impl TranslatedString {
@ -1275,7 +1283,7 @@ impl TranslatedString {
Self::address__title_cosigner => "Cosigner", Self::address__title_cosigner => "Cosigner",
Self::address__title_receive_address => "Receive address", Self::address__title_receive_address => "Receive address",
Self::address__title_yours => "Yours", Self::address__title_yours => "Yours",
Self::address_details__derivation_path => "Derivation path:", Self::address_details__derivation_path_colon => "Derivation path:",
Self::address_details__title_receive_address => "Receive address", Self::address_details__title_receive_address => "Receive address",
Self::address_details__title_receiving_to => "Receiving to", Self::address_details__title_receiving_to => "Receiving to",
Self::authenticate__confirm_template => "Allow connected computer to confirm your {0} is genuine?", Self::authenticate__confirm_template => "Allow connected computer to confirm your {0} is genuine?",
@ -2515,6 +2523,14 @@ impl TranslatedString {
Self::reset__check_backup_instructions => "Let's do a quick check of your backup.", Self::reset__check_backup_instructions => "Let's do a quick check of your backup.",
Self::words__instructions => "Instructions", Self::words__instructions => "Instructions",
Self::words__not_recommended => "Not recommended!", Self::words__not_recommended => "Not recommended!",
Self::address_details__account_info => "Account info",
Self::address__cancel_contact_support => "If receive address doesn't match, contact Trezor Support at trezor.io/support.",
Self::address__cancel_receive => "Cancel receive",
Self::address__qr_code => "QR code",
Self::address_details__derivation_path => "Derivation path",
Self::instructions__continue_in_app => "Continue in the app",
Self::words__cancel_and_exit => "Cancel and exit",
Self::address__confirmed => "Receive address confirmed",
} }
} }
@ -2531,7 +2547,7 @@ impl TranslatedString {
Qstr::MP_QSTR_address__title_cosigner => Some(Self::address__title_cosigner), Qstr::MP_QSTR_address__title_cosigner => Some(Self::address__title_cosigner),
Qstr::MP_QSTR_address__title_receive_address => Some(Self::address__title_receive_address), Qstr::MP_QSTR_address__title_receive_address => Some(Self::address__title_receive_address),
Qstr::MP_QSTR_address__title_yours => Some(Self::address__title_yours), Qstr::MP_QSTR_address__title_yours => Some(Self::address__title_yours),
Qstr::MP_QSTR_address_details__derivation_path => Some(Self::address_details__derivation_path), Qstr::MP_QSTR_address_details__derivation_path_colon => Some(Self::address_details__derivation_path_colon),
Qstr::MP_QSTR_address_details__title_receive_address => Some(Self::address_details__title_receive_address), Qstr::MP_QSTR_address_details__title_receive_address => Some(Self::address_details__title_receive_address),
Qstr::MP_QSTR_address_details__title_receiving_to => Some(Self::address_details__title_receiving_to), Qstr::MP_QSTR_address_details__title_receiving_to => Some(Self::address_details__title_receiving_to),
Qstr::MP_QSTR_authenticate__confirm_template => Some(Self::authenticate__confirm_template), Qstr::MP_QSTR_authenticate__confirm_template => Some(Self::authenticate__confirm_template),
@ -3771,6 +3787,14 @@ impl TranslatedString {
Qstr::MP_QSTR_reset__check_backup_instructions => Some(Self::reset__check_backup_instructions), Qstr::MP_QSTR_reset__check_backup_instructions => Some(Self::reset__check_backup_instructions),
Qstr::MP_QSTR_words__instructions => Some(Self::words__instructions), Qstr::MP_QSTR_words__instructions => Some(Self::words__instructions),
Qstr::MP_QSTR_words__not_recommended => Some(Self::words__not_recommended), Qstr::MP_QSTR_words__not_recommended => Some(Self::words__not_recommended),
Qstr::MP_QSTR_address_details__account_info => Some(Self::address_details__account_info),
Qstr::MP_QSTR_address__cancel_contact_support => Some(Self::address__cancel_contact_support),
Qstr::MP_QSTR_address__cancel_receive => Some(Self::address__cancel_receive),
Qstr::MP_QSTR_address__qr_code => Some(Self::address__qr_code),
Qstr::MP_QSTR_address_details__derivation_path => Some(Self::address_details__derivation_path),
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),
_ => None, _ => None,
} }
} }

View File

@ -82,7 +82,7 @@ pub struct Child<T> {
} }
impl<T> Child<T> { impl<T> Child<T> {
pub fn new(component: T) -> Self { pub const fn new(component: T) -> Self {
Self { Self {
component, component,
marked_for_paint: true, marked_for_paint: true,

View File

@ -1,6 +1,7 @@
use super::{Component, Event, EventCtx, Never}; use super::{Component, Event, EventCtx, Never};
use crate::ui::{geometry::Rect, shape::Renderer}; use crate::ui::{geometry::Rect, shape::Renderer};
#[derive(Clone)]
pub struct Empty; pub struct Empty;
impl Component for Empty { impl Component for Empty {

View File

@ -18,7 +18,7 @@ pub struct Label<'a> {
} }
impl<'a> Label<'a> { impl<'a> Label<'a> {
pub fn new(text: TString<'a>, align: Alignment, style: TextStyle) -> Self { pub const fn new(text: TString<'a>, align: Alignment, style: TextStyle) -> Self {
Self { Self {
text, text,
layout: TextLayout::new(style).with_align(align), layout: TextLayout::new(style).with_align(align),
@ -26,34 +26,34 @@ impl<'a> Label<'a> {
} }
} }
pub fn left_aligned(text: TString<'a>, style: TextStyle) -> Self { pub const fn left_aligned(text: TString<'a>, style: TextStyle) -> Self {
Self::new(text, Alignment::Start, style) Self::new(text, Alignment::Start, style)
} }
pub fn right_aligned(text: TString<'a>, style: TextStyle) -> Self { pub const fn right_aligned(text: TString<'a>, style: TextStyle) -> Self {
Self::new(text, Alignment::End, style) Self::new(text, Alignment::End, style)
} }
pub fn centered(text: TString<'a>, style: TextStyle) -> Self { pub const fn centered(text: TString<'a>, style: TextStyle) -> Self {
Self::new(text, Alignment::Center, style) Self::new(text, Alignment::Center, style)
} }
pub fn top_aligned(mut self) -> Self { pub const fn top_aligned(mut self) -> Self {
self.vertical = Alignment::Start; self.vertical = Alignment::Start;
self self
} }
pub fn vertically_centered(mut self) -> Self { pub const fn vertically_centered(mut self) -> Self {
self.vertical = Alignment::Center; self.vertical = Alignment::Center;
self self
} }
pub fn bottom_aligned(mut self) -> Self { pub const fn bottom_aligned(mut self) -> Self {
self.vertical = Alignment::End; self.vertical = Alignment::End;
self self
} }
pub fn styled(mut self, style: TextStyle) -> Self { pub const fn styled(mut self, style: TextStyle) -> Self {
self.layout.style = style; self.layout.style = style;
self self
} }
@ -74,7 +74,7 @@ impl<'a> Label<'a> {
self.layout.bounds self.layout.bounds
} }
pub fn alignment(&self) -> Alignment { pub const fn alignment(&self) -> Alignment {
self.layout.align self.layout.align
} }

View File

@ -199,7 +199,7 @@ impl TextStyle {
impl TextLayout { impl TextLayout {
/// Create a new text layout, with empty size and default text parameters /// Create a new text layout, with empty size and default text parameters
/// filled from `T`. /// filled from `T`.
pub fn new(style: TextStyle) -> Self { pub const fn new(style: TextStyle) -> Self {
Self { Self {
bounds: Rect::zero(), bounds: Rect::zero(),
padding_top: 0, padding_top: 0,
@ -210,12 +210,12 @@ impl TextLayout {
} }
} }
pub fn with_bounds(mut self, bounds: Rect) -> Self { pub const fn with_bounds(mut self, bounds: Rect) -> Self {
self.bounds = bounds; self.bounds = bounds;
self self
} }
pub fn with_align(mut self, align: Alignment) -> Self { pub const fn with_align(mut self, align: Alignment) -> Self {
self.align = align; self.align = align;
self self
} }

View File

@ -25,6 +25,7 @@ use crate::{
/// consumption and conversion time. /// consumption and conversion time.
pub const MAX_HEX_CHARS_ON_SCREEN: usize = 256; pub const MAX_HEX_CHARS_ON_SCREEN: usize = 256;
#[derive(Clone)]
pub enum StrOrBytes { pub enum StrOrBytes {
Str(TString<'static>), Str(TString<'static>),
Bytes(Obj), Bytes(Obj),
@ -55,6 +56,7 @@ impl TryFrom<Obj> for StrOrBytes {
} }
} }
#[derive(Clone)]
pub struct ConfirmBlob { pub struct ConfirmBlob {
pub description: TString<'static>, pub description: TString<'static>,
pub extra: TString<'static>, pub extra: TString<'static>,

View File

@ -7,7 +7,7 @@ use crate::{
ui::{ ui::{
component::{ component::{
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
Component, Event, EventCtx, Paginate, Qr, Component, Event, EventCtx, Paginate,
}, },
geometry::Rect, geometry::Rect,
shape::Renderer, shape::Renderer,
@ -18,8 +18,8 @@ use super::{theme, Frame, FrameMsg};
const MAX_XPUBS: usize = 16; const MAX_XPUBS: usize = 16;
#[derive(Clone)]
pub struct AddressDetails { pub struct AddressDetails {
qr_code: Frame<Qr>,
details: Frame<Paragraphs<ParagraphVecShort<'static>>>, details: Frame<Paragraphs<ParagraphVecShort<'static>>>,
xpub_view: Frame<Paragraphs<Paragraph<'static>>>, xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
xpubs: Vec<(TString<'static>, TString<'static>), MAX_XPUBS>, xpubs: Vec<(TString<'static>, TString<'static>), MAX_XPUBS>,
@ -29,43 +29,38 @@ pub struct AddressDetails {
impl AddressDetails { impl AddressDetails {
pub fn new( pub fn new(
qr_title: TString<'static>,
qr_address: TString<'static>,
case_sensitive: bool,
details_title: TString<'static>, details_title: TString<'static>,
account: Option<TString<'static>>, account: Option<TString<'static>>,
path: Option<TString<'static>>, path: Option<TString<'static>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut para = ParagraphVecShort::new(); let mut para = ParagraphVecShort::new();
if let Some(a) = account { if let Some(a) = account {
para.add(Paragraph::new( para.add(Paragraph::new::<TString>(
&theme::TEXT_NORMAL, &theme::TEXT_SUB_GREY,
TR::words__account_colon, TR::words__account.into(),
));
para.add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, a));
}
if account.is_some() & path.is_some() {
para.add(Paragraph::new(
&theme::TEXT_SUB_GREY,
TString::from_str(" "),
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, a));
} }
if let Some(p) = path { if let Some(p) = path {
para.add(Paragraph::new( para.add(Paragraph::new::<TString>(
&theme::TEXT_NORMAL, &theme::TEXT_SUB_GREY,
TR::address_details__derivation_path, TR::address_details__derivation_path.into(),
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, p)); para.add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, p));
} }
let result = Self { let result = Self {
qr_code: Frame::left_aligned(
qr_title,
qr_address
.map(|s| Qr::new(s, case_sensitive))?
.with_border(7),
)
.with_cancel_button()
.with_border(theme::borders_horizontal_scroll()),
details: Frame::left_aligned(details_title, para.into_paragraphs()) details: Frame::left_aligned(details_title, para.into_paragraphs())
.with_cancel_button() .with_cancel_button()
.with_border(theme::borders_horizontal_scroll()), .with_border(theme::borders_horizontal_scroll()),
xpub_view: Frame::left_aligned( xpub_view: Frame::left_aligned(
" \n ".into(), " \n ".into(),
Paragraph::new(&theme::TEXT_MONO, "").into_paragraphs(), Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, "").into_paragraphs(),
) )
.with_cancel_button() .with_cancel_button()
.with_border(theme::borders_horizontal_scroll()), .with_border(theme::borders_horizontal_scroll()),
@ -121,13 +116,13 @@ impl AddressDetails {
impl Paginate for AddressDetails { impl Paginate for AddressDetails {
fn page_count(&mut self) -> usize { fn page_count(&mut self) -> usize {
let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum(); let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum();
2usize.saturating_add(total_xpub_pages.into()) 1usize.saturating_add(total_xpub_pages.into())
} }
fn change_page(&mut self, to_page: usize) { fn change_page(&mut self, to_page: usize) {
self.current_page = to_page; self.current_page = to_page;
if to_page > 1 { if to_page > 0 {
let i = to_page - 2; let i = to_page - 1;
let (xpub_index, xpub_page) = self.lookup(i); let (xpub_index, xpub_page) = self.lookup(i);
self.switch_xpub(xpub_index, xpub_page); self.switch_xpub(xpub_index, xpub_page);
} }
@ -138,7 +133,6 @@ impl Component for AddressDetails {
type Msg = (); type Msg = ();
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
self.qr_code.place(bounds);
self.details.place(bounds); self.details.place(bounds);
self.xpub_view.place(bounds); self.xpub_view.place(bounds);
@ -153,8 +147,7 @@ impl Component for AddressDetails {
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let msg = match self.current_page { let msg = match self.current_page {
0 => self.qr_code.event(ctx, event), 0 => self.details.event(ctx, event),
1 => self.details.event(ctx, event),
_ => self.xpub_view.event(ctx, event), _ => self.xpub_view.event(ctx, event),
}; };
match msg { match msg {
@ -165,16 +158,14 @@ impl Component for AddressDetails {
fn paint(&mut self) { fn paint(&mut self) {
match self.current_page { match self.current_page {
0 => self.qr_code.paint(), 0 => self.details.paint(),
1 => self.details.paint(),
_ => self.xpub_view.paint(), _ => self.xpub_view.paint(),
} }
} }
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
match self.current_page { match self.current_page {
0 => self.qr_code.render(target), 0 => self.details.render(target),
1 => self.details.render(target),
_ => self.xpub_view.render(target), _ => self.xpub_view.render(target),
} }
} }
@ -182,8 +173,7 @@ impl Component for AddressDetails {
#[cfg(feature = "ui_bounds")] #[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) { fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
match self.current_page { match self.current_page {
0 => self.qr_code.bounds(sink), 0 => self.details.bounds(sink),
1 => self.details.bounds(sink),
_ => self.xpub_view.bounds(sink), _ => self.xpub_view.bounds(sink),
} }
} }
@ -194,8 +184,7 @@ impl crate::trace::Trace for AddressDetails {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("AddressDetails"); t.component("AddressDetails");
match self.current_page { match self.current_page {
0 => t.child("qr_code", &self.qr_code), 0 => t.child("details", &self.details),
1 => t.child("details", &self.details),
_ => t.child("xpub_view", &self.xpub_view), _ => t.child("xpub_view", &self.xpub_view),
} }
} }

View File

@ -35,7 +35,7 @@ impl<T> Frame<T>
where where
T: Component, T: Component,
{ {
pub fn new(alignment: Alignment, title: TString<'static>, content: T) -> Self { pub const fn new(alignment: Alignment, title: TString<'static>, content: T) -> Self {
Self { Self {
title: Child::new( title: Child::new(
Label::new(title, alignment, theme::label_title_main()).vertically_centered(), Label::new(title, alignment, theme::label_title_main()).vertically_centered(),
@ -49,19 +49,19 @@ where
} }
} }
pub fn left_aligned(title: TString<'static>, content: T) -> Self { pub const fn left_aligned(title: TString<'static>, content: T) -> Self {
Self::new(Alignment::Start, title, content) Self::new(Alignment::Start, title, content)
} }
pub fn right_aligned(title: TString<'static>, content: T) -> Self { pub const fn right_aligned(title: TString<'static>, content: T) -> Self {
Self::new(Alignment::End, title, content) Self::new(Alignment::End, title, content)
} }
pub fn centered(title: TString<'static>, content: T) -> Self { pub const fn centered(title: TString<'static>, content: T) -> Self {
Self::new(Alignment::Center, title, content) Self::new(Alignment::Center, title, content)
} }
pub fn with_border(mut self, border: Insets) -> Self { pub const fn with_border(mut self, border: Insets) -> Self {
self.border = border; self.border = border;
self self
} }
@ -130,6 +130,11 @@ where
self self
} }
pub fn with_danger(self) -> Self {
self.button_styled(theme::button_danger())
.title_styled(theme::label_title_danger())
}
pub fn inner(&self) -> &T { pub fn inner(&self) -> &T {
self.content.inner() self.content.inner()
} }

View File

@ -10,6 +10,7 @@ use super::{theme, Swipe, SwipeDirection};
/// Component showing status of an operation. Most typically embedded as a /// Component showing status of an operation. Most typically embedded as a
/// content of a Frame and showing success (checkmark with a circle around). /// content of a Frame and showing success (checkmark with a circle around).
#[derive(Clone)]
pub struct StatusScreen { pub struct StatusScreen {
area: Rect, area: Rect,
icon: Icon, icon: Icon,
@ -18,6 +19,7 @@ pub struct StatusScreen {
dismiss_type: DismissType, dismiss_type: DismissType,
} }
#[derive(Clone)]
enum DismissType { enum DismissType {
SwipeUp(Swipe), SwipeUp(Swipe),
Timeout(Timeout), Timeout(Timeout),
@ -62,6 +64,15 @@ impl StatusScreen {
DismissType::SwipeUp(Swipe::new().up()), DismissType::SwipeUp(Swipe::new().up()),
) )
} }
pub fn new_neutral_timeout() -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::GREY_EXTRA_LIGHT,
theme::GREY_DARK,
DismissType::Timeout(Timeout::new(TIMEOUT_MS)),
)
}
} }
impl Component for StatusScreen { impl Component for StatusScreen {

View File

@ -10,6 +10,7 @@ use super::theme;
pub use crate::ui::component::SwipeDirection; pub use crate::ui::component::SwipeDirection;
#[derive(Clone)]
pub struct Swipe { pub struct Swipe {
pub area: Rect, pub area: Rect,
pub allow_up: bool, pub allow_up: bool,

View File

@ -1,33 +1,41 @@
use crate::{ use crate::{
error, error,
micropython::{iter::IterBuf, qstr::Qstr},
strutil::TString,
translations::TR,
ui::{ ui::{
component::{ component::{
image::BlendedImage, text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
text::paragraphs::{Paragraph, Paragraphs}, ComponentExt, Qr, SwipeDirection,
ComponentExt, Qr, SwipeDirection, Timeout,
}, },
flow::{ flow::{
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow, base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow,
SwipePage, SwipePage,
}, },
layout::util::ConfirmBlob,
}, },
}; };
use super::super::{ use super::super::{
component::{Frame, FrameMsg, IconDialog, VerticalMenu, VerticalMenuChoiceMsg}, component::{
AddressDetails, CancelInfoConfirmMsg, Frame, FrameMsg, PromptScreen, StatusScreen,
VerticalMenu, VerticalMenuChoiceMsg,
},
theme, theme,
}; };
const LONGSTRING: &'static str = "https://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKo"; const QR_BORDER: i16 = 4;
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum GetAddress { pub enum GetAddress {
Address, Address,
Tap,
Confirmed,
Menu, Menu,
QrCode, QrCode,
AccountInfo, AccountInfo,
Cancel, Cancel,
Success, CancelTap,
} }
impl FlowState for GetAddress { impl FlowState for GetAddress {
@ -36,8 +44,9 @@ impl FlowState for GetAddress {
(GetAddress::Address, SwipeDirection::Left) => { (GetAddress::Address, SwipeDirection::Left) => {
Decision::Goto(GetAddress::Menu, direction) Decision::Goto(GetAddress::Menu, direction)
} }
(GetAddress::Address, SwipeDirection::Up) => { (GetAddress::Address, SwipeDirection::Up) => Decision::Goto(GetAddress::Tap, direction),
Decision::Goto(GetAddress::Success, direction) (GetAddress::Tap, SwipeDirection::Down) => {
Decision::Goto(GetAddress::Address, direction)
} }
(GetAddress::Menu, SwipeDirection::Right) => { (GetAddress::Menu, SwipeDirection::Right) => {
Decision::Goto(GetAddress::Address, direction) Decision::Goto(GetAddress::Address, direction)
@ -48,7 +57,15 @@ impl FlowState for GetAddress {
(GetAddress::AccountInfo, SwipeDirection::Right) => { (GetAddress::AccountInfo, SwipeDirection::Right) => {
Decision::Goto(GetAddress::Menu, direction) Decision::Goto(GetAddress::Menu, direction)
} }
(GetAddress::Cancel, SwipeDirection::Up) => Decision::Return(FlowMsg::Cancelled), (GetAddress::Cancel, SwipeDirection::Up) => {
Decision::Goto(GetAddress::CancelTap, direction)
}
(GetAddress::Cancel, SwipeDirection::Right) => {
Decision::Goto(GetAddress::Menu, direction)
}
(GetAddress::CancelTap, SwipeDirection::Down) => {
Decision::Goto(GetAddress::Cancel, direction)
}
_ => Decision::Nothing, _ => Decision::Nothing,
} }
} }
@ -59,6 +76,12 @@ impl FlowState for GetAddress {
Decision::Goto(GetAddress::Menu, SwipeDirection::Left) Decision::Goto(GetAddress::Menu, SwipeDirection::Left)
} }
(GetAddress::Tap, FlowMsg::Confirmed) => {
Decision::Goto(GetAddress::Confirmed, SwipeDirection::Up)
}
(GetAddress::Confirmed, _) => Decision::Return(FlowMsg::Confirmed),
(GetAddress::Menu, FlowMsg::Choice(0)) => { (GetAddress::Menu, FlowMsg::Choice(0)) => {
Decision::Goto(GetAddress::QrCode, SwipeDirection::Left) Decision::Goto(GetAddress::QrCode, SwipeDirection::Left)
} }
@ -87,14 +110,19 @@ impl FlowState for GetAddress {
Decision::Goto(GetAddress::Menu, SwipeDirection::Right) Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
} }
(GetAddress::Success, _) => Decision::Return(FlowMsg::Confirmed), (GetAddress::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled),
(GetAddress::CancelTap, FlowMsg::Cancelled) => {
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
}
_ => Decision::Nothing, _ => Decision::Nothing,
} }
} }
} }
use crate::{ use crate::{
micropython::{buffer::StrBuffer, map::Map, obj::Obj, util}, micropython::{map::Map, obj::Obj, util},
ui::layout::obj::LayoutObj, ui::layout::obj::LayoutObj,
}; };
@ -103,83 +131,132 @@ pub extern "C" fn new_get_address(n_args: usize, args: *const Obj, kwargs: *mut
} }
impl GetAddress { impl GetAddress {
fn new(_args: &[Obj], _kwargs: &Map) -> Result<Obj, error::Error> { fn new(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = "Receive address".into(); // TODO: address__title_receive_address w/o uppercase
let description: Option<TString> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let extra: Option<TString> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
let address: Obj = kwargs.get(Qstr::MP_QSTR_address)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let address_qr: TString = kwargs.get(Qstr::MP_QSTR_address_qr)?.try_into()?;
let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?;
let account: Option<TString> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
let path: Option<TString> = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?;
let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?;
// Address
let data_style = if chunkify {
let address: TString = address.try_into()?;
theme::get_chunkified_text_style(address.len())
} else {
&theme::TEXT_MONO
};
let paragraphs = ConfirmBlob {
description: description.unwrap_or("".into()),
extra: extra.unwrap_or("".into()),
data: address.try_into()?,
description_font: &theme::TEXT_NORMAL,
extra_font: &theme::TEXT_DEMIBOLD,
data_font: data_style,
}
.into_paragraphs();
let content_address = 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));
// .one_button_request(ButtonRequestCode::Address, "show_address");
// Tap
let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm())
.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 content_confirmed = IgnoreSwipe::new(
Frame::left_aligned(
TR::address__confirmed.into(),
StatusScreen::new_success_timeout(),
)
.with_footer(TR::instructions__continue_in_app.into(), None),
)
.map(|_| Some(FlowMsg::Confirmed));
// Menu
let content_menu = Frame::left_aligned(
"".into(),
VerticalMenu::empty()
.item(theme::ICON_QR_CODE, TR::address__qr_code.into())
.item(
theme::ICON_CHEVRON_RIGHT,
TR::address_details__account_info.into(),
)
.danger(theme::ICON_CANCEL, TR::address__cancel_receive.into()),
)
.with_cancel_button()
.map(|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
// QrCode
let content_qr = Frame::left_aligned(
title,
IgnoreSwipe::new(
address_qr
.map(|s| Qr::new(s, case_sensitive))?
.with_border(QR_BORDER),
),
)
.with_cancel_button()
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled));
// AccountInfo
let mut ad = AddressDetails::new(TR::address_details__account_info.into(), account, path)?;
for i in IterBuf::new().try_iterate(xpubs)? {
let [xtitle, text]: [TString; 2] = util::iter_into_array(i)?;
ad.add_xpub(xtitle, text)?;
}
let content_account = SwipePage::vertical(ad).map(|_| Some(FlowMsg::Cancelled));
// Cancel
let content_cancel_info = Frame::left_aligned(
TR::address__cancel_receive.into(),
SwipePage::vertical(Paragraphs::new(Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
TR::address__cancel_contact_support,
))),
)
.with_cancel_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled));
// CancelTap
let content_cancel_tap = Frame::left_aligned(
TR::address__cancel_receive.into(),
PromptScreen::new_tap_to_cancel(),
)
.with_cancel_button()
.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() let store = flow_store()
.add( .add(content_address)?
Frame::left_aligned( .add(content_tap)?
"Receive".into(), .add(content_confirmed)?
SwipePage::vertical(Paragraphs::new(Paragraph::new( .add(content_menu)?
&theme::TEXT_MONO, .add(content_qr)?
StrBuffer::from(LONGSTRING), .add(content_account)?
))), .add(content_cancel_info)?
) .add(content_cancel_tap)?;
.with_subtitle("address".into())
.with_menu_button()
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)),
)?
.add(
Frame::left_aligned(
"".into(),
VerticalMenu::empty()
.item(theme::ICON_QR_CODE, "Address QR code".into())
.item(theme::ICON_CHEVRON_RIGHT, "Account info".into())
.danger(theme::ICON_CANCEL, "Cancel operation".into()),
)
.with_cancel_button()
.map(|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => {
Some(FlowMsg::Choice(i))
}
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
}),
)?
.add(
Frame::left_aligned(
"Receive address".into(),
IgnoreSwipe::new(Qr::new(
"https://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKo",
true,
)?),
)
.with_cancel_button()
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled)),
)?
.add(
Frame::left_aligned(
"Account info".into(),
SwipePage::vertical(Paragraphs::new(Paragraph::new(
&theme::TEXT_NORMAL,
StrBuffer::from("taproot xp"),
))),
)
.with_cancel_button()
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled)),
)?
.add(
Frame::left_aligned(
"Cancel receive".into(),
SwipePage::vertical(Paragraphs::new(Paragraph::new(
&theme::TEXT_NORMAL,
StrBuffer::from("O rly?"),
))),
)
.with_cancel_button()
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled)),
)?
.add(
IconDialog::new(
BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_WARN,
theme::SUCCESS_COLOR,
theme::FG,
theme::BG,
),
StrBuffer::from("Confirmed"),
Timeout::new(100),
)
.map(|_| Some(FlowMsg::Confirmed)),
)?;
let res = SwipeFlow::new(GetAddress::Address, store)?; let res = SwipeFlow::new(GetAddress::Address, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }

View File

@ -3,9 +3,11 @@ pub mod confirm_reset_recover;
pub mod get_address; pub mod get_address;
pub mod prompt_backup; pub mod prompt_backup;
pub mod show_share_words; pub mod show_share_words;
pub mod warning_hi_prio;
pub use confirm_reset_create::ConfirmResetCreate; pub use confirm_reset_create::ConfirmResetCreate;
pub use confirm_reset_recover::ConfirmResetRecover; pub use confirm_reset_recover::ConfirmResetRecover;
pub use get_address::GetAddress; pub use get_address::GetAddress;
pub use prompt_backup::PromptBackup; pub use prompt_backup::PromptBackup;
pub use show_share_words::ShowShareWords; pub use show_share_words::ShowShareWords;
pub use warning_hi_prio::WarningHiPrio;

View File

@ -0,0 +1,125 @@
use crate::{
error,
micropython::qstr::Qstr,
strutil::TString,
translations::TR,
ui::{
component::{
text::paragraphs::{Paragraph, ParagraphSource},
ComponentExt, SwipeDirection,
},
flow::{
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow,
SwipePage,
},
},
};
use super::super::{
component::{Frame, FrameMsg, StatusScreen, VerticalMenu, VerticalMenuChoiceMsg},
theme,
};
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum WarningHiPrio {
Message,
Menu,
Cancelled,
}
impl FlowState for WarningHiPrio {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(WarningHiPrio::Message, SwipeDirection::Left) => {
Decision::Goto(WarningHiPrio::Menu, direction)
}
(WarningHiPrio::Message, SwipeDirection::Up) => {
Decision::Goto(WarningHiPrio::Cancelled, direction)
}
(WarningHiPrio::Menu, SwipeDirection::Right) => {
Decision::Goto(WarningHiPrio::Message, direction)
}
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(WarningHiPrio::Message, FlowMsg::Info) => {
Decision::Goto(WarningHiPrio::Menu, SwipeDirection::Left)
}
(WarningHiPrio::Menu, FlowMsg::Choice(1)) => Decision::Return(FlowMsg::Confirmed),
(WarningHiPrio::Menu, FlowMsg::Choice(_)) => {
Decision::Goto(WarningHiPrio::Cancelled, SwipeDirection::Up)
}
(WarningHiPrio::Menu, FlowMsg::Cancelled) => {
Decision::Goto(WarningHiPrio::Message, SwipeDirection::Right)
}
(WarningHiPrio::Cancelled, _) => Decision::Return(FlowMsg::Cancelled),
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::layout::obj::LayoutObj,
};
pub extern "C" fn new_warning_hi_prio(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, WarningHiPrio::new) }
}
impl WarningHiPrio {
const EXTRA_PADDING: i16 = 6;
fn new(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = kwargs.get_or(Qstr::MP_QSTR_title, TR::words__warning.try_into()?)?;
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?;
let cancel: TString = TR::words__cancel_and_exit.into();
let confirm: TString = "Continue anyway".into(); // FIXME: en.json has punctuation
let done_title: TString = "Operation cancelled".into();
// Message
let paragraphs = [
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description),
Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value)
.with_top_padding(Self::EXTRA_PADDING),
]
.into_paragraphs();
let content_message = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), Some(cancel))
.with_danger()
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
// .one_button_request(ButtonRequestCode::Warning, br_type);
// Menu
let content_menu = Frame::left_aligned(
"".into(),
VerticalMenu::empty()
.item(theme::ICON_CANCEL, "Cancel".into()) // TODO: button__cancel after it's lowercase
.danger(theme::ICON_CHEVRON_RIGHT, confirm),
)
.with_cancel_button()
.map(|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
// Cancelled
let content_cancelled = IgnoreSwipe::new(
Frame::left_aligned(done_title, StatusScreen::new_neutral_timeout())
.with_footer(TR::instructions__continue_in_app.into(), None),
)
.map(|_| Some(FlowMsg::Cancelled));
let store = flow_store()
.add(content_message)?
.add(content_menu)?
.add(content_cancelled)?;
let res = SwipeFlow::new(WarningHiPrio::Message, store)?;
Ok(LayoutObj::new(res)?.into())
}
}

View File

@ -684,38 +684,6 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
extern "C" fn new_show_address_details(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let qr_title: TString = kwargs.get(Qstr::MP_QSTR_qr_title)?.try_into()?;
let details_title: TString = kwargs.get(Qstr::MP_QSTR_details_title)?.try_into()?;
let address: TString = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?;
let account: Option<TString> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
let path: Option<TString> = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?;
let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?;
let mut ad = AddressDetails::new(
qr_title,
address,
case_sensitive,
details_title,
account,
path,
)?;
for i in IterBuf::new().try_iterate(xpubs)? {
let [xtitle, text]: [TString; 2] = util::iter_into_array(i)?;
ad.add_xpub(xtitle, text)?;
}
let obj =
LayoutObj::new(SimplePage::horizontal(ad, theme::BG).with_swipe_right_to_go_back())?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_info_with_cancel(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_info_with_cancel(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -1012,12 +980,13 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?;
let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?;
let content = SwipeUpScreen::new(Paragraphs::new([Paragraph::new( let content = SwipeUpScreen::new(Paragraphs::new([
&theme::TEXT_MAIN_GREY_LIGHT, Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description),
value, Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value),
)])); ]));
let obj = LayoutObj::new( let obj = LayoutObj::new(
Frame::left_aligned(title, content) Frame::left_aligned(title, content)
.with_warning_button() .with_warning_button()
@ -1753,19 +1722,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Confirm TOS before creating a wallet and have a user hold to confirm creation.""" /// """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(), Qstr::MP_QSTR_flow_confirm_reset_create => obj_fn_kw!(0, flow::confirm_reset_create::new_confirm_reset_create).as_obj(),
/// def show_address_details(
/// *,
/// qr_title: str,
/// address: str,
/// case_sensitive: bool,
/// details_title: str,
/// account: str | None,
/// path: str | None,
/// xpubs: list[tuple[str, str]],
/// ) -> LayoutObj[UiResult]:
/// """Show address details - QR code, account, path, cosigner xpubs."""
Qstr::MP_QSTR_show_address_details => obj_fn_kw!(0, new_show_address_details).as_obj(),
/// def show_info_with_cancel( /// def show_info_with_cancel(
/// *, /// *,
/// title: str, /// title: str,
@ -2092,9 +2048,29 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Show single-line text in the middle of the screen.""" /// """Show single-line text in the middle of the screen."""
Qstr::MP_QSTR_show_wait_text => obj_fn_1!(new_show_wait_text).as_obj(), Qstr::MP_QSTR_show_wait_text => obj_fn_1!(new_show_wait_text).as_obj(),
/// def flow_get_address() -> LayoutObj[UiResult]: /// def flow_get_address(
/// *,
/// address: str | bytes,
/// description: str | None,
/// extra: str | None,
/// chunkify: bool,
/// address_qr: str | None,
/// case_sensitive: bool,
/// account: str | None,
/// path: str | None,
/// xpubs: list[tuple[str, str]],
/// ) -> LayoutObj[UiResult]:
/// """Get address / receive funds.""" /// """Get address / receive funds."""
Qstr::MP_QSTR_flow_get_address => obj_fn_kw!(0, flow::get_address::new_get_address).as_obj(), Qstr::MP_QSTR_flow_get_address => obj_fn_kw!(0, flow::get_address::new_get_address).as_obj(),
/// def flow_warning_hi_prio(
/// *,
/// title: str,
/// description: str,
/// value: str = "",
/// ) -> LayoutObj[UiResult]:
/// """Warning modal with multiple steps to confirm."""
Qstr::MP_QSTR_flow_warning_hi_prio => obj_fn_kw!(0, flow::warning_hi_prio::new_warning_hi_prio).as_obj(),
}; };
#[cfg(test)] #[cfg(test)]

View File

@ -215,6 +215,16 @@ pub const fn label_title_main() -> TextStyle {
) )
} }
pub const fn label_title_danger() -> TextStyle {
TextStyle::new(
Font::NORMAL,
ORANGE_LIGHT,
GREY_DARK,
GREY_LIGHT,
GREY_LIGHT,
)
}
pub const fn label_title_sub() -> TextStyle { pub const fn label_title_sub() -> TextStyle {
TextStyle::new(Font::SUB, GREY, GREY_DARK, GREY_LIGHT, GREY_LIGHT) TextStyle::new(Font::SUB, GREY, GREY_DARK, GREY_LIGHT, GREY_LIGHT)
} }
@ -355,9 +365,30 @@ pub const fn button_cancel() -> ButtonStyleSheet {
} }
} }
// TODO: delete
pub const fn button_danger() -> ButtonStyleSheet { pub const fn button_danger() -> ButtonStyleSheet {
button_cancel() ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: ORANGE_LIGHT,
button_color: BG,
icon_color: ORANGE_LIGHT,
background_color: BG,
},
active: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: ORANGE_LIGHT,
button_color: BG,
icon_color: ORANGE_LIGHT,
background_color: BG,
},
disabled: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: ORANGE_LIGHT,
button_color: BG,
icon_color: ORANGE_LIGHT,
background_color: BG,
},
}
} }
pub const fn button_reset() -> ButtonStyleSheet { pub const fn button_reset() -> ButtonStyleSheet {
@ -591,6 +622,10 @@ pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, GREY_EXTRA_LIGHT, BG
.with_page_breaking(PageBreaking::CutAndInsertEllipsisBoth) .with_page_breaking(PageBreaking::CutAndInsertEllipsisBoth)
.with_ellipsis_icon(ICON_PAGE_NEXT, 0) .with_ellipsis_icon(ICON_PAGE_NEXT, 0)
.with_prev_page_icon(ICON_PAGE_PREV, 0); .with_prev_page_icon(ICON_PAGE_PREV, 0);
pub const TEXT_MONO_GREY_LIGHT: TextStyle = TextStyle {
text_color: GREY_LIGHT,
..TEXT_MONO
};
/// Makes sure that the displayed text (usually address) will get divided into /// Makes sure that the displayed text (usually address) will get divided into
/// smaller chunks. /// smaller chunks.
pub const TEXT_MONO_ADDRESS_CHUNKS: TextStyle = TEXT_MONO pub const TEXT_MONO_ADDRESS_CHUNKS: TextStyle = TEXT_MONO
@ -656,6 +691,7 @@ pub const MNEMONIC_BUTTON_HEIGHT: i16 = 52;
pub const RESULT_PADDING: i16 = 6; pub const RESULT_PADDING: i16 = 6;
pub const RESULT_FOOTER_START: i16 = 171; pub const RESULT_FOOTER_START: i16 = 171;
pub const RESULT_FOOTER_HEIGHT: i16 = 62; pub const RESULT_FOOTER_HEIGHT: i16 = 62;
pub const DETAILS_SPACING: i16 = 8;
// checklist settings // checklist settings
pub const CHECKLIST_CHECK_WIDTH: i16 = 16; pub const CHECKLIST_CHECK_WIDTH: i16 = 16;

View File

@ -52,7 +52,7 @@ impl AddressDetails {
if let Some(path) = path { if let Some(path) = path {
para.add(Paragraph::new( para.add(Paragraph::new(
&theme::TEXT_BOLD, &theme::TEXT_BOLD,
TR::address_details__derivation_path, TR::address_details__derivation_path_colon,
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, path)); para.add(Paragraph::new(&theme::TEXT_MONO, path));
} }

View File

@ -47,7 +47,7 @@ impl AddressDetails {
if let Some(p) = path { if let Some(p) = path {
para.add(Paragraph::new( para.add(Paragraph::new(
&theme::TEXT_NORMAL, &theme::TEXT_NORMAL,
TR::address_details__derivation_path, TR::address_details__derivation_path_colon,
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, p)); para.add(Paragraph::new(&theme::TEXT_MONO, p));
} }

View File

@ -155,20 +155,6 @@ def flow_confirm_reset_create() -> LayoutObj[UiResult]:
"""Confirm TOS before creating a wallet and have a user hold to confirm creation.""" """Confirm TOS before creating a wallet and have a user hold to confirm creation."""
# rust/src/ui/model_mercury/layout.rs
def show_address_details(
*,
qr_title: str,
address: str,
case_sensitive: bool,
details_title: str,
account: str | None,
path: str | None,
xpubs: list[tuple[str, str]],
) -> LayoutObj[UiResult]:
"""Show address details - QR code, account, path, cosigner xpubs."""
# rust/src/ui/model_mercury/layout.rs # rust/src/ui/model_mercury/layout.rs
def show_info_with_cancel( def show_info_with_cancel(
*, *,
@ -529,8 +515,29 @@ def show_wait_text(message: str, /) -> LayoutObj[None]:
# rust/src/ui/model_mercury/layout.rs # rust/src/ui/model_mercury/layout.rs
def flow_get_address() -> LayoutObj[UiResult]: def flow_get_address(
*,
address: str | bytes,
description: str | None,
extra: str | None,
chunkify: bool,
address_qr: str | None,
case_sensitive: bool,
account: str | None,
path: str | None,
xpubs: list[tuple[str, str]],
) -> LayoutObj[UiResult]:
"""Get address / receive funds.""" """Get address / receive funds."""
# rust/src/ui/model_mercury/layout.rs
def flow_warning_hi_prio(
*,
title: str,
description: str,
value: str = "",
) -> LayoutObj[UiResult]:
"""Warning modal with multiple steps to confirm."""
CONFIRMED: UiResult CONFIRMED: UiResult
CANCELLED: UiResult CANCELLED: UiResult
INFO: UiResult INFO: UiResult

View File

@ -8,11 +8,17 @@ class TR:
addr_mismatch__support_url: str = "trezor.io/support" addr_mismatch__support_url: str = "trezor.io/support"
addr_mismatch__wrong_derivation_path: str = "Wrong derivation path for selected account." addr_mismatch__wrong_derivation_path: str = "Wrong derivation path for selected account."
addr_mismatch__xpub_mismatch: str = "XPUB mismatch?" addr_mismatch__xpub_mismatch: str = "XPUB mismatch?"
address__cancel_contact_support: str = "If receive address doesn't match, contact Trezor Support at trezor.io/support."
address__cancel_receive: str = "Cancel receive"
address__confirmed: str = "Receive address confirmed"
address__public_key: str = "Public key" address__public_key: str = "Public key"
address__qr_code: str = "QR code"
address__title_cosigner: str = "Cosigner" address__title_cosigner: str = "Cosigner"
address__title_receive_address: str = "Receive address" address__title_receive_address: str = "Receive address"
address__title_yours: str = "Yours" address__title_yours: str = "Yours"
address_details__derivation_path: str = "Derivation path:" address_details__account_info: str = "Account info"
address_details__derivation_path: str = "Derivation path"
address_details__derivation_path_colon: str = "Derivation path:"
address_details__title_receive_address: str = "Receive address" address_details__title_receive_address: str = "Receive address"
address_details__title_receiving_to: str = "Receiving to" address_details__title_receiving_to: str = "Receiving to"
authenticate__confirm_template: str = "Allow connected computer to confirm your {0} is genuine?" authenticate__confirm_template: str = "Allow connected computer to confirm your {0} is genuine?"
@ -356,6 +362,7 @@ class TR:
inputs__return: str = "RETURN" inputs__return: str = "RETURN"
inputs__show: str = "SHOW" inputs__show: str = "SHOW"
inputs__space: str = "SPACE" inputs__space: str = "SPACE"
instructions__continue_in_app: str = "Continue in the app"
instructions__hold_to_confirm: str = "Hold to confirm" instructions__hold_to_confirm: str = "Hold to confirm"
instructions__swipe_up: str = "Swipe up" instructions__swipe_up: str = "Swipe up"
instructions__tap_to_confirm: str = "Tap to confirm" instructions__tap_to_confirm: str = "Tap to confirm"
@ -828,6 +835,7 @@ class TR:
words__array_of: str = "Array of" words__array_of: str = "Array of"
words__blockhash: str = "Blockhash" words__blockhash: str = "Blockhash"
words__buying: str = "Buying" words__buying: str = "Buying"
words__cancel_and_exit: str = "Cancel and exit"
words__confirm: str = "Confirm" words__confirm: str = "Confirm"
words__confirm_fee: str = "Confirm fee" words__confirm_fee: str = "Confirm fee"
words__contains: str = "Contains" words__contains: str = "Contains"

View File

@ -31,10 +31,9 @@ def _get_xpubs(
@with_keychain @with_keychain
async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Address: async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Address:
from trezor import TR
from trezor.enums import InputScriptType from trezor.enums import InputScriptType
from trezor.messages import Address from trezor.messages import Address
from trezor.ui.layouts import show_address, show_warning from trezor.ui.layouts import confirm_multisig_warning, show_address
from apps.common.address_mac import get_address_mac from apps.common.address_mac import get_address_mac
from apps.common.paths import address_n_to_str, validate_path from apps.common.paths import address_n_to_str, validate_path
@ -104,11 +103,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
pubnodes = [hd.node for hd in multisig.pubkeys] pubnodes = [hd.node for hd in multisig.pubkeys]
multisig_index = multisig_pubkey_index(multisig, node.public_key()) multisig_index = multisig_pubkey_index(multisig, node.public_key())
await show_warning( await confirm_multisig_warning()
"warning_multisig",
TR.send__receiving_to_multisig,
TR.words__continue_anyway,
)
await show_address( await show_address(
address_short, address_short,

View File

@ -279,17 +279,6 @@ async def confirm_action(
) )
async def flow_demo() -> None:
await raise_if_not_confirmed(
interact(
RustLayout(trezorui2.flow_get_address()),
"get_address",
BR_TYPE_OTHER,
),
ActionCancelled,
)
async def confirm_single( async def confirm_single(
br_type: str, br_type: str,
title: str, title: str,
@ -362,7 +351,7 @@ async def confirm_path_warning(
path: str, path: str,
path_type: str | None = None, path_type: str | None = None,
) -> None: ) -> None:
title = ( description = (
TR.addr_mismatch__wrong_derivation_path TR.addr_mismatch__wrong_derivation_path
if not path_type if not path_type
else f"{TR.words__unknown} {path_type.lower()}." else f"{TR.words__unknown} {path_type.lower()}."
@ -370,11 +359,8 @@ async def confirm_path_warning(
await raise_if_not_confirmed( await raise_if_not_confirmed(
interact( interact(
RustLayout( RustLayout(
trezorui2.show_warning( trezorui2.flow_warning_hi_prio(
title=title, title=f"{TR.words__warning}!", description=description, value=path
value=path,
description=TR.words__continue_anyway,
button=TR.buttons__continue,
) )
), ),
"path_warning", "path_warning",
@ -383,6 +369,21 @@ async def confirm_path_warning(
) )
async def confirm_multisig_warning() -> None:
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.flow_warning_hi_prio(
title=f"{TR.words__important}!",
description=TR.send__receiving_to_multisig,
)
),
"warning_multisig",
br_code=ButtonRequestType.Warning,
)
)
async def confirm_homescreen( async def confirm_homescreen(
image: bytes, image: bytes,
) -> None: ) -> None:
@ -417,78 +418,35 @@ async def show_address(
br_code: ButtonRequestType = ButtonRequestType.Address, br_code: ButtonRequestType = ButtonRequestType.Address,
chunkify: bool = False, chunkify: bool = False,
) -> None: ) -> None:
mismatch_title = mismatch_title or TR.addr_mismatch__mismatch # def_arg def xpub_title(i: int) -> str:
send_button_request = True result = f"Multisig XPUB #{i + 1}\n"
result += (
f"({TR.address__title_yours.lower()})"
if i == multisig_index
else f"({TR.address__title_cosigner.lower()})"
)
return result
if title is None: await raise_if_not_confirmed(
title = TR.address__title_receive_address interact(
if multisig_index is not None: RustLayout(
title = f"{title}\n(MULTISIG)" trezorui2.flow_get_address(
details_title = TR.send__title_receiving_to address=address,
elif details_title is None: description=network or "",
details_title = title extra=None,
chunkify=chunkify,
layout = RustLayout( address_qr=address if address_qr is None else address_qr,
trezorui2.confirm_address( case_sensitive=case_sensitive,
title=title, account=account,
data=address, path=path,
description=network or "", xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)],
extra=None, )
chunkify=chunkify, ),
br_type,
br_code,
) )
) )
while True:
if send_button_request:
send_button_request = False
await button_request(
br_type,
br_code,
pages=layout.page_count(),
)
layout.request_complete_repaint()
result = await ctx_wait(layout)
# User pressed right button.
if result is CONFIRMED:
break
# User pressed corner button or swiped left, go to address details.
elif result is INFO:
def xpub_title(i: int) -> str:
result = f"MULTISIG XPUB #{i + 1}\n"
result += (
f"({TR.address__title_yours})"
if i == multisig_index
else f"({TR.address__title_cosigner})"
)
return result
result = await ctx_wait(
RustLayout(
trezorui2.show_address_details(
qr_title=title,
address=address if address_qr is None else address_qr,
case_sensitive=case_sensitive,
details_title=details_title,
account=account,
path=path,
xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)],
)
)
)
assert result is CANCELLED
else:
result = await ctx_wait(
RustLayout(trezorui2.show_mismatch(title=mismatch_title))
)
assert result in (CONFIRMED, CANCELLED)
# Right button aborts action, left goes back to showing address.
if result is CONFIRMED:
raise ActionCancelled
def show_pubkey( def show_pubkey(
pubkey: str, pubkey: str,
@ -1305,7 +1263,7 @@ async def confirm_signverify(
items: list[tuple[str, str]] = [] items: list[tuple[str, str]] = []
if account is not None: if account is not None:
items.append((f"{TR.words__account}:", account)) items.append((TR.words__account, account))
if path is not None: if path is not None:
items.append((TR.address_details__derivation_path, path)) items.append((TR.address_details__derivation_path, path))
items.append( items.append(

View File

@ -492,6 +492,14 @@ def confirm_path_warning(
) )
def confirm_multisig_warning() -> Awaitable[None]:
return show_warning(
"warning_multisig",
TR.send__receiving_to_multisig,
TR.words__continue_anyway,
)
def confirm_homescreen(image: bytes) -> Awaitable[None]: def confirm_homescreen(image: bytes) -> Awaitable[None]:
return raise_if_not_confirmed( return raise_if_not_confirmed(
interact( interact(

View File

@ -431,6 +431,14 @@ def confirm_path_warning(path: str, path_type: str | None = None) -> Awaitable[N
) )
def confirm_multisig_warning() -> Awaitable[None]:
return show_warning(
"warning_multisig",
TR.send__receiving_to_multisig,
TR.words__continue_anyway,
)
def confirm_homescreen(image: bytes) -> Awaitable[None]: def confirm_homescreen(image: bytes) -> Awaitable[None]:
return raise_if_not_confirmed( return raise_if_not_confirmed(
interact( interact(
@ -1349,7 +1357,7 @@ async def confirm_signverify(
if account is not None: if account is not None:
items.append((f"{TR.words__account}:", account)) items.append((f"{TR.words__account}:", account))
if path is not None: if path is not None:
items.append((TR.address_details__derivation_path, path)) items.append((TR.address_details__derivation_path_colon, path))
items.append( items.append(
( (
TR.sign_message__message_size, TR.sign_message__message_size,

View File

@ -46,7 +46,8 @@
"address__title_cosigner": "Další podepisující", "address__title_cosigner": "Další podepisující",
"address__title_receive_address": "Přijímací adresa", "address__title_receive_address": "Přijímací adresa",
"address__title_yours": "Vaše", "address__title_yours": "Vaše",
"address_details__derivation_path": "Derivační cesta:", "address_details__derivation_path": "Derivační cesta",
"address_details__derivation_path_colon": "Derivační cesta:",
"address_details__title_receive_address": "Přijímací adresa", "address_details__title_receive_address": "Přijímací adresa",
"address_details__title_receiving_to": "Příjem", "address_details__title_receiving_to": "Příjem",
"authenticate__confirm_template": "Povolit připojenému počítači potvrdit pravost vašeho {0}?", "authenticate__confirm_template": "Povolit připojenému počítači potvrdit pravost vašeho {0}?",

View File

@ -46,7 +46,8 @@
"address__title_cosigner": "Mitunterzeich.", "address__title_cosigner": "Mitunterzeich.",
"address__title_receive_address": "Empfäng-adresse", "address__title_receive_address": "Empfäng-adresse",
"address__title_yours": "Deiner", "address__title_yours": "Deiner",
"address_details__derivation_path": "Ableitungspfad:", "address_details__derivation_path": "Ableitungspfad",
"address_details__derivation_path_colon": "Ableitungspfad:",
"address_details__title_receive_address": "Empfäng-adresse", "address_details__title_receive_address": "Empfäng-adresse",
"address_details__title_receiving_to": "Empfänger", "address_details__title_receiving_to": "Empfänger",
"authenticate__confirm_template": "Darf verbundener Computer bestätigen, dass {0} echt ist?", "authenticate__confirm_template": "Darf verbundener Computer bestätigen, dass {0} echt ist?",

View File

@ -10,11 +10,17 @@
"addr_mismatch__support_url": "trezor.io/support", "addr_mismatch__support_url": "trezor.io/support",
"addr_mismatch__wrong_derivation_path": "Wrong derivation path for selected account.", "addr_mismatch__wrong_derivation_path": "Wrong derivation path for selected account.",
"addr_mismatch__xpub_mismatch": "XPUB mismatch?", "addr_mismatch__xpub_mismatch": "XPUB mismatch?",
"address__cancel_contact_support": "If receive address doesn't match, contact Trezor Support at trezor.io/support.",
"address__cancel_receive": "Cancel receive",
"address__confirmed": "Receive address confirmed",
"address__public_key": "Public key", "address__public_key": "Public key",
"address__qr_code": "QR code",
"address__title_cosigner": "Cosigner", "address__title_cosigner": "Cosigner",
"address__title_receive_address": "Receive address", "address__title_receive_address": "Receive address",
"address__title_yours": "Yours", "address__title_yours": "Yours",
"address_details__derivation_path": "Derivation path:", "address_details__derivation_path": "Derivation path",
"address_details__derivation_path_colon": "Derivation path:",
"address_details__account_info": "Account info",
"address_details__title_receive_address": "Receive address", "address_details__title_receive_address": "Receive address",
"address_details__title_receiving_to": "Receiving to", "address_details__title_receiving_to": "Receiving to",
"authenticate__confirm_template": "Allow connected computer to confirm your {0} is genuine?", "authenticate__confirm_template": "Allow connected computer to confirm your {0} is genuine?",
@ -358,6 +364,7 @@
"inputs__previous": "PREVIOUS", "inputs__previous": "PREVIOUS",
"inputs__show": "SHOW", "inputs__show": "SHOW",
"inputs__space": "SPACE", "inputs__space": "SPACE",
"instructions__continue_in_app": "Continue in the app",
"instructions__swipe_up": "Swipe up", "instructions__swipe_up": "Swipe up",
"instructions__tap_to_confirm": "Tap to confirm", "instructions__tap_to_confirm": "Tap to confirm",
"instructions__hold_to_confirm": "Hold to confirm", "instructions__hold_to_confirm": "Hold to confirm",
@ -830,6 +837,7 @@
"words__array_of": "Array of", "words__array_of": "Array of",
"words__blockhash": "Blockhash", "words__blockhash": "Blockhash",
"words__buying": "Buying", "words__buying": "Buying",
"words__cancel_and_exit": "Cancel and exit",
"words__confirm": "Confirm", "words__confirm": "Confirm",
"words__confirm_fee": "Confirm fee", "words__confirm_fee": "Confirm fee",
"words__contains": "Contains", "words__contains": "Contains",

View File

@ -46,7 +46,8 @@
"address__title_cosigner": "Cofirmante", "address__title_cosigner": "Cofirmante",
"address__title_receive_address": "Dirección destino", "address__title_receive_address": "Dirección destino",
"address__title_yours": "Tuyo", "address__title_yours": "Tuyo",
"address_details__derivation_path": "Ruta de derivación:", "address_details__derivation_path": "Ruta de derivación",
"address_details__derivation_path_colon": "Ruta de derivación:",
"address_details__title_receive_address": "Dirección destino", "address_details__title_receive_address": "Dirección destino",
"address_details__title_receiving_to": "Recibir en", "address_details__title_receiving_to": "Recibir en",
"authenticate__confirm_template": "¿Confirmar con el ordenador conectado que tu {0} es original?", "authenticate__confirm_template": "¿Confirmar con el ordenador conectado que tu {0} es original?",

View File

@ -46,7 +46,8 @@
"address__title_cosigner": "Cosignataire", "address__title_cosigner": "Cosignataire",
"address__title_receive_address": "Adr. de récep.", "address__title_receive_address": "Adr. de récep.",
"address__title_yours": "La vôtre", "address__title_yours": "La vôtre",
"address_details__derivation_path": "Chemin de dérivation :", "address_details__derivation_path": "Chemin de dérivation",
"address_details__derivation_path_colon": "Chemin de dérivation :",
"address_details__title_receive_address": "Adr. de récep.", "address_details__title_receive_address": "Adr. de récep.",
"address_details__title_receiving_to": "Récep. sur", "address_details__title_receiving_to": "Récep. sur",
"authenticate__confirm_template": "Autoriser l'ord. connecté à conf. que votre {0} est auth. ?", "authenticate__confirm_template": "Autoriser l'ord. connecté à conf. que votre {0} est auth. ?",

View File

@ -9,7 +9,7 @@
"7": "address__title_cosigner", "7": "address__title_cosigner",
"8": "address__title_receive_address", "8": "address__title_receive_address",
"9": "address__title_yours", "9": "address__title_yours",
"10": "address_details__derivation_path", "10": "address_details__derivation_path_colon",
"11": "address_details__title_receive_address", "11": "address_details__title_receive_address",
"12": "address_details__title_receiving_to", "12": "address_details__title_receiving_to",
"13": "authenticate__confirm_template", "13": "authenticate__confirm_template",
@ -860,5 +860,13 @@
"858": "backup__create_backup_to_prevent_loss", "858": "backup__create_backup_to_prevent_loss",
"859": "reset__check_backup_instructions", "859": "reset__check_backup_instructions",
"860": "words__instructions", "860": "words__instructions",
"861": "words__not_recommended" "861": "words__not_recommended",
"862": "address_details__account_info",
"863": "address__cancel_contact_support",
"864": "address__cancel_receive",
"865": "address__qr_code",
"866": "address_details__derivation_path",
"867": "instructions__continue_in_app",
"868": "words__cancel_and_exit",
"869": "address__confirmed"
} }

View File

@ -1,8 +1,8 @@
{ {
"current": { "current": {
"merkle_root": "5261bdad43d4e3cda9263ec6ce080218a0d897e02307ca8cafa2525d6a8d3d6b", "merkle_root": "d2a00bb90ebc87448eb0786432129db7c4e67316de7c491bf854d8429d2db9b8",
"datetime": "2024-05-17T10:18:14.246905", "datetime": "2024-05-17T10:29:37.039056",
"commit": "58db509b926fd99b3a36eb3bd550945bf1a139c4" "commit": "b3379e14e0658ab2327bffdfff5227f6079c8f74"
}, },
"history": [ "history": [
{ {

View File

@ -434,7 +434,7 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
layout = self.debug.swipe_left(wait=True) layout = self.debug.swipe_left(wait=True)
# address details # address details
assert "Multisig 2 of 3" in layout.screen_content() assert "Multisig 2 of 3" in layout.screen_content()
TR.assert_in(layout.screen_content(), "address_details__derivation_path") TR.assert_in(layout.screen_content(), "address_details__derivation_path_colon")
# Three xpub pages with the same testing logic # Three xpub pages with the same testing logic
for xpub_num in range(3): for xpub_num in range(3):
@ -508,7 +508,7 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
layout = self.debug.swipe_left(wait=True) layout = self.debug.swipe_left(wait=True)
# address details # address details
assert "Multisig 2 of 3" in layout.screen_content() assert "Multisig 2 of 3" in layout.screen_content()
TR.assert_in(layout.screen_content(), "address_details__derivation_path") TR.assert_in(layout.screen_content(), "address_details__derivation_path_colon")
# Three xpub pages with the same testing logic # Three xpub pages with the same testing logic
for xpub_num in range(3): for xpub_num in range(3):