1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-19 21:08:07 +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__xpub_mismatch;
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__qr_code;
MP_QSTR_address__title_cosigner;
MP_QSTR_address__title_receive_address;
MP_QSTR_address__title_yours;
MP_QSTR_address_details__account_info;
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_receiving_to;
MP_QSTR_address_label;
MP_QSTR_address_qr;
MP_QSTR_address_title;
MP_QSTR_allow_cancel;
MP_QSTR_altcoin_tx_summary;
@ -210,6 +217,7 @@ static void _librust_qstrs(void) {
MP_QSTR_flow_get_address;
MP_QSTR_flow_prompt_backup;
MP_QSTR_flow_show_share_words;
MP_QSTR_flow_warning_hi_prio;
MP_QSTR_get_language;
MP_QSTR_hold;
MP_QSTR_hold_danger;
@ -237,6 +245,7 @@ static void _librust_qstrs(void) {
MP_QSTR_inputs__return;
MP_QSTR_inputs__show;
MP_QSTR_inputs__space;
MP_QSTR_instructions__continue_in_app;
MP_QSTR_instructions__hold_to_confirm;
MP_QSTR_instructions__swipe_up;
MP_QSTR_instructions__tap_to_confirm;
@ -623,6 +632,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__array_of;
MP_QSTR_words__blockhash;
MP_QSTR_words__buying;
MP_QSTR_words__cancel_and_exit;
MP_QSTR_words__confirm;
MP_QSTR_words__confirm_fee;
MP_QSTR_words__contains;

View File

@ -20,7 +20,7 @@ pub enum TranslatedString {
address__title_cosigner = 7, // "Cosigner"
address__title_receive_address = 8, // "Receive address"
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_receiving_to = 12, // "Receiving to"
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."
words__instructions = 860, // "Instructions"
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 {
@ -1275,7 +1283,7 @@ impl TranslatedString {
Self::address__title_cosigner => "Cosigner",
Self::address__title_receive_address => "Receive address",
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_receiving_to => "Receiving to",
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::words__instructions => "Instructions",
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_receive_address => Some(Self::address__title_receive_address),
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_receiving_to => Some(Self::address_details__title_receiving_to),
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_words__instructions => Some(Self::words__instructions),
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,
}
}

View File

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

View File

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

View File

@ -18,7 +18,7 @@ pub struct 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 {
text,
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)
}
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)
}
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)
}
pub fn top_aligned(mut self) -> Self {
pub const fn top_aligned(mut self) -> Self {
self.vertical = Alignment::Start;
self
}
pub fn vertically_centered(mut self) -> Self {
pub const fn vertically_centered(mut self) -> Self {
self.vertical = Alignment::Center;
self
}
pub fn bottom_aligned(mut self) -> Self {
pub const fn bottom_aligned(mut self) -> Self {
self.vertical = Alignment::End;
self
}
pub fn styled(mut self, style: TextStyle) -> Self {
pub const fn styled(mut self, style: TextStyle) -> Self {
self.layout.style = style;
self
}
@ -74,7 +74,7 @@ impl<'a> Label<'a> {
self.layout.bounds
}
pub fn alignment(&self) -> Alignment {
pub const fn alignment(&self) -> Alignment {
self.layout.align
}

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ impl<T> Frame<T>
where
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 {
title: Child::new(
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)
}
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)
}
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)
}
pub fn with_border(mut self, border: Insets) -> Self {
pub const fn with_border(mut self, border: Insets) -> Self {
self.border = border;
self
}
@ -130,6 +130,11 @@ where
self
}
pub fn with_danger(self) -> Self {
self.button_styled(theme::button_danger())
.title_styled(theme::label_title_danger())
}
pub fn inner(&self) -> &T {
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
/// content of a Frame and showing success (checkmark with a circle around).
#[derive(Clone)]
pub struct StatusScreen {
area: Rect,
icon: Icon,
@ -18,6 +19,7 @@ pub struct StatusScreen {
dismiss_type: DismissType,
}
#[derive(Clone)]
enum DismissType {
SwipeUp(Swipe),
Timeout(Timeout),
@ -62,6 +64,15 @@ impl StatusScreen {
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 {

View File

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

View File

@ -1,33 +1,41 @@
use crate::{
error,
micropython::{iter::IterBuf, qstr::Qstr},
strutil::TString,
translations::TR,
ui::{
component::{
image::BlendedImage,
text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, Qr, SwipeDirection, Timeout,
text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
ComponentExt, Qr, SwipeDirection,
},
flow::{
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow,
SwipePage,
},
layout::util::ConfirmBlob,
},
};
use super::super::{
component::{Frame, FrameMsg, IconDialog, VerticalMenu, VerticalMenuChoiceMsg},
component::{
AddressDetails, CancelInfoConfirmMsg, Frame, FrameMsg, PromptScreen, StatusScreen,
VerticalMenu, VerticalMenuChoiceMsg,
},
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)]
pub enum GetAddress {
Address,
Tap,
Confirmed,
Menu,
QrCode,
AccountInfo,
Cancel,
Success,
CancelTap,
}
impl FlowState for GetAddress {
@ -36,8 +44,9 @@ impl FlowState for GetAddress {
(GetAddress::Address, SwipeDirection::Left) => {
Decision::Goto(GetAddress::Menu, direction)
}
(GetAddress::Address, SwipeDirection::Up) => {
Decision::Goto(GetAddress::Success, direction)
(GetAddress::Address, SwipeDirection::Up) => Decision::Goto(GetAddress::Tap, direction),
(GetAddress::Tap, SwipeDirection::Down) => {
Decision::Goto(GetAddress::Address, direction)
}
(GetAddress::Menu, SwipeDirection::Right) => {
Decision::Goto(GetAddress::Address, direction)
@ -48,7 +57,15 @@ impl FlowState for GetAddress {
(GetAddress::AccountInfo, SwipeDirection::Right) => {
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,
}
}
@ -59,6 +76,12 @@ impl FlowState for GetAddress {
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)) => {
Decision::Goto(GetAddress::QrCode, SwipeDirection::Left)
}
@ -87,14 +110,19 @@ impl FlowState for GetAddress {
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,
}
}
}
use crate::{
micropython::{buffer::StrBuffer, map::Map, obj::Obj, util},
micropython::{map::Map, obj::Obj, util},
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 {
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()
.add(
Frame::left_aligned(
"Receive".into(),
SwipePage::vertical(Paragraphs::new(Paragraph::new(
&theme::TEXT_MONO,
StrBuffer::from(LONGSTRING),
))),
)
.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)),
)?;
.add(content_address)?
.add(content_tap)?
.add(content_confirmed)?
.add(content_menu)?
.add(content_qr)?
.add(content_account)?
.add(content_cancel_info)?
.add(content_cancel_tap)?;
let res = SwipeFlow::new(GetAddress::Address, store)?;
Ok(LayoutObj::new(res)?.into())
}

View File

@ -3,9 +3,11 @@ pub mod confirm_reset_recover;
pub mod get_address;
pub mod prompt_backup;
pub mod show_share_words;
pub mod warning_hi_prio;
pub use confirm_reset_create::ConfirmResetCreate;
pub use confirm_reset_recover::ConfirmResetRecover;
pub use get_address::GetAddress;
pub use prompt_backup::PromptBackup;
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) }
}
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
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 content = SwipeUpScreen::new(Paragraphs::new([Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
value,
)]));
let content = SwipeUpScreen::new(Paragraphs::new([
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description),
Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value),
]));
let obj = LayoutObj::new(
Frame::left_aligned(title, content)
.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."""
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(
/// *,
/// title: str,
@ -2092,9 +2048,29 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """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(),
/// 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."""
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)]

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 {
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 {
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 {
@ -591,6 +622,10 @@ pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, GREY_EXTRA_LIGHT, BG
.with_page_breaking(PageBreaking::CutAndInsertEllipsisBoth)
.with_ellipsis_icon(ICON_PAGE_NEXT, 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
/// smaller chunks.
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_FOOTER_START: i16 = 171;
pub const RESULT_FOOTER_HEIGHT: i16 = 62;
pub const DETAILS_SPACING: i16 = 8;
// checklist settings
pub const CHECKLIST_CHECK_WIDTH: i16 = 16;

View File

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

View File

@ -47,7 +47,7 @@ impl AddressDetails {
if let Some(p) = path {
para.add(Paragraph::new(
&theme::TEXT_NORMAL,
TR::address_details__derivation_path,
TR::address_details__derivation_path_colon,
));
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."""
# 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
def show_info_with_cancel(
*,
@ -529,8 +515,29 @@ def show_wait_text(message: str, /) -> LayoutObj[None]:
# 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."""
# 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
CANCELLED: UiResult
INFO: UiResult

View File

@ -8,11 +8,17 @@ class TR:
addr_mismatch__support_url: str = "trezor.io/support"
addr_mismatch__wrong_derivation_path: str = "Wrong derivation path for selected account."
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__qr_code: str = "QR code"
address__title_cosigner: str = "Cosigner"
address__title_receive_address: str = "Receive address"
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_receiving_to: str = "Receiving to"
authenticate__confirm_template: str = "Allow connected computer to confirm your {0} is genuine?"
@ -356,6 +362,7 @@ class TR:
inputs__return: str = "RETURN"
inputs__show: str = "SHOW"
inputs__space: str = "SPACE"
instructions__continue_in_app: str = "Continue in the app"
instructions__hold_to_confirm: str = "Hold to confirm"
instructions__swipe_up: str = "Swipe up"
instructions__tap_to_confirm: str = "Tap to confirm"
@ -828,6 +835,7 @@ class TR:
words__array_of: str = "Array of"
words__blockhash: str = "Blockhash"
words__buying: str = "Buying"
words__cancel_and_exit: str = "Cancel and exit"
words__confirm: str = "Confirm"
words__confirm_fee: str = "Confirm fee"
words__contains: str = "Contains"

View File

@ -31,10 +31,9 @@ def _get_xpubs(
@with_keychain
async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Address:
from trezor import TR
from trezor.enums import InputScriptType
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.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]
multisig_index = multisig_pubkey_index(multisig, node.public_key())
await show_warning(
"warning_multisig",
TR.send__receiving_to_multisig,
TR.words__continue_anyway,
)
await confirm_multisig_warning()
await show_address(
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(
br_type: str,
title: str,
@ -362,7 +351,7 @@ async def confirm_path_warning(
path: str,
path_type: str | None = None,
) -> None:
title = (
description = (
TR.addr_mismatch__wrong_derivation_path
if not path_type
else f"{TR.words__unknown} {path_type.lower()}."
@ -370,11 +359,8 @@ async def confirm_path_warning(
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.show_warning(
title=title,
value=path,
description=TR.words__continue_anyway,
button=TR.buttons__continue,
trezorui2.flow_warning_hi_prio(
title=f"{TR.words__warning}!", description=description, value=path
)
),
"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(
image: bytes,
) -> None:
@ -417,78 +418,35 @@ async def show_address(
br_code: ButtonRequestType = ButtonRequestType.Address,
chunkify: bool = False,
) -> None:
mismatch_title = mismatch_title or TR.addr_mismatch__mismatch # def_arg
send_button_request = True
def xpub_title(i: int) -> str:
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:
title = TR.address__title_receive_address
if multisig_index is not None:
title = f"{title}\n(MULTISIG)"
details_title = TR.send__title_receiving_to
elif details_title is None:
details_title = title
layout = RustLayout(
trezorui2.confirm_address(
title=title,
data=address,
description=network or "",
extra=None,
chunkify=chunkify,
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.flow_get_address(
address=address,
description=network or "",
extra=None,
chunkify=chunkify,
address_qr=address if address_qr is None else address_qr,
case_sensitive=case_sensitive,
account=account,
path=path,
xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)],
)
),
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(
pubkey: str,
@ -1305,7 +1263,7 @@ async def confirm_signverify(
items: list[tuple[str, str]] = []
if account is not None:
items.append((f"{TR.words__account}:", account))
items.append((TR.words__account, account))
if path is not None:
items.append((TR.address_details__derivation_path, path))
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]:
return raise_if_not_confirmed(
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]:
return raise_if_not_confirmed(
interact(
@ -1349,7 +1357,7 @@ async def confirm_signverify(
if account is not None:
items.append((f"{TR.words__account}:", account))
if path is not None:
items.append((TR.address_details__derivation_path, path))
items.append((TR.address_details__derivation_path_colon, path))
items.append(
(
TR.sign_message__message_size,

View File

@ -46,7 +46,8 @@
"address__title_cosigner": "Další podepisující",
"address__title_receive_address": "Přijímací adresa",
"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_receiving_to": "Příjem",
"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_receive_address": "Empfäng-adresse",
"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_receiving_to": "Empfänger",
"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__wrong_derivation_path": "Wrong derivation path for selected account.",
"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__qr_code": "QR code",
"address__title_cosigner": "Cosigner",
"address__title_receive_address": "Receive address",
"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_receiving_to": "Receiving to",
"authenticate__confirm_template": "Allow connected computer to confirm your {0} is genuine?",
@ -358,6 +364,7 @@
"inputs__previous": "PREVIOUS",
"inputs__show": "SHOW",
"inputs__space": "SPACE",
"instructions__continue_in_app": "Continue in the app",
"instructions__swipe_up": "Swipe up",
"instructions__tap_to_confirm": "Tap to confirm",
"instructions__hold_to_confirm": "Hold to confirm",
@ -830,6 +837,7 @@
"words__array_of": "Array of",
"words__blockhash": "Blockhash",
"words__buying": "Buying",
"words__cancel_and_exit": "Cancel and exit",
"words__confirm": "Confirm",
"words__confirm_fee": "Confirm fee",
"words__contains": "Contains",

View File

@ -46,7 +46,8 @@
"address__title_cosigner": "Cofirmante",
"address__title_receive_address": "Dirección destino",
"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_receiving_to": "Recibir en",
"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_receive_address": "Adr. de récep.",
"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_receiving_to": "Récep. sur",
"authenticate__confirm_template": "Autoriser l'ord. connecté à conf. que votre {0} est auth. ?",

View File

@ -9,7 +9,7 @@
"7": "address__title_cosigner",
"8": "address__title_receive_address",
"9": "address__title_yours",
"10": "address_details__derivation_path",
"10": "address_details__derivation_path_colon",
"11": "address_details__title_receive_address",
"12": "address_details__title_receiving_to",
"13": "authenticate__confirm_template",
@ -860,5 +860,13 @@
"858": "backup__create_backup_to_prevent_loss",
"859": "reset__check_backup_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": {
"merkle_root": "5261bdad43d4e3cda9263ec6ce080218a0d897e02307ca8cafa2525d6a8d3d6b",
"datetime": "2024-05-17T10:18:14.246905",
"commit": "58db509b926fd99b3a36eb3bd550945bf1a139c4"
"merkle_root": "d2a00bb90ebc87448eb0786432129db7c4e67316de7c491bf854d8429d2db9b8",
"datetime": "2024-05-17T10:29:37.039056",
"commit": "b3379e14e0658ab2327bffdfff5227f6079c8f74"
},
"history": [
{

View File

@ -434,7 +434,7 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
layout = self.debug.swipe_left(wait=True)
# address details
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
for xpub_num in range(3):
@ -508,7 +508,7 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
layout = self.debug.swipe_left(wait=True)
# address details
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
for xpub_num in range(3):