mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-16 17:42:02 +00:00
feat(core/ui): redesigned receive flow
[no changelog]
This commit is contained in:
parent
1b94a7cb7b
commit
4af5939a0b
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
core/assets/info-circle.png
Normal file
BIN
core/assets/info-circle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 266 B |
BIN
core/assets/octa-bang.png
Normal file
BIN
core/assets/octa-bang.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 468 B |
@ -21,6 +21,7 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_jpeg_test;
|
MP_QSTR_jpeg_test;
|
||||||
MP_QSTR_confirm_action;
|
MP_QSTR_confirm_action;
|
||||||
MP_QSTR_confirm_homescreen;
|
MP_QSTR_confirm_homescreen;
|
||||||
|
MP_QSTR_confirm_address;
|
||||||
MP_QSTR_confirm_blob;
|
MP_QSTR_confirm_blob;
|
||||||
MP_QSTR_confirm_properties;
|
MP_QSTR_confirm_properties;
|
||||||
MP_QSTR_confirm_coinjoin;
|
MP_QSTR_confirm_coinjoin;
|
||||||
@ -40,6 +41,7 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_show_success;
|
MP_QSTR_show_success;
|
||||||
MP_QSTR_show_warning;
|
MP_QSTR_show_warning;
|
||||||
MP_QSTR_show_info;
|
MP_QSTR_show_info;
|
||||||
|
MP_QSTR_show_mismatch;
|
||||||
MP_QSTR_show_simple;
|
MP_QSTR_show_simple;
|
||||||
MP_QSTR_request_number;
|
MP_QSTR_request_number;
|
||||||
MP_QSTR_request_pin;
|
MP_QSTR_request_pin;
|
||||||
@ -56,6 +58,7 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_show_remaining_shares;
|
MP_QSTR_show_remaining_shares;
|
||||||
MP_QSTR_show_share_words;
|
MP_QSTR_show_share_words;
|
||||||
MP_QSTR_show_progress;
|
MP_QSTR_show_progress;
|
||||||
|
MP_QSTR_show_address_details;
|
||||||
|
|
||||||
MP_QSTR_attach_timer_fn;
|
MP_QSTR_attach_timer_fn;
|
||||||
MP_QSTR_touch_event;
|
MP_QSTR_touch_event;
|
||||||
@ -107,6 +110,7 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_time_ms;
|
MP_QSTR_time_ms;
|
||||||
MP_QSTR_app_name;
|
MP_QSTR_app_name;
|
||||||
MP_QSTR_icon_name;
|
MP_QSTR_icon_name;
|
||||||
|
MP_QSTR_account;
|
||||||
MP_QSTR_accounts;
|
MP_QSTR_accounts;
|
||||||
MP_QSTR_indeterminate;
|
MP_QSTR_indeterminate;
|
||||||
MP_QSTR_notification;
|
MP_QSTR_notification;
|
||||||
@ -114,4 +118,5 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_bootscreen;
|
MP_QSTR_bootscreen;
|
||||||
MP_QSTR_skip_first_paint;
|
MP_QSTR_skip_first_paint;
|
||||||
MP_QSTR_wrong_pin;
|
MP_QSTR_wrong_pin;
|
||||||
|
MP_QSTR_xpubs;
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,12 @@ impl BlendedImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: currently this function is used too rarely to justify writing special
|
||||||
|
// case for unblended image.
|
||||||
|
pub fn single(icon: Icon, color: Color, area_color: Color) -> Self {
|
||||||
|
Self::new(icon, icon, color, color, area_color)
|
||||||
|
}
|
||||||
|
|
||||||
fn paint_image(&self) {
|
fn paint_image(&self) {
|
||||||
display::icon_over_icon(
|
display::icon_over_icon(
|
||||||
None,
|
None,
|
||||||
|
@ -24,9 +24,9 @@ pub use map::Map;
|
|||||||
pub use marquee::Marquee;
|
pub use marquee::Marquee;
|
||||||
pub use maybe::Maybe;
|
pub use maybe::Maybe;
|
||||||
pub use pad::Pad;
|
pub use pad::Pad;
|
||||||
pub use paginated::{PageMsg, Paginate};
|
pub use paginated::{AuxPageMsg, PageMsg, Paginate};
|
||||||
pub use painter::Painter;
|
pub use painter::Painter;
|
||||||
pub use placed::{FixedHeightBar, GridPlaced};
|
pub use placed::{FixedHeightBar, Floating, GridPlaced, VSplit};
|
||||||
pub use qr_code::Qr;
|
pub use qr_code::Qr;
|
||||||
pub use text::{
|
pub use text::{
|
||||||
formatted::FormattedText,
|
formatted::FormattedText,
|
||||||
|
@ -3,6 +3,15 @@ use crate::ui::component::{
|
|||||||
FormattedText,
|
FormattedText,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub enum AuxPageMsg {
|
||||||
|
/// Page component was instantiated with BACK button on every page and it
|
||||||
|
/// was pressed.
|
||||||
|
GoBack,
|
||||||
|
|
||||||
|
/// Page component was configured to react to swipes and user swiped left.
|
||||||
|
SwipeLeft,
|
||||||
|
}
|
||||||
|
|
||||||
/// Common message type for pagination components.
|
/// Common message type for pagination components.
|
||||||
pub enum PageMsg<T, U> {
|
pub enum PageMsg<T, U> {
|
||||||
/// Pass-through from paged component.
|
/// Pass-through from paged component.
|
||||||
@ -12,9 +21,8 @@ pub enum PageMsg<T, U> {
|
|||||||
/// "OK" and "Cancel" buttons.
|
/// "OK" and "Cancel" buttons.
|
||||||
Controls(U),
|
Controls(U),
|
||||||
|
|
||||||
/// Page component was instantiated with BACK button on every page and it
|
/// Auxilliary events used by exotic pages on touchscreens.
|
||||||
/// was pressed.
|
Aux(AuxPageMsg),
|
||||||
GoBack,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Paginate {
|
pub trait Paginate {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Component, Event, EventCtx},
|
component::{Component, Event, EventCtx},
|
||||||
geometry::{Grid, GridCellSpan, Rect},
|
geometry::{Alignment, Alignment2D, Grid, GridCellSpan, Insets, Offset, Rect, TOP_RIGHT},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct GridPlaced<T> {
|
pub struct GridPlaced<T> {
|
||||||
@ -121,3 +121,135 @@ where
|
|||||||
d.close();
|
d.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Floating<T> {
|
||||||
|
inner: T,
|
||||||
|
size: Offset,
|
||||||
|
border: Offset,
|
||||||
|
align: Alignment2D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Floating<T> {
|
||||||
|
pub const fn new(size: Offset, border: Offset, align: Alignment2D, inner: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
size,
|
||||||
|
border,
|
||||||
|
align,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn top_right(side: i16, border: i16, inner: T) -> Self {
|
||||||
|
let size = Offset::uniform(side);
|
||||||
|
let border = Offset::uniform(border);
|
||||||
|
Self::new(size, border, TOP_RIGHT, inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Component for Floating<T>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
{
|
||||||
|
type Msg = T::Msg;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let mut border = self.border;
|
||||||
|
let area = match self.align.0 {
|
||||||
|
Alignment::Start => bounds.split_left(self.size.x).0,
|
||||||
|
Alignment::Center => panic!("alignment not supported"),
|
||||||
|
Alignment::End => {
|
||||||
|
border.x = -border.x;
|
||||||
|
bounds.split_right(self.size.x).1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let area = match self.align.1 {
|
||||||
|
Alignment::Start => area.split_top(self.size.y).0,
|
||||||
|
Alignment::Center => panic!("alignment not supported"),
|
||||||
|
Alignment::End => {
|
||||||
|
border.y = -border.y;
|
||||||
|
area.split_bottom(self.size.y).1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.inner.place(area.translate(border))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
self.inner.event(ctx, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.inner.paint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for Floating<T>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
T: crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
|
||||||
|
d.open("Floating");
|
||||||
|
d.field("inner", &self.inner);
|
||||||
|
d.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VSplit<T, U> {
|
||||||
|
first: T,
|
||||||
|
second: U,
|
||||||
|
width: i16,
|
||||||
|
spacing: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> VSplit<T, U> {
|
||||||
|
pub const fn new(width: i16, spacing: i16, first: T, second: U) -> Self {
|
||||||
|
Self {
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
width,
|
||||||
|
spacing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, T, U> Component for VSplit<T, U>
|
||||||
|
where
|
||||||
|
T: Component<Msg = M>,
|
||||||
|
U: Component<Msg = M>,
|
||||||
|
{
|
||||||
|
type Msg = M;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let (left, right) = bounds.split_left(self.width);
|
||||||
|
let right = right.inset(Insets::left(self.spacing));
|
||||||
|
self.first.place(left);
|
||||||
|
self.second.place(right);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
self.first
|
||||||
|
.event(ctx, event)
|
||||||
|
.or_else(|| self.second.event(ctx, event))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.first.paint();
|
||||||
|
self.second.paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T, U> crate::trace::Trace for VSplit<T, U>
|
||||||
|
where
|
||||||
|
T: Component + crate::trace::Trace,
|
||||||
|
U: Component + crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
|
||||||
|
d.open("VSplit");
|
||||||
|
d.field("first", &self.first);
|
||||||
|
d.field("second", &self.second);
|
||||||
|
d.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ where
|
|||||||
PageMsg::Content(_) => Err(Error::TypeError),
|
PageMsg::Content(_) => Err(Error::TypeError),
|
||||||
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
|
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
|
||||||
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
|
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
|
||||||
PageMsg::GoBack => unreachable!(),
|
PageMsg::Aux(_) => Err(Error::TypeError),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
158
core/embed/rust/src/ui/model_tt/component/address_details.rs
Normal file
158
core/embed/rust/src/ui/model_tt/component/address_details.rs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
use heapless::Vec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::Error,
|
||||||
|
ui::{
|
||||||
|
component::{
|
||||||
|
text::paragraphs::{
|
||||||
|
Paragraph, ParagraphSource, ParagraphStrType, ParagraphVecShort, Paragraphs, VecExt,
|
||||||
|
},
|
||||||
|
Component, Event, EventCtx, Never, Paginate, Qr,
|
||||||
|
},
|
||||||
|
geometry::Rect,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{theme, Frame};
|
||||||
|
|
||||||
|
pub struct AddressDetails<T> {
|
||||||
|
qr_code: Frame<Qr, T>,
|
||||||
|
details: Frame<Paragraphs<ParagraphVecShort<T>>, T>,
|
||||||
|
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>,
|
||||||
|
xpubs: Vec<(T, T), 16>,
|
||||||
|
current_page: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AddressDetails<T>
|
||||||
|
where
|
||||||
|
T: ParagraphStrType + From<&'static str>,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
qr_address: T,
|
||||||
|
case_sensitive: bool,
|
||||||
|
account: Option<T>,
|
||||||
|
path: Option<T>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let mut para = ParagraphVecShort::new();
|
||||||
|
if let Some(a) = account {
|
||||||
|
para.add(Paragraph::new(&theme::TEXT_NORMAL, "Account:".into()));
|
||||||
|
para.add(Paragraph::new(&theme::TEXT_MONO, a));
|
||||||
|
}
|
||||||
|
if let Some(p) = path {
|
||||||
|
para.add(Paragraph::new(
|
||||||
|
&theme::TEXT_NORMAL,
|
||||||
|
"Derivation path:".into(),
|
||||||
|
));
|
||||||
|
para.add(Paragraph::new(&theme::TEXT_MONO, p));
|
||||||
|
}
|
||||||
|
let result = Self {
|
||||||
|
qr_code: Frame::left_aligned(
|
||||||
|
theme::label_title(),
|
||||||
|
"RECEIVE ADDRESS".into(),
|
||||||
|
Qr::new(qr_address, case_sensitive)?.with_border(7),
|
||||||
|
)
|
||||||
|
.with_border(theme::borders_horizontal_scroll()),
|
||||||
|
details: Frame::left_aligned(
|
||||||
|
theme::label_title(),
|
||||||
|
"RECEIVING TO".into(),
|
||||||
|
para.into_paragraphs(),
|
||||||
|
)
|
||||||
|
.with_border(theme::borders_horizontal_scroll()),
|
||||||
|
xpub_view: Frame::left_aligned(
|
||||||
|
theme::label_title(),
|
||||||
|
" \n ".into(),
|
||||||
|
Paragraph::new(&theme::TEXT_XPUB, "".into()).into_paragraphs(),
|
||||||
|
)
|
||||||
|
.with_border(theme::borders_horizontal_scroll()),
|
||||||
|
xpubs: Vec::new(),
|
||||||
|
current_page: 0,
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_xpub(&mut self, title: T, xpub: T) -> Result<(), Error> {
|
||||||
|
self.xpubs
|
||||||
|
.push((title, xpub))
|
||||||
|
.map_err(|_| Error::OutOfRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Paginate for AddressDetails<T>
|
||||||
|
where
|
||||||
|
T: ParagraphStrType + Clone,
|
||||||
|
{
|
||||||
|
fn page_count(&mut self) -> usize {
|
||||||
|
2 + self.xpubs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_page(&mut self, to_page: usize) {
|
||||||
|
self.current_page = to_page;
|
||||||
|
if to_page > 1 {
|
||||||
|
let i = to_page - 2;
|
||||||
|
// Context is needed for updating child so that it can request repaint. In this
|
||||||
|
// case the parent component that handles paging always requests complete
|
||||||
|
// repaint after page change so we can use a dummy context here.
|
||||||
|
let mut dummy_ctx = EventCtx::new();
|
||||||
|
self.xpub_view
|
||||||
|
.update_title(&mut dummy_ctx, self.xpubs[i].0.clone());
|
||||||
|
self.xpub_view.update_content(&mut dummy_ctx, |p| {
|
||||||
|
p.inner_mut().update(self.xpubs[i].1.clone());
|
||||||
|
p.change_page(0)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Component for AddressDetails<T>
|
||||||
|
where
|
||||||
|
T: ParagraphStrType,
|
||||||
|
{
|
||||||
|
type Msg = Never;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
self.qr_code.place(bounds);
|
||||||
|
self.details.place(bounds);
|
||||||
|
self.xpub_view.place(bounds);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
match self.current_page {
|
||||||
|
0 => self.qr_code.event(ctx, event),
|
||||||
|
1 => self.details.event(ctx, event),
|
||||||
|
_ => self.xpub_view.event(ctx, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
match self.current_page {
|
||||||
|
0 => self.qr_code.paint(),
|
||||||
|
1 => self.details.paint(),
|
||||||
|
_ => self.xpub_view.paint(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
match self.current_page {
|
||||||
|
0 => self.qr_code.bounds(sink),
|
||||||
|
1 => self.details.bounds(sink),
|
||||||
|
_ => self.xpub_view.bounds(sink),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for AddressDetails<T>
|
||||||
|
where
|
||||||
|
T: ParagraphStrType + crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("AddressDetails");
|
||||||
|
match self.current_page {
|
||||||
|
0 => self.qr_code.trace(t),
|
||||||
|
1 => self.details.trace(t),
|
||||||
|
_ => self.xpub_view.trace(t),
|
||||||
|
}
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,8 @@ use crate::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
Component, ComponentExt, Event, EventCtx, FixedHeightBar, GridPlaced, Map, TimerToken,
|
Component, ComponentExt, Event, EventCtx, FixedHeightBar, Floating, GridPlaced, Map,
|
||||||
|
Paginate, TimerToken, VSplit,
|
||||||
},
|
},
|
||||||
display::{self, toif::Icon, Color, Font},
|
display::{self, toif::Icon, Color, Font},
|
||||||
event::TouchEvent,
|
event::TouchEvent,
|
||||||
@ -34,7 +35,7 @@ impl<T> Button<T> {
|
|||||||
/// (positive).
|
/// (positive).
|
||||||
pub const BASELINE_OFFSET: i16 = -3;
|
pub const BASELINE_OFFSET: i16 = -3;
|
||||||
|
|
||||||
pub fn new(content: ButtonContent<T>) -> Self {
|
pub const fn new(content: ButtonContent<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
content,
|
content,
|
||||||
area: Rect::zero(),
|
area: Rect::zero(),
|
||||||
@ -46,19 +47,19 @@ impl<T> Button<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_text(text: T) -> Self {
|
pub const fn with_text(text: T) -> Self {
|
||||||
Self::new(ButtonContent::Text(text))
|
Self::new(ButtonContent::Text(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_icon(icon: Icon) -> Self {
|
pub const fn with_icon(icon: Icon) -> Self {
|
||||||
Self::new(ButtonContent::Icon(icon))
|
Self::new(ButtonContent::Icon(icon))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_icon_and_text(content: IconText) -> Self {
|
pub const fn with_icon_and_text(content: IconText) -> Self {
|
||||||
Self::new(ButtonContent::IconAndText(content))
|
Self::new(ButtonContent::IconAndText(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self {
|
pub const fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self {
|
||||||
Self::new(ButtonContent::IconBlend(bg, fg, fg_offset))
|
Self::new(ButtonContent::IconBlend(bg, fg, fg_offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,6 +426,29 @@ impl<T> Button<T> {
|
|||||||
Self::cancel_confirm(left, right, right_size_factor)
|
Self::cancel_confirm(left, right, right_size_factor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cancel_confirm_square(
|
||||||
|
left: Button<T>,
|
||||||
|
right: Button<T>,
|
||||||
|
) -> CancelConfirmSquare<
|
||||||
|
T,
|
||||||
|
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||||
|
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
theme::button_bar(VSplit::new(
|
||||||
|
theme::BUTTON_HEIGHT,
|
||||||
|
theme::BUTTON_SPACING,
|
||||||
|
left.map(|msg| {
|
||||||
|
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Cancelled)
|
||||||
|
}),
|
||||||
|
right.map(|msg| {
|
||||||
|
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cancel_info_confirm(
|
pub fn cancel_info_confirm(
|
||||||
confirm: T,
|
confirm: T,
|
||||||
info: T,
|
info: T,
|
||||||
@ -542,6 +566,9 @@ type CancelInfoConfirm<T, F0, F1, F2> = FixedHeightBar<(
|
|||||||
Map<GridPlaced<Button<T>>, F2>,
|
Map<GridPlaced<Button<T>>, F2>,
|
||||||
)>;
|
)>;
|
||||||
|
|
||||||
|
type CancelConfirmSquare<T, F0, F1> =
|
||||||
|
FixedHeightBar<VSplit<Map<Button<T>, F0>, Map<Button<T>, F1>>>;
|
||||||
|
|
||||||
pub enum CancelInfoConfirmMsg {
|
pub enum CancelInfoConfirmMsg {
|
||||||
Cancelled,
|
Cancelled,
|
||||||
Info,
|
Info,
|
||||||
@ -610,3 +637,87 @@ impl IconText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FloatingButton<T> {
|
||||||
|
inner: T,
|
||||||
|
button: Floating<Button<&'static str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FloatingButtonMsg<T> {
|
||||||
|
ButtonClicked,
|
||||||
|
Content(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FloatingButton<T>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
{
|
||||||
|
pub const fn top_right_corner(icon: Icon, inner: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
button: Floating::top_right(
|
||||||
|
theme::CORNER_BUTTON_SIDE,
|
||||||
|
theme::CORNER_BUTTON_SPACING,
|
||||||
|
Button::with_icon(icon),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &T {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Component for FloatingButton<T>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
{
|
||||||
|
type Msg = FloatingButtonMsg<T::Msg>;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
self.button.place(bounds);
|
||||||
|
self.inner.place(bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
|
||||||
|
return Some(FloatingButtonMsg::ButtonClicked);
|
||||||
|
}
|
||||||
|
self.inner.event(ctx, event).map(FloatingButtonMsg::Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.inner.paint();
|
||||||
|
self.button.paint();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
self.inner.bounds(sink);
|
||||||
|
self.button.bounds(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Paginate for FloatingButton<T>
|
||||||
|
where
|
||||||
|
T: Paginate,
|
||||||
|
{
|
||||||
|
fn page_count(&mut self) -> usize {
|
||||||
|
self.inner.page_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_page(&mut self, to_page: usize) {
|
||||||
|
self.inner.change_page(to_page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for FloatingButton<T>
|
||||||
|
where
|
||||||
|
T: Component + crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("FloatingButton");
|
||||||
|
t.field("inner", self.inner());
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{
|
component::{
|
||||||
image::BlendedImage,
|
image::BlendedImage,
|
||||||
text::paragraphs::{
|
text::{
|
||||||
Paragraph, ParagraphSource, ParagraphStrType, ParagraphVecShort, Paragraphs, VecExt,
|
paragraphs::{
|
||||||
|
Paragraph, ParagraphSource, ParagraphStrType, ParagraphVecShort, Paragraphs, VecExt,
|
||||||
|
},
|
||||||
|
TextStyle,
|
||||||
},
|
},
|
||||||
Child, Component, Event, EventCtx, Never,
|
Child, Component, Event, EventCtx, Never,
|
||||||
},
|
},
|
||||||
@ -116,15 +119,19 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_description(mut self, description: T) -> Self {
|
pub fn with_text(mut self, style: &'static TextStyle, text: T) -> Self {
|
||||||
if !description.as_ref().is_empty() {
|
if !text.as_ref().is_empty() {
|
||||||
self.paragraphs
|
self.paragraphs
|
||||||
.inner_mut()
|
.inner_mut()
|
||||||
.add(Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, description).centered());
|
.add(Paragraph::new(style, text).centered());
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_description(self, description: T) -> Self {
|
||||||
|
self.with_text(&theme::TEXT_NORMAL_OFF_WHITE, description)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_shares(lines: [T; 4], controls: U) -> Self {
|
pub fn new_shares(lines: [T; 4], controls: U) -> Self {
|
||||||
let [l0, l1, l2, l3] = lines;
|
let [l0, l1, l2, l3] = lines;
|
||||||
Self {
|
Self {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use super::theme;
|
use super::theme;
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{label::Label, text::TextStyle, Child, Component, Event, EventCtx},
|
component::{
|
||||||
|
base::ComponentExt, label::Label, text::TextStyle, Child, Component, Event, EventCtx,
|
||||||
|
},
|
||||||
display::{self, toif::Icon, Color},
|
display::{self, toif::Icon, Color},
|
||||||
geometry::{Alignment, Insets, Offset, Rect},
|
geometry::{Alignment, Insets, Offset, Rect},
|
||||||
util::icon_text_center,
|
util::icon_text_center,
|
||||||
@ -45,6 +47,23 @@ where
|
|||||||
pub fn inner(&self) -> &T {
|
pub fn inner(&self) -> &T {
|
||||||
self.content.inner()
|
self.content.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: U) {
|
||||||
|
self.title.mutate(ctx, |ctx, t| {
|
||||||
|
t.set_text(new_title);
|
||||||
|
t.request_complete_repaint(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_content<F>(&mut self, ctx: &mut EventCtx, update_fn: F)
|
||||||
|
where
|
||||||
|
F: Fn(&mut T),
|
||||||
|
{
|
||||||
|
self.content.mutate(ctx, |ctx, c| {
|
||||||
|
update_fn(c);
|
||||||
|
c.request_complete_repaint(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Component for Frame<T, U>
|
impl<T, U> Component for Frame<T, U>
|
||||||
|
144
core/embed/rust/src/ui/model_tt/component/horizontal_page.rs
Normal file
144
core/embed/rust/src/ui/model_tt/component/horizontal_page.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{
|
||||||
|
base::ComponentExt, AuxPageMsg, Component, Event, EventCtx, Never, Pad, PageMsg, Paginate,
|
||||||
|
},
|
||||||
|
display::{self, Color},
|
||||||
|
geometry::Rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{theme, ScrollBar, Swipe, SwipeDirection};
|
||||||
|
|
||||||
|
const SCROLLBAR_HEIGHT: i16 = 32;
|
||||||
|
|
||||||
|
pub struct HorizontalPage<T> {
|
||||||
|
content: T,
|
||||||
|
pad: Pad,
|
||||||
|
swipe: Swipe,
|
||||||
|
scrollbar: ScrollBar,
|
||||||
|
swipe_right_to_go_back: bool,
|
||||||
|
fade: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HorizontalPage<T>
|
||||||
|
where
|
||||||
|
T: Paginate,
|
||||||
|
T: Component,
|
||||||
|
{
|
||||||
|
pub fn new(content: T, background: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
content,
|
||||||
|
swipe: Swipe::new(),
|
||||||
|
pad: Pad::with_background(background),
|
||||||
|
scrollbar: ScrollBar::horizontal(),
|
||||||
|
swipe_right_to_go_back: false,
|
||||||
|
fade: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_swipe_right_to_go_back(mut self) -> Self {
|
||||||
|
self.swipe_right_to_go_back = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &T {
|
||||||
|
&self.content
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_swipe(&mut self) {
|
||||||
|
self.swipe.allow_left = self.scrollbar.has_next_page();
|
||||||
|
self.swipe.allow_right = self.scrollbar.has_previous_page() || self.swipe_right_to_go_back;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_page_change(&mut self, ctx: &mut EventCtx) {
|
||||||
|
// Adjust the swipe parameters according to the scrollbar.
|
||||||
|
self.setup_swipe();
|
||||||
|
|
||||||
|
// Change the page in the content, make sure it gets completely repainted and
|
||||||
|
// clear the background under it.
|
||||||
|
self.content.change_page(self.scrollbar.active_page);
|
||||||
|
self.content.request_complete_repaint(ctx);
|
||||||
|
self.pad.clear();
|
||||||
|
|
||||||
|
// Swipe has dimmed the screen, so fade back to normal backlight after the next
|
||||||
|
// paint.
|
||||||
|
self.fade = Some(theme::BACKLIGHT_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Component for HorizontalPage<T>
|
||||||
|
where
|
||||||
|
T: Paginate,
|
||||||
|
T: Component,
|
||||||
|
{
|
||||||
|
type Msg = PageMsg<T::Msg, Never>;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
self.swipe.place(bounds);
|
||||||
|
|
||||||
|
let (content, scrollbar) = bounds.split_bottom(SCROLLBAR_HEIGHT);
|
||||||
|
self.pad.place(content);
|
||||||
|
self.content.place(content);
|
||||||
|
self.scrollbar.place(scrollbar);
|
||||||
|
|
||||||
|
self.scrollbar
|
||||||
|
.set_count_and_active_page(self.content.page_count(), 0);
|
||||||
|
self.setup_swipe();
|
||||||
|
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
ctx.set_page_count(self.scrollbar.page_count);
|
||||||
|
if let Some(swipe) = self.swipe.event(ctx, event) {
|
||||||
|
match swipe {
|
||||||
|
SwipeDirection::Left => {
|
||||||
|
self.scrollbar.go_to_next_page();
|
||||||
|
self.on_page_change(ctx);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
SwipeDirection::Right => {
|
||||||
|
if self.swipe_right_to_go_back && self.scrollbar.active_page == 0 {
|
||||||
|
return Some(PageMsg::Aux(AuxPageMsg::GoBack));
|
||||||
|
}
|
||||||
|
self.scrollbar.go_to_previous_page();
|
||||||
|
self.on_page_change(ctx);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other directions.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.content.event(ctx, event).map(PageMsg::Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.pad.paint();
|
||||||
|
self.content.paint();
|
||||||
|
self.scrollbar.paint();
|
||||||
|
if let Some(val) = self.fade.take() {
|
||||||
|
// Note that this is blocking and takes some time.
|
||||||
|
display::fade_backlight(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
sink(self.pad.area);
|
||||||
|
self.scrollbar.bounds(sink);
|
||||||
|
self.content.bounds(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for HorizontalPage<T>
|
||||||
|
where
|
||||||
|
T: crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("HorizontalPage");
|
||||||
|
t.field("active_page", &self.scrollbar.active_page);
|
||||||
|
t.field("page_count", &self.scrollbar.page_count);
|
||||||
|
t.field("content", &self.content);
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
mod address_details;
|
||||||
mod button;
|
mod button;
|
||||||
mod dialog;
|
mod dialog;
|
||||||
mod fido;
|
mod fido;
|
||||||
@ -7,6 +8,7 @@ mod frame;
|
|||||||
mod hold_to_confirm;
|
mod hold_to_confirm;
|
||||||
#[cfg(feature = "dma2d")]
|
#[cfg(feature = "dma2d")]
|
||||||
mod homescreen;
|
mod homescreen;
|
||||||
|
mod horizontal_page;
|
||||||
mod keyboard;
|
mod keyboard;
|
||||||
mod loader;
|
mod loader;
|
||||||
mod number_input;
|
mod number_input;
|
||||||
@ -17,9 +19,10 @@ mod scroll;
|
|||||||
mod swipe;
|
mod swipe;
|
||||||
mod welcome_screen;
|
mod welcome_screen;
|
||||||
|
|
||||||
|
pub use address_details::AddressDetails;
|
||||||
pub use button::{
|
pub use button::{
|
||||||
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg,
|
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg,
|
||||||
CancelInfoConfirmMsg, IconText, SelectWordMsg,
|
CancelInfoConfirmMsg, FloatingButton, FloatingButtonMsg, IconText, SelectWordMsg,
|
||||||
};
|
};
|
||||||
pub use dialog::{Dialog, DialogMsg, IconDialog};
|
pub use dialog::{Dialog, DialogMsg, IconDialog};
|
||||||
pub use fido::{FidoConfirm, FidoMsg};
|
pub use fido::{FidoConfirm, FidoMsg};
|
||||||
@ -27,6 +30,7 @@ pub use frame::{Frame, NotificationFrame};
|
|||||||
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
|
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
|
||||||
#[cfg(feature = "dma2d")]
|
#[cfg(feature = "dma2d")]
|
||||||
pub use homescreen::{Homescreen, HomescreenMsg, Lockscreen};
|
pub use homescreen::{Homescreen, HomescreenMsg, Lockscreen};
|
||||||
|
pub use horizontal_page::HorizontalPage;
|
||||||
pub use keyboard::{
|
pub use keyboard::{
|
||||||
bip39::Bip39Input,
|
bip39::Bip39Input,
|
||||||
mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg},
|
mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg},
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{
|
component::{
|
||||||
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, FixedHeightBar, Label,
|
base::ComponentExt,
|
||||||
Pad, Paginate,
|
paginated::{AuxPageMsg, PageMsg},
|
||||||
|
Component, Event, EventCtx, FixedHeightBar, Label, Pad, Paginate,
|
||||||
},
|
},
|
||||||
display::{self, toif::Icon, Color},
|
display::{self, toif::Icon, Color},
|
||||||
geometry::{Insets, Rect},
|
geometry::{Insets, Rect},
|
||||||
@ -21,6 +22,7 @@ pub struct SwipePage<T, U> {
|
|||||||
scrollbar: ScrollBar,
|
scrollbar: ScrollBar,
|
||||||
hint: Label<&'static str>,
|
hint: Label<&'static str>,
|
||||||
button_back: Option<Button<&'static str>>,
|
button_back: Option<Button<&'static str>>,
|
||||||
|
swipe_left: bool,
|
||||||
fade: Option<u16>,
|
fade: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ where
|
|||||||
pad: Pad::with_background(background),
|
pad: Pad::with_background(background),
|
||||||
hint: Label::centered("SWIPE TO CONTINUE", theme::label_page_hint()),
|
hint: Label::centered("SWIPE TO CONTINUE", theme::label_page_hint()),
|
||||||
button_back: None,
|
button_back: None,
|
||||||
|
swipe_left: false,
|
||||||
fade: None,
|
fade: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,9 +51,15 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_swipe_left(mut self) -> Self {
|
||||||
|
self.swipe_left = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_swipe(&mut self) {
|
fn setup_swipe(&mut self) {
|
||||||
self.swipe.allow_up = self.scrollbar.has_next_page();
|
self.swipe.allow_up = self.scrollbar.has_next_page();
|
||||||
self.swipe.allow_down = self.scrollbar.has_previous_page();
|
self.swipe.allow_down = self.scrollbar.has_previous_page();
|
||||||
|
self.swipe.allow_left = self.swipe_left;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_page_change(&mut self, ctx: &mut EventCtx) {
|
fn on_page_change(&mut self, ctx: &mut EventCtx) {
|
||||||
@ -138,6 +147,9 @@ where
|
|||||||
self.on_page_change(ctx);
|
self.on_page_change(ctx);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
SwipeDirection::Left if self.swipe_left => {
|
||||||
|
return Some(PageMsg::Aux(AuxPageMsg::SwipeLeft));
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Ignore other directions.
|
// Ignore other directions.
|
||||||
}
|
}
|
||||||
@ -152,7 +164,7 @@ where
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(ButtonMsg::Clicked) = self.button_back.event(ctx, event) {
|
if let Some(ButtonMsg::Clicked) = self.button_back.event(ctx, event) {
|
||||||
return Some(PageMsg::GoBack);
|
return Some(PageMsg::Aux(AuxPageMsg::GoBack));
|
||||||
}
|
}
|
||||||
self.hint.event(ctx, event);
|
self.hint.event(ctx, event);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
|||||||
component::{
|
component::{
|
||||||
base::ComponentExt,
|
base::ComponentExt,
|
||||||
image::BlendedImage,
|
image::BlendedImage,
|
||||||
paginated::{PageMsg, Paginate},
|
paginated::{AuxPageMsg, PageMsg, Paginate},
|
||||||
painter,
|
painter,
|
||||||
placed::GridPlaced,
|
placed::GridPlaced,
|
||||||
text::{
|
text::{
|
||||||
@ -45,9 +45,10 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
component::{
|
component::{
|
||||||
Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, CancelInfoConfirmMsg,
|
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg,
|
||||||
Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
|
CancelInfoConfirmMsg, Dialog, DialogMsg, FidoConfirm, FidoMsg, FloatingButton,
|
||||||
Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput, MnemonicKeyboard,
|
FloatingButtonMsg, Frame, HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg,
|
||||||
|
HorizontalPage, IconDialog, Lockscreen, MnemonicInput, MnemonicKeyboard,
|
||||||
MnemonicKeyboardMsg, NotificationFrame, NumberInputDialog, NumberInputDialogMsg,
|
MnemonicKeyboardMsg, NotificationFrame, NumberInputDialog, NumberInputDialogMsg,
|
||||||
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
|
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
|
||||||
SelectWordCount, SelectWordCountMsg, SelectWordMsg, Slip39Input, SwipeHoldPage, SwipePage,
|
SelectWordCount, SelectWordCountMsg, SelectWordMsg, Slip39Input, SwipeHoldPage, SwipePage,
|
||||||
@ -223,7 +224,8 @@ where
|
|||||||
match msg {
|
match msg {
|
||||||
PageMsg::Content(_) => Err(Error::TypeError),
|
PageMsg::Content(_) => Err(Error::TypeError),
|
||||||
PageMsg::Controls(msg) => msg.try_into(),
|
PageMsg::Controls(msg) => msg.try_into(),
|
||||||
PageMsg::GoBack => Ok(CANCELLED.as_obj()),
|
PageMsg::Aux(AuxPageMsg::GoBack) => Ok(CANCELLED.as_obj()),
|
||||||
|
PageMsg::Aux(AuxPageMsg::SwipeLeft) => Ok(INFO.as_obj()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,7 +238,7 @@ where
|
|||||||
match msg {
|
match msg {
|
||||||
PageMsg::Content(_) => Err(Error::TypeError),
|
PageMsg::Content(_) => Err(Error::TypeError),
|
||||||
PageMsg::Controls(msg) => msg.try_into(),
|
PageMsg::Controls(msg) => msg.try_into(),
|
||||||
PageMsg::GoBack => unreachable!(),
|
PageMsg::Aux(_) => Err(Error::TypeError),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,6 +340,41 @@ impl ComponentMsgObj for Qr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> ComponentMsgObj for FloatingButton<T>
|
||||||
|
where
|
||||||
|
T: ComponentMsgObj,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
FloatingButtonMsg::ButtonClicked => Ok(INFO.as_obj()),
|
||||||
|
FloatingButtonMsg::Content(c) => self.inner().msg_try_into_obj(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ComponentMsgObj for HorizontalPage<T>
|
||||||
|
where
|
||||||
|
T: ComponentMsgObj + Paginate,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
PageMsg::Content(inner_msg) => Ok(self.inner().msg_try_into_obj(inner_msg)?),
|
||||||
|
PageMsg::Controls(_) => Err(Error::TypeError),
|
||||||
|
PageMsg::Aux(AuxPageMsg::GoBack) => Ok(CANCELLED.as_obj()),
|
||||||
|
PageMsg::Aux(_) => Err(Error::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ComponentMsgObj for AddressDetails<T>
|
||||||
|
where
|
||||||
|
T: ParagraphStrType,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_confirm_action(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: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
@ -434,23 +471,54 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
|
|||||||
let description: Option<StrBuffer> =
|
let description: Option<StrBuffer> =
|
||||||
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
|
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
|
||||||
|
let verb: Option<StrBuffer> = kwargs
|
||||||
|
.get(Qstr::MP_QSTR_verb)
|
||||||
|
.unwrap_or_else(|_| Obj::const_none())
|
||||||
|
.try_into_option()?;
|
||||||
let verb_cancel: Option<StrBuffer> = kwargs
|
let verb_cancel: Option<StrBuffer> = kwargs
|
||||||
.get(Qstr::MP_QSTR_verb_cancel)
|
.get(Qstr::MP_QSTR_verb_cancel)
|
||||||
.unwrap_or_else(|_| Obj::const_none())
|
.unwrap_or_else(|_| Obj::const_none())
|
||||||
.try_into_option()?;
|
.try_into_option()?;
|
||||||
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||||
|
|
||||||
let verb: StrBuffer = "CONFIRM".into();
|
confirm_blob(title, data, description, extra, verb, verb_cancel, hold)
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
confirm_blob(
|
extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
title,
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
data,
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
description,
|
let description: Option<StrBuffer> =
|
||||||
extra,
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
Some(verb),
|
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
|
||||||
verb_cancel,
|
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
|
||||||
hold,
|
|
||||||
)
|
let paragraphs = ConfirmBlob {
|
||||||
|
description: description.unwrap_or_else(StrBuffer::empty),
|
||||||
|
extra: extra.unwrap_or_else(StrBuffer::empty),
|
||||||
|
data: data.try_into()?,
|
||||||
|
description_font: &theme::TEXT_NORMAL,
|
||||||
|
extra_font: &theme::TEXT_BOLD,
|
||||||
|
data_font: &theme::TEXT_MONO,
|
||||||
|
}
|
||||||
|
.into_paragraphs();
|
||||||
|
|
||||||
|
let buttons = Button::cancel_confirm(
|
||||||
|
Button::<&'static str>::with_icon(Icon::new(theme::ICON_CANCEL)),
|
||||||
|
Button::<&'static str>::with_icon(Icon::new(theme::ICON_CONFIRM))
|
||||||
|
.styled(theme::button_confirm()),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let obj = LayoutObj::new(FloatingButton::top_right_corner(
|
||||||
|
Icon::new(theme::ICON_INFO_CIRCLE),
|
||||||
|
Frame::left_aligned(
|
||||||
|
theme::label_title(),
|
||||||
|
title,
|
||||||
|
SwipePage::new(paragraphs, buttons, theme::BG).with_swipe_left(),
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
Ok(obj.into())
|
||||||
};
|
};
|
||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
}
|
}
|
||||||
@ -583,6 +651,36 @@ extern "C" fn new_show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
|
|||||||
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 address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
|
||||||
|
let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?;
|
||||||
|
let account: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
|
||||||
|
let path: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?;
|
||||||
|
|
||||||
|
let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?;
|
||||||
|
let mut iter_buf = IterBuf::new();
|
||||||
|
let iter = Iter::try_from_obj_with_buf(xpubs, &mut iter_buf)?;
|
||||||
|
|
||||||
|
let mut ad = AddressDetails::new(address, case_sensitive, account, path)?;
|
||||||
|
|
||||||
|
for i in iter {
|
||||||
|
let [xtitle, text]: [StrBuffer; 2] = iter_into_array(i)?;
|
||||||
|
ad.add_xpub(xtitle, text)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
HorizontalPage::new(
|
||||||
|
FloatingButton::top_right_corner(Icon::new(theme::ICON_CANCEL_LARGER), 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_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_confirm_value(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: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
@ -847,6 +945,33 @@ extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -
|
|||||||
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_mismatch() -> Obj {
|
||||||
|
let block = move || {
|
||||||
|
let title: StrBuffer = "Address mismatch?".into();
|
||||||
|
let description: StrBuffer = "Please contact Trezor support at".into();
|
||||||
|
let url: StrBuffer = "trezor.io/support".into();
|
||||||
|
let button = "QUIT";
|
||||||
|
|
||||||
|
let icon = BlendedImage::single(Icon::new(theme::ICON_OCTA), theme::WARN_COLOR, theme::BG);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
IconDialog::new(
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
Button::cancel_confirm_square(
|
||||||
|
Button::with_icon(Icon::new(theme::ICON_BACK)),
|
||||||
|
Button::with_text(button).styled(theme::button_reset()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_description(description)
|
||||||
|
.with_text(&theme::TEXT_DEMIBOLD, url),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_or_raise(block) }
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_show_simple(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: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?;
|
let title: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?;
|
||||||
@ -1422,12 +1547,24 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// data: str | bytes,
|
/// data: str | bytes,
|
||||||
/// description: str | None,
|
/// description: str | None,
|
||||||
/// extra: str | None,
|
/// extra: str | None,
|
||||||
|
/// verb: str | None = None,
|
||||||
/// verb_cancel: str | None = None,
|
/// verb_cancel: str | None = None,
|
||||||
/// hold: bool = False,
|
/// hold: bool = False,
|
||||||
/// ) -> object:
|
/// ) -> object:
|
||||||
/// """Confirm byte sequence data."""
|
/// """Confirm byte sequence data."""
|
||||||
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),
|
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_address(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// data: str | bytes,
|
||||||
|
/// description: str | None,
|
||||||
|
/// extra: str | None,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm address. Similar to `confirm_blob` but has corner info button
|
||||||
|
/// and allows left swipe which does the same thing as the button."""
|
||||||
|
Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(),
|
||||||
|
|
||||||
/// def confirm_properties(
|
/// def confirm_properties(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str,
|
/// title: str,
|
||||||
@ -1456,6 +1593,17 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// """Show QR code."""
|
/// """Show QR code."""
|
||||||
Qstr::MP_QSTR_show_qr => obj_fn_kw!(0, new_show_qr).as_obj(),
|
Qstr::MP_QSTR_show_qr => obj_fn_kw!(0, new_show_qr).as_obj(),
|
||||||
|
|
||||||
|
/// def show_address_details(
|
||||||
|
/// *,
|
||||||
|
/// address: str,
|
||||||
|
/// case_sensitive: bool,
|
||||||
|
/// account: str | None,
|
||||||
|
/// path: str | None,
|
||||||
|
/// xpubs: list[tuple[str, str]],
|
||||||
|
/// ) -> object:
|
||||||
|
/// """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 confirm_value(
|
/// def confirm_value(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str,
|
/// title: str,
|
||||||
@ -1551,6 +1699,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// """Info modal. No buttons shown when `button` is empty string."""
|
/// """Info modal. No buttons shown when `button` is empty string."""
|
||||||
Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(),
|
Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(),
|
||||||
|
|
||||||
|
/// def show_mismatch() -> object:
|
||||||
|
/// """Warning modal, receiving address mismatch."""
|
||||||
|
Qstr::MP_QSTR_show_mismatch => obj_fn_0!(new_show_mismatch).as_obj(),
|
||||||
|
|
||||||
/// def show_simple(
|
/// def show_simple(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str | None,
|
/// title: str | None,
|
||||||
|
BIN
core/embed/rust/src/ui/model_tt/res/cancel-larger.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/cancel-larger.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_tt/res/info-circle.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/info-circle.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_tt/res/octa-bang.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/octa-bang.toif
Normal file
Binary file not shown.
@ -55,6 +55,7 @@ pub const ICON_SIZE: i16 = 16;
|
|||||||
|
|
||||||
// UI icons (greyscale).
|
// UI icons (greyscale).
|
||||||
pub const ICON_CANCEL: &[u8] = include_res!("model_tt/res/cancel.toif");
|
pub const ICON_CANCEL: &[u8] = include_res!("model_tt/res/cancel.toif");
|
||||||
|
pub const ICON_CANCEL_LARGER: &[u8] = include_res!("model_tt/res/cancel-larger.toif");
|
||||||
pub const ICON_CONFIRM: &[u8] = include_res!("model_tt/res/confirm.toif");
|
pub const ICON_CONFIRM: &[u8] = include_res!("model_tt/res/confirm.toif");
|
||||||
pub const ICON_SPACE: &[u8] = include_res!("model_tt/res/space.toif");
|
pub const ICON_SPACE: &[u8] = include_res!("model_tt/res/space.toif");
|
||||||
pub const ICON_BACK: &[u8] = include_res!("model_tt/res/back.toif");
|
pub const ICON_BACK: &[u8] = include_res!("model_tt/res/back.toif");
|
||||||
@ -70,6 +71,8 @@ pub const ICON_SUCCESS_SMALL: &[u8] = include_res!("model_tt/res/success_bld.toi
|
|||||||
pub const ICON_WARN_SMALL: &[u8] = include_res!("model_tt/res/warn_bld.toif");
|
pub const ICON_WARN_SMALL: &[u8] = include_res!("model_tt/res/warn_bld.toif");
|
||||||
pub const ICON_PAGE_NEXT: &[u8] = include_res!("model_tt/res/page-next.toif");
|
pub const ICON_PAGE_NEXT: &[u8] = include_res!("model_tt/res/page-next.toif");
|
||||||
pub const ICON_PAGE_PREV: &[u8] = include_res!("model_tt/res/page-prev.toif");
|
pub const ICON_PAGE_PREV: &[u8] = include_res!("model_tt/res/page-prev.toif");
|
||||||
|
pub const ICON_OCTA: &[u8] = include_res!("model_tt/res/octa-bang.toif");
|
||||||
|
pub const ICON_INFO_CIRCLE: &[u8] = include_res!("model_tt/res/info-circle.toif");
|
||||||
|
|
||||||
// Large, three-color icons.
|
// Large, three-color icons.
|
||||||
pub const WARN_COLOR: Color = YELLOW;
|
pub const WARN_COLOR: Color = YELLOW;
|
||||||
@ -139,7 +142,7 @@ pub const fn label_title() -> TextStyle {
|
|||||||
TextStyle::new(Font::BOLD, GREY_LIGHT, BG, GREY_LIGHT, GREY_LIGHT)
|
TextStyle::new(Font::BOLD, GREY_LIGHT, BG, GREY_LIGHT, GREY_LIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_default() -> ButtonStyleSheet {
|
pub const fn button_default() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: Font::BOLD,
|
font: Font::BOLD,
|
||||||
@ -171,7 +174,7 @@ pub fn button_default() -> ButtonStyleSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_confirm() -> ButtonStyleSheet {
|
pub const fn button_confirm() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: Font::BOLD,
|
font: Font::BOLD,
|
||||||
@ -203,7 +206,7 @@ pub fn button_confirm() -> ButtonStyleSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_cancel() -> ButtonStyleSheet {
|
pub const fn button_cancel() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: Font::BOLD,
|
font: Font::BOLD,
|
||||||
@ -235,11 +238,11 @@ pub fn button_cancel() -> ButtonStyleSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_danger() -> ButtonStyleSheet {
|
pub const fn button_danger() -> ButtonStyleSheet {
|
||||||
button_cancel()
|
button_cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_reset() -> ButtonStyleSheet {
|
pub const fn button_reset() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: Font::BOLD,
|
font: Font::BOLD,
|
||||||
@ -271,7 +274,7 @@ pub fn button_reset() -> ButtonStyleSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_info() -> ButtonStyleSheet {
|
pub const fn button_info() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: Font::BOLD,
|
font: Font::BOLD,
|
||||||
@ -303,7 +306,7 @@ pub fn button_info() -> ButtonStyleSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_pin() -> ButtonStyleSheet {
|
pub const fn button_pin() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: Font::MONO,
|
font: Font::MONO,
|
||||||
@ -335,7 +338,7 @@ pub fn button_pin() -> ButtonStyleSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_counter() -> ButtonStyleSheet {
|
pub const fn button_counter() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: Font::DEMIBOLD,
|
font: Font::DEMIBOLD,
|
||||||
@ -367,11 +370,11 @@ pub fn button_counter() -> ButtonStyleSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_clear() -> ButtonStyleSheet {
|
pub const fn button_clear() -> ButtonStyleSheet {
|
||||||
button_default()
|
button_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loader_default() -> LoaderStyleSheet {
|
pub const fn loader_default() -> LoaderStyleSheet {
|
||||||
LoaderStyleSheet {
|
LoaderStyleSheet {
|
||||||
normal: &LoaderStyle {
|
normal: &LoaderStyle {
|
||||||
icon: None,
|
icon: None,
|
||||||
@ -418,6 +421,7 @@ pub const TEXT_CHECKLIST_SELECTED: TextStyle =
|
|||||||
TextStyle::new(Font::NORMAL, FG, BG, GREY_LIGHT, GREY_LIGHT);
|
TextStyle::new(Font::NORMAL, FG, BG, GREY_LIGHT, GREY_LIGHT);
|
||||||
pub const TEXT_CHECKLIST_DONE: TextStyle =
|
pub const TEXT_CHECKLIST_DONE: TextStyle =
|
||||||
TextStyle::new(Font::NORMAL, GREEN_DARK, BG, GREY_LIGHT, GREY_LIGHT);
|
TextStyle::new(Font::NORMAL, GREEN_DARK, BG, GREY_LIGHT, GREY_LIGHT);
|
||||||
|
pub const TEXT_XPUB: TextStyle = TEXT_NORMAL.with_line_breaking(LineBreaking::BreakWordsNoHyphen);
|
||||||
|
|
||||||
pub const FORMATTED: FormattedFonts = FormattedFonts {
|
pub const FORMATTED: FormattedFonts = FormattedFonts {
|
||||||
normal: Font::NORMAL,
|
normal: Font::NORMAL,
|
||||||
@ -432,6 +436,8 @@ pub const BUTTON_HEIGHT: i16 = 38;
|
|||||||
pub const BUTTON_SPACING: i16 = 6;
|
pub const BUTTON_SPACING: i16 = 6;
|
||||||
pub const CHECKLIST_SPACING: i16 = 10;
|
pub const CHECKLIST_SPACING: i16 = 10;
|
||||||
pub const RECOVERY_SPACING: i16 = 18;
|
pub const RECOVERY_SPACING: i16 = 18;
|
||||||
|
pub const CORNER_BUTTON_SIDE: i16 = 32;
|
||||||
|
pub const CORNER_BUTTON_SPACING: i16 = 8;
|
||||||
|
|
||||||
/// Standard button height in pixels.
|
/// Standard button height in pixels.
|
||||||
pub const fn button_rows(count: usize) -> i16 {
|
pub const fn button_rows(count: usize) -> i16 {
|
||||||
@ -462,6 +468,10 @@ pub const fn borders_scroll() -> Insets {
|
|||||||
Insets::new(13, 5, 14, 10)
|
Insets::new(13, 5, 14, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn borders_horizontal_scroll() -> Insets {
|
||||||
|
Insets::new(13, 10, 0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn borders_notification() -> Insets {
|
pub const fn borders_notification() -> Insets {
|
||||||
Insets::new(48, 10, 14, 10)
|
Insets::new(48, 10, 14, 10)
|
||||||
}
|
}
|
||||||
|
@ -82,12 +82,25 @@ def confirm_blob(
|
|||||||
data: str | bytes,
|
data: str | bytes,
|
||||||
description: str | None,
|
description: str | None,
|
||||||
extra: str | None,
|
extra: str | None,
|
||||||
|
verb: str | None = None,
|
||||||
verb_cancel: str | None = None,
|
verb_cancel: str | None = None,
|
||||||
hold: bool = False,
|
hold: bool = False,
|
||||||
) -> object:
|
) -> object:
|
||||||
"""Confirm byte sequence data."""
|
"""Confirm byte sequence data."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_address(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
data: str | bytes,
|
||||||
|
description: str | None,
|
||||||
|
extra: str | None,
|
||||||
|
) -> object:
|
||||||
|
"""Confirm address. Similar to `confirm_blob` but has corner info button
|
||||||
|
and allows left swipe which does the same thing as the button."""
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_tt/layout.rs
|
# rust/src/ui/model_tt/layout.rs
|
||||||
def confirm_properties(
|
def confirm_properties(
|
||||||
*,
|
*,
|
||||||
@ -119,6 +132,18 @@ def show_qr(
|
|||||||
"""Show QR code."""
|
"""Show QR code."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def show_address_details(
|
||||||
|
*,
|
||||||
|
address: str,
|
||||||
|
case_sensitive: bool,
|
||||||
|
account: str | None,
|
||||||
|
path: str | None,
|
||||||
|
xpubs: list[tuple[str, str]],
|
||||||
|
) -> object:
|
||||||
|
"""Show address details - QR code, account, path, cosigner xpubs."""
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_tt/layout.rs
|
# rust/src/ui/model_tt/layout.rs
|
||||||
def confirm_value(
|
def confirm_value(
|
||||||
*,
|
*,
|
||||||
@ -222,6 +247,11 @@ def show_info(
|
|||||||
"""Info modal. No buttons shown when `button` is empty string."""
|
"""Info modal. No buttons shown when `button` is empty string."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def show_mismatch() -> object:
|
||||||
|
"""Warning modal, receiving address mismatch."""
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_tt/layout.rs
|
# rust/src/ui/model_tt/layout.rs
|
||||||
def show_simple(
|
def show_simple(
|
||||||
*,
|
*,
|
||||||
|
@ -28,7 +28,6 @@ async def get_address(
|
|||||||
pubkey = node.public_key()
|
pubkey = node.public_key()
|
||||||
address = address_from_public_key(pubkey, HRP)
|
address = address_from_public_key(pubkey, HRP)
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = paths.address_n_to_str(address_n)
|
await show_address(ctx, address, path=paths.address_n_to_str(address_n))
|
||||||
await show_address(ctx, address, title=title)
|
|
||||||
|
|
||||||
return BinanceAddress(address=address)
|
return BinanceAddress(address=address)
|
||||||
|
@ -41,7 +41,7 @@ async def get_address(
|
|||||||
from apps.common.paths import address_n_to_str, validate_path
|
from apps.common.paths import address_n_to_str, validate_path
|
||||||
|
|
||||||
from . import addresses
|
from . import addresses
|
||||||
from .keychain import validate_path_against_script_type
|
from .keychain import address_n_to_name, validate_path_against_script_type
|
||||||
from .multisig import multisig_pubkey_index
|
from .multisig import multisig_pubkey_index
|
||||||
|
|
||||||
multisig = msg.multisig # local_cache_attribute
|
multisig = msg.multisig # local_cache_attribute
|
||||||
@ -95,6 +95,7 @@ async def get_address(
|
|||||||
mac = get_address_mac(address, coin.slip44, keychain)
|
mac = get_address_mac(address, coin.slip44, keychain)
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
|
path = address_n_to_str(address_n)
|
||||||
if multisig:
|
if multisig:
|
||||||
if multisig.nodes:
|
if multisig.nodes:
|
||||||
pubnodes = multisig.nodes
|
pubnodes = multisig.nodes
|
||||||
@ -102,23 +103,30 @@ async def get_address(
|
|||||||
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())
|
||||||
|
|
||||||
title = f"Multisig {multisig.m} of {len(pubnodes)}"
|
|
||||||
await show_address(
|
await show_address(
|
||||||
ctx,
|
ctx,
|
||||||
address_short,
|
address_short,
|
||||||
case_sensitive=address_case_sensitive,
|
case_sensitive=address_case_sensitive,
|
||||||
title=title,
|
path=path,
|
||||||
multisig_index=multisig_index,
|
multisig_index=multisig_index,
|
||||||
xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes),
|
xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes),
|
||||||
|
account=f"Multisig {multisig.m} of {len(pubnodes)}",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
title = address_n_to_str(address_n)
|
account_name = address_n_to_name(coin, address_n, script_type)
|
||||||
|
if account_name is None:
|
||||||
|
account = "Unknown path"
|
||||||
|
elif account_name == "":
|
||||||
|
account = coin.coin_shortcut
|
||||||
|
else:
|
||||||
|
account = f"{coin.coin_shortcut} {account_name}"
|
||||||
await show_address(
|
await show_address(
|
||||||
ctx,
|
ctx,
|
||||||
address_short,
|
address_short,
|
||||||
address_qr=address,
|
address_qr=address,
|
||||||
case_sensitive=address_case_sensitive,
|
case_sensitive=address_case_sensitive,
|
||||||
title=title,
|
path=path,
|
||||||
|
account=account,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Address(address=address, mac=mac)
|
return Address(address=address, mac=mac)
|
||||||
|
@ -384,7 +384,7 @@ def address_n_to_name(
|
|||||||
) -> str | None:
|
) -> str | None:
|
||||||
ACCOUNT_TYPES = (
|
ACCOUNT_TYPES = (
|
||||||
AccountType(
|
AccountType(
|
||||||
"Legacy account",
|
"Legacy",
|
||||||
PATTERN_BIP44,
|
PATTERN_BIP44,
|
||||||
InputScriptType.SPENDADDRESS,
|
InputScriptType.SPENDADDRESS,
|
||||||
require_segwit=True,
|
require_segwit=True,
|
||||||
@ -392,7 +392,7 @@ def address_n_to_name(
|
|||||||
require_taproot=False,
|
require_taproot=False,
|
||||||
),
|
),
|
||||||
AccountType(
|
AccountType(
|
||||||
"Account",
|
"",
|
||||||
PATTERN_BIP44,
|
PATTERN_BIP44,
|
||||||
InputScriptType.SPENDADDRESS,
|
InputScriptType.SPENDADDRESS,
|
||||||
require_segwit=False,
|
require_segwit=False,
|
||||||
@ -400,7 +400,7 @@ def address_n_to_name(
|
|||||||
require_taproot=False,
|
require_taproot=False,
|
||||||
),
|
),
|
||||||
AccountType(
|
AccountType(
|
||||||
"Legacy SegWit account",
|
"L. SegWit",
|
||||||
PATTERN_BIP49,
|
PATTERN_BIP49,
|
||||||
InputScriptType.SPENDP2SHWITNESS,
|
InputScriptType.SPENDP2SHWITNESS,
|
||||||
require_segwit=True,
|
require_segwit=True,
|
||||||
@ -408,7 +408,7 @@ def address_n_to_name(
|
|||||||
require_taproot=False,
|
require_taproot=False,
|
||||||
),
|
),
|
||||||
AccountType(
|
AccountType(
|
||||||
"SegWit account",
|
"SegWit",
|
||||||
PATTERN_BIP84,
|
PATTERN_BIP84,
|
||||||
InputScriptType.SPENDWITNESS,
|
InputScriptType.SPENDWITNESS,
|
||||||
require_segwit=True,
|
require_segwit=True,
|
||||||
@ -416,7 +416,7 @@ def address_n_to_name(
|
|||||||
require_taproot=False,
|
require_taproot=False,
|
||||||
),
|
),
|
||||||
AccountType(
|
AccountType(
|
||||||
"Taproot account",
|
"Taproot",
|
||||||
PATTERN_BIP86,
|
PATTERN_BIP86,
|
||||||
InputScriptType.SPENDTAPROOT,
|
InputScriptType.SPENDTAPROOT,
|
||||||
require_segwit=False,
|
require_segwit=False,
|
||||||
@ -424,7 +424,7 @@ def address_n_to_name(
|
|||||||
require_taproot=True,
|
require_taproot=True,
|
||||||
),
|
),
|
||||||
AccountType(
|
AccountType(
|
||||||
"Coinjoin account",
|
"Coinjoin",
|
||||||
PATTERN_SLIP25_TAPROOT,
|
PATTERN_SLIP25_TAPROOT,
|
||||||
InputScriptType.SPENDTAPROOT,
|
InputScriptType.SPENDTAPROOT,
|
||||||
require_segwit=False,
|
require_segwit=False,
|
||||||
|
@ -975,9 +975,8 @@ async def show_cardano_address(
|
|||||||
if not protocol_magics.is_mainnet(protocol_magic):
|
if not protocol_magics.is_mainnet(protocol_magic):
|
||||||
network_name = protocol_magics.to_ui_string(protocol_magic)
|
network_name = protocol_magics.to_ui_string(protocol_magic)
|
||||||
|
|
||||||
title = f"{ADDRESS_TYPE_NAMES[address_parameters.address_type]} address"
|
path = None
|
||||||
address_extra = None
|
account = ADDRESS_TYPE_NAMES[address_parameters.address_type]
|
||||||
title_qr = title
|
|
||||||
if address_parameters.address_type in (
|
if address_parameters.address_type in (
|
||||||
CAT.BYRON,
|
CAT.BYRON,
|
||||||
CAT.BASE,
|
CAT.BASE,
|
||||||
@ -987,17 +986,14 @@ async def show_cardano_address(
|
|||||||
CAT.REWARD,
|
CAT.REWARD,
|
||||||
):
|
):
|
||||||
if address_parameters.address_n:
|
if address_parameters.address_n:
|
||||||
address_extra = address_n_to_str(address_parameters.address_n)
|
path = address_n_to_str(address_parameters.address_n)
|
||||||
title_qr = address_n_to_str(address_parameters.address_n)
|
|
||||||
elif address_parameters.address_n_staking:
|
elif address_parameters.address_n_staking:
|
||||||
address_extra = address_n_to_str(address_parameters.address_n_staking)
|
path = address_n_to_str(address_parameters.address_n_staking)
|
||||||
title_qr = address_n_to_str(address_parameters.address_n_staking)
|
|
||||||
|
|
||||||
await layouts.show_address(
|
await layouts.show_address(
|
||||||
ctx,
|
ctx,
|
||||||
address,
|
address,
|
||||||
title=title,
|
path=path,
|
||||||
|
account=account,
|
||||||
network=network_name,
|
network=network_name,
|
||||||
address_extra=address_extra,
|
|
||||||
title_qr=title_qr,
|
|
||||||
)
|
)
|
||||||
|
@ -32,7 +32,6 @@ async def get_address(
|
|||||||
address = address_from_bytes(node.ethereum_pubkeyhash(), network)
|
address = address_from_bytes(node.ethereum_pubkeyhash(), network)
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = paths.address_n_to_str(address_n)
|
await show_address(ctx, address, path=paths.address_n_to_str(address_n))
|
||||||
await show_address(ctx, address, title=title)
|
|
||||||
|
|
||||||
return EthereumAddress(address=address)
|
return EthereumAddress(address=address)
|
||||||
|
@ -68,12 +68,11 @@ async def get_address(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = paths.address_n_to_str(msg.address_n)
|
|
||||||
await show_address(
|
await show_address(
|
||||||
ctx,
|
ctx,
|
||||||
addr,
|
addr,
|
||||||
address_qr="monero:" + addr,
|
address_qr="monero:" + addr,
|
||||||
title=title,
|
path=paths.address_n_to_str(msg.address_n),
|
||||||
)
|
)
|
||||||
|
|
||||||
return MoneroAddress(address=addr.encode())
|
return MoneroAddress(address=addr.encode())
|
||||||
|
@ -30,12 +30,11 @@ async def get_address(
|
|||||||
address = node.nem_address(network)
|
address = node.nem_address(network)
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = address_n_to_str(address_n)
|
|
||||||
await show_address(
|
await show_address(
|
||||||
ctx,
|
ctx,
|
||||||
address,
|
address,
|
||||||
case_sensitive=False,
|
case_sensitive=False,
|
||||||
title=title,
|
path=address_n_to_str(address_n),
|
||||||
network=get_network_str(network),
|
network=get_network_str(network),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ async def get_address(
|
|||||||
address = address_from_public_key(pubkey)
|
address = address_from_public_key(pubkey)
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = paths.address_n_to_str(msg.address_n)
|
await show_address(ctx, address, path=paths.address_n_to_str(msg.address_n))
|
||||||
await show_address(ctx, address, title=title)
|
|
||||||
|
|
||||||
return RippleAddress(address=address)
|
return RippleAddress(address=address)
|
||||||
|
@ -24,7 +24,7 @@ async def get_address(
|
|||||||
address = helpers.address_from_public_key(pubkey)
|
address = helpers.address_from_public_key(pubkey)
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = paths.address_n_to_str(msg.address_n)
|
path = paths.address_n_to_str(msg.address_n)
|
||||||
await show_address(ctx, address, case_sensitive=False, title=title)
|
await show_address(ctx, address, case_sensitive=False, path=path)
|
||||||
|
|
||||||
return StellarAddress(address=address)
|
return StellarAddress(address=address)
|
||||||
|
@ -29,7 +29,6 @@ async def get_address(
|
|||||||
address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX)
|
address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX)
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = paths.address_n_to_str(msg.address_n)
|
await show_address(ctx, address, path=paths.address_n_to_str(msg.address_n))
|
||||||
await show_address(ctx, address, title=title)
|
|
||||||
|
|
||||||
return TezosAddress(address=address)
|
return TezosAddress(address=address)
|
||||||
|
@ -164,8 +164,6 @@ async def show_address(
|
|||||||
network: str | None = None,
|
network: str | None = None,
|
||||||
multisig_index: int | None = None,
|
multisig_index: int | None = None,
|
||||||
xpubs: Sequence[str] = (),
|
xpubs: Sequence[str] = (),
|
||||||
address_extra: str | None = None,
|
|
||||||
title_qr: str | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
result = await interact(
|
result = await interact(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -353,24 +353,20 @@ async def confirm_homescreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _show_xpub(xpub: str, title: str, cancel: str | None) -> ui.Layout:
|
|
||||||
content = RustLayout(
|
|
||||||
trezorui2.confirm_blob(
|
|
||||||
title=title,
|
|
||||||
data=xpub,
|
|
||||||
verb_cancel=cancel,
|
|
||||||
extra=None,
|
|
||||||
description=None,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None:
|
async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None:
|
||||||
await raise_if_not_confirmed(
|
await raise_if_not_confirmed(
|
||||||
interact(
|
interact(
|
||||||
ctx,
|
ctx,
|
||||||
_show_xpub(xpub, title, None),
|
RustLayout(
|
||||||
|
trezorui2.confirm_blob(
|
||||||
|
title=title,
|
||||||
|
data=xpub,
|
||||||
|
verb="CONFIRM",
|
||||||
|
verb_cancel=None,
|
||||||
|
extra=None,
|
||||||
|
description=None,
|
||||||
|
)
|
||||||
|
),
|
||||||
"show_xpub",
|
"show_xpub",
|
||||||
ButtonRequestType.PublicKey,
|
ButtonRequestType.PublicKey,
|
||||||
)
|
)
|
||||||
@ -383,61 +379,72 @@ async def show_address(
|
|||||||
*,
|
*,
|
||||||
address_qr: str | None = None,
|
address_qr: str | None = None,
|
||||||
case_sensitive: bool = True,
|
case_sensitive: bool = True,
|
||||||
title: str = "Confirm address",
|
path: str | None = None,
|
||||||
|
account: str | None = None,
|
||||||
network: str | None = None,
|
network: str | None = None,
|
||||||
multisig_index: int | None = None,
|
multisig_index: int | None = None,
|
||||||
xpubs: Sequence[str] = (),
|
xpubs: Sequence[str] = (),
|
||||||
address_extra: str | None = None,
|
|
||||||
title_qr: str | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
is_multisig = len(xpubs) > 0
|
|
||||||
while True:
|
while True:
|
||||||
|
title = (
|
||||||
|
"RECEIVE ADDRESS\n(MULTISIG)"
|
||||||
|
if multisig_index is not None
|
||||||
|
else "RECEIVE ADDRESS"
|
||||||
|
)
|
||||||
result = await interact(
|
result = await interact(
|
||||||
ctx,
|
ctx,
|
||||||
RustLayout(
|
RustLayout(
|
||||||
trezorui2.confirm_blob(
|
trezorui2.confirm_address(
|
||||||
title=title.upper(),
|
title=title,
|
||||||
data=address,
|
data=address,
|
||||||
description=network or "",
|
description=network or "",
|
||||||
extra=address_extra or "",
|
extra=None,
|
||||||
verb_cancel="QR",
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"show_address",
|
"show_address",
|
||||||
ButtonRequestType.Address,
|
ButtonRequestType.Address,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# User pressed right button.
|
||||||
if result is CONFIRMED:
|
if result is CONFIRMED:
|
||||||
break
|
break
|
||||||
|
|
||||||
result = await interact(
|
# User pressed corner button or swiped left, go to address details.
|
||||||
ctx,
|
elif result is INFO:
|
||||||
RustLayout(
|
|
||||||
trezorui2.show_qr(
|
|
||||||
address=address if address_qr is None else address_qr,
|
|
||||||
case_sensitive=case_sensitive,
|
|
||||||
title=title.upper() if title_qr is None else title_qr.upper(),
|
|
||||||
verb_cancel="XPUBs" if is_multisig else "ADDRESS",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"show_qr",
|
|
||||||
ButtonRequestType.Address,
|
|
||||||
)
|
|
||||||
if result is CONFIRMED:
|
|
||||||
break
|
|
||||||
|
|
||||||
if is_multisig:
|
def xpub_title(i: int):
|
||||||
for i, xpub in enumerate(xpubs):
|
result = f"MULTISIG XPUB #{i + 1}\n"
|
||||||
cancel = "NEXT" if i < len(xpubs) - 1 else "ADDRESS"
|
result += " (YOURS)" if i == multisig_index else " (COSIGNER)"
|
||||||
title_xpub = f"XPUB #{i + 1}"
|
return result
|
||||||
title_xpub += " (yours)" if i == multisig_index else " (cosigner)"
|
|
||||||
result = await interact(
|
result = await interact(
|
||||||
ctx,
|
ctx,
|
||||||
_show_xpub(xpub, title=title_xpub, cancel=cancel),
|
RustLayout(
|
||||||
"show_xpub",
|
trezorui2.show_address_details(
|
||||||
ButtonRequestType.PublicKey,
|
address=address if address_qr is None else address_qr,
|
||||||
)
|
case_sensitive=case_sensitive,
|
||||||
if result is CONFIRMED:
|
account=account,
|
||||||
return
|
path=path,
|
||||||
|
xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"show_address_details",
|
||||||
|
ButtonRequestType.Address,
|
||||||
|
)
|
||||||
|
# Can only go back from the address details but corner button returns INFO.
|
||||||
|
assert result in (INFO, CANCELLED)
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
RustLayout(trezorui2.show_mismatch()),
|
||||||
|
"warning_address_mismatch",
|
||||||
|
ButtonRequestType.Warning,
|
||||||
|
)
|
||||||
|
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(
|
||||||
@ -693,6 +700,7 @@ async def confirm_blob(
|
|||||||
data=data,
|
data=data,
|
||||||
extra=None,
|
extra=None,
|
||||||
hold=hold,
|
hold=hold,
|
||||||
|
verb="CONFIRM",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -129,9 +129,11 @@ class LayoutContent:
|
|||||||
|
|
||||||
# First line should have content after the tag, last line does not store content
|
# First line should have content after the tag, last line does not store content
|
||||||
tag = f"< {tag_name}"
|
tag = f"< {tag_name}"
|
||||||
if tag in self.lines[0]:
|
for i in range(len(self.lines)):
|
||||||
first_line = self.lines[0].split(tag)[1]
|
if tag in self.lines[i]:
|
||||||
all_lines = [first_line] + self.lines[1:-1]
|
first_line = self.lines[i].split(tag)[1]
|
||||||
|
all_lines = [first_line] + self.lines[i + 1 : -1]
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
all_lines = self.lines[1:-1]
|
all_lines = self.lines[1:-1]
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import pytest
|
|||||||
|
|
||||||
from trezorlib import btc, messages, tools
|
from trezorlib import btc, messages, tools
|
||||||
from trezorlib.debuglink import TrezorClientDebugLink as Client
|
from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||||
from trezorlib.exceptions import TrezorFailure
|
from trezorlib.exceptions import Cancelled, TrezorFailure
|
||||||
|
|
||||||
VECTORS = ( # path, script_type, address
|
VECTORS = ( # path, script_type, address
|
||||||
(
|
(
|
||||||
@ -43,9 +43,12 @@ VECTORS = ( # path, script_type, address
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CORNER_BUTTON = (215, 25)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_t2
|
||||||
@pytest.mark.parametrize("path, script_type, address", VECTORS)
|
@pytest.mark.parametrize("path, script_type, address", VECTORS)
|
||||||
def test_show(
|
def test_show_t1(
|
||||||
client: Client, path: str, script_type: messages.InputScriptType, address: str
|
client: Client, path: str, script_type: messages.InputScriptType, address: str
|
||||||
):
|
):
|
||||||
def input_flow():
|
def input_flow():
|
||||||
@ -68,6 +71,67 @@ def test_show(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_t1
|
||||||
|
@pytest.mark.parametrize("path, script_type, address", VECTORS)
|
||||||
|
def test_show_tt(
|
||||||
|
client: Client, path: str, script_type: messages.InputScriptType, address: str
|
||||||
|
):
|
||||||
|
def input_flow():
|
||||||
|
yield
|
||||||
|
client.debug.click(CORNER_BUTTON, wait=True)
|
||||||
|
yield
|
||||||
|
client.debug.swipe_left(wait=True)
|
||||||
|
client.debug.swipe_right(wait=True)
|
||||||
|
client.debug.swipe_left(wait=True)
|
||||||
|
client.debug.click(CORNER_BUTTON, wait=True)
|
||||||
|
yield
|
||||||
|
client.debug.press_no()
|
||||||
|
yield
|
||||||
|
client.debug.press_no()
|
||||||
|
yield
|
||||||
|
client.debug.press_yes()
|
||||||
|
|
||||||
|
with client:
|
||||||
|
client.set_input_flow(input_flow)
|
||||||
|
assert (
|
||||||
|
btc.get_address(
|
||||||
|
client,
|
||||||
|
"Bitcoin",
|
||||||
|
tools.parse_path(path),
|
||||||
|
script_type=script_type,
|
||||||
|
show_display=True,
|
||||||
|
)
|
||||||
|
== address
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_t1
|
||||||
|
@pytest.mark.parametrize("path, script_type, address", VECTORS)
|
||||||
|
def test_show_cancel(
|
||||||
|
client: Client, path: str, script_type: messages.InputScriptType, address: str
|
||||||
|
):
|
||||||
|
def input_flow():
|
||||||
|
yield
|
||||||
|
client.debug.click(CORNER_BUTTON, wait=True)
|
||||||
|
yield
|
||||||
|
client.debug.swipe_left(wait=True)
|
||||||
|
client.debug.click(CORNER_BUTTON, wait=True)
|
||||||
|
yield
|
||||||
|
client.debug.press_no()
|
||||||
|
yield
|
||||||
|
client.debug.press_yes()
|
||||||
|
|
||||||
|
with client, pytest.raises(Cancelled):
|
||||||
|
client.set_input_flow(input_flow)
|
||||||
|
btc.get_address(
|
||||||
|
client,
|
||||||
|
"Bitcoin",
|
||||||
|
tools.parse_path(path),
|
||||||
|
script_type=script_type,
|
||||||
|
show_display=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_show_unrecognized_path(client: Client):
|
def test_show_unrecognized_path(client: Client):
|
||||||
with pytest.raises(TrezorFailure):
|
with pytest.raises(TrezorFailure):
|
||||||
btc.get_address(
|
btc.get_address(
|
||||||
@ -213,32 +277,36 @@ def test_show_multisig_xpubs(
|
|||||||
def input_flow():
|
def input_flow():
|
||||||
yield # show address
|
yield # show address
|
||||||
layout = client.debug.wait_layout() # TODO: do not need to *wait* now?
|
layout = client.debug.wait_layout() # TODO: do not need to *wait* now?
|
||||||
assert layout.get_title() == "MULTISIG 2 OF 3"
|
assert layout.get_title() == "RECEIVE ADDRESS (MULTISIG)"
|
||||||
assert layout.get_content().replace(" ", "") == address
|
assert layout.get_content().replace(" ", "") == address
|
||||||
|
|
||||||
client.debug.press_no()
|
client.debug.click(CORNER_BUTTON)
|
||||||
yield # show QR code
|
yield # show QR code
|
||||||
assert "Painter" in client.debug.wait_layout().text
|
assert "Qr" in client.debug.wait_layout().text
|
||||||
|
|
||||||
|
client.debug.swipe_left()
|
||||||
|
# address details
|
||||||
|
layout = client.debug.wait_layout()
|
||||||
|
assert "Multisig 2 of 3" in layout.text
|
||||||
|
|
||||||
# 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):
|
||||||
expected_title = f"XPUB #{xpub_num + 1} " + (
|
expected_title = f"MULTISIG XPUB #{xpub_num + 1} " + (
|
||||||
"(yours)" if i == xpub_num else "(cosigner)"
|
"(YOURS)" if i == xpub_num else "(COSIGNER)"
|
||||||
)
|
)
|
||||||
|
|
||||||
client.debug.press_no()
|
client.debug.swipe_left()
|
||||||
yield # show XPUB#{xpub_num}
|
layout = client.debug.wait_layout()
|
||||||
layout1 = client.debug.wait_layout()
|
assert layout.get_title() == expected_title
|
||||||
assert layout1.get_title() == expected_title
|
content = layout.get_content().replace(" ", "")
|
||||||
client.debug.swipe_up()
|
assert xpubs[xpub_num] in content
|
||||||
|
|
||||||
layout2 = client.debug.wait_layout()
|
|
||||||
assert layout2.get_title() == expected_title
|
|
||||||
content = (layout1.get_content() + layout2.get_content()).replace(
|
|
||||||
" ", ""
|
|
||||||
)
|
|
||||||
assert content == xpubs[xpub_num]
|
|
||||||
|
|
||||||
|
client.debug.click(CORNER_BUTTON)
|
||||||
|
yield # show address
|
||||||
|
client.debug.press_no()
|
||||||
|
yield # address mismatch
|
||||||
|
client.debug.press_no()
|
||||||
|
yield # show address
|
||||||
client.debug.press_yes()
|
client.debug.press_yes()
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
|
@ -340,7 +340,7 @@ def test_signmessage_pagination(client: Client, message: str):
|
|||||||
expected_message = (
|
expected_message = (
|
||||||
("Confirm message: " + message).replace("\n", "").replace(" ", "")
|
("Confirm message: " + message).replace("\n", "").replace(" ", "")
|
||||||
)
|
)
|
||||||
message_read = message_read.replace(" ", "")
|
message_read = message_read.replace(" ", "").replace("...", "")
|
||||||
assert expected_message == message_read
|
assert expected_message == message_read
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user