1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-26 16:18:22 +00:00

fix(core/ui): coinjoin layouts style update

[no changelog]
This commit is contained in:
Martin Milata 2023-03-24 16:54:47 +01:00
parent 77d8af1322
commit 08cad2f909
23 changed files with 439 additions and 162 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 378 B

View File

@ -50,7 +50,6 @@ static void _librust_qstrs(void) {
MP_QSTR_request_slip39;
MP_QSTR_select_word;
MP_QSTR_select_word_count;
MP_QSTR_show_busyscreen;
MP_QSTR_show_group_share_success;
MP_QSTR_show_homescreen;
MP_QSTR_show_lockscreen;
@ -58,6 +57,7 @@ static void _librust_qstrs(void) {
MP_QSTR_show_remaining_shares;
MP_QSTR_show_share_words;
MP_QSTR_show_progress;
MP_QSTR_show_progress_coinjoin;
MP_QSTR_show_address_details;
MP_QSTR_attach_timer_fn;

View File

@ -1,7 +1,7 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display::Font,
geometry::{Alignment, Offset, Rect},
geometry::{Alignment, Insets, Offset, Rect},
};
use super::{text::TextStyle, TextLayout};
@ -9,6 +9,7 @@ use super::{text::TextStyle, TextLayout};
pub struct Label<T> {
text: T,
layout: TextLayout,
vertical: Alignment,
}
impl<T> Label<T>
@ -19,6 +20,7 @@ where
Self {
text,
layout: TextLayout::new(style).with_align(align),
vertical: Alignment::Start,
}
}
@ -34,6 +36,11 @@ where
Self::new(text, Alignment::Center, style)
}
pub fn vertically_aligned(mut self, align: Alignment) -> Self {
self.vertical = align;
self
}
pub fn text(&self) -> &T {
&self.text
}
@ -68,7 +75,13 @@ where
.with_bounds(bounds)
.fit_text(self.text.as_ref())
.height();
self.layout = self.layout.with_bounds(bounds.with_height(height));
let diff = bounds.height() - height;
let insets = match self.vertical {
Alignment::Start => Insets::bottom(diff),
Alignment::Center => Insets::new(diff / 2, 0, diff / 2 + diff % 2, 0),
Alignment::End => Insets::top(diff),
};
self.layout.bounds = bounds.inset(insets);
self.layout.bounds
}

View File

@ -67,3 +67,8 @@ pub fn jpeg_painter<'a>(
let f = move |area: Rect| display::tjpgd::jpeg(image(), area.center() - off, scale);
Painter::new(f)
}
pub fn rect_painter(fg: display::Color, bg: display::Color) -> Painter<impl FnMut(Rect)> {
let f = move |area: Rect| display::rect_fill_rounded(area, fg, bg, 2);
Painter::new(f)
}

View File

@ -214,13 +214,21 @@ impl<T, U> Split<T, U> {
}
}
pub const fn vertical(size: i16, spacing: i16, first: T, second: U) -> Self {
pub const fn left(size: i16, spacing: i16, first: T, second: U) -> Self {
Self::new(Axis::Vertical, size, spacing, first, second)
}
pub const fn horizontal(size: i16, spacing: i16, first: T, second: U) -> Self {
pub const fn right(size: i16, spacing: i16, first: T, second: U) -> Self {
Self::new(Axis::Vertical, -size, spacing, first, second)
}
pub const fn top(size: i16, spacing: i16, first: T, second: U) -> Self {
Self::new(Axis::Horizontal, size, spacing, first, second)
}
pub const fn bottom(size: i16, spacing: i16, first: T, second: U) -> Self {
Self::new(Axis::Horizontal, -size, spacing, first, second)
}
}
impl<M, T, U> Component for Split<T, U>

View File

@ -1,6 +1,8 @@
use crate::ui::{
constant, display,
display::Color,
constant,
constant::{screen, LOADER_INNER, LOADER_OUTER},
display,
display::{toif::Icon, Color},
geometry::{Offset, Point, Rect},
};
@ -10,25 +12,35 @@ use crate::trezorhal::{
dma2d::{dma2d_setup_4bpp_over_4bpp, dma2d_start_blend, dma2d_wait_for_transfer},
};
use crate::ui::{
constant::{screen, LOADER_OUTER},
display::toif::Icon,
};
const LOADER_SIZE: i32 = (LOADER_OUTER * 2.0) as i32;
const OUTER: f32 = constant::LOADER_OUTER;
const INNER: f32 = constant::LOADER_INNER;
const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE;
const IN_INNER_ANTI: i32 = ((INNER - 0.5) * (INNER - 0.5)) as i32;
const INNER_MIN: i32 = ((INNER + 0.5) * (INNER + 0.5)) as i32;
const INNER_MAX: i32 = ((INNER + 1.5) * (INNER + 1.5)) as i32;
const INNER_OUTER_ANTI: i32 = ((INNER + 2.5) * (INNER + 2.5)) as i32;
const OUTER_OUT_ANTI: i32 = ((OUTER - 1.5) * (OUTER - 1.5)) as i32;
const OUTER_MAX: i32 = ((OUTER - 0.5) * (OUTER - 0.5)) as i32;
#[derive(Clone, Copy)]
pub struct LoaderDimensions {
in_inner_anti: i32,
inner_min: i32,
inner_max: i32,
inner_outer_anti: i32,
outer_out_anti: i32,
outer_max: i32,
}
impl LoaderDimensions {
pub fn new(outer: i16, inner: i16) -> Self {
let outer: f32 = outer.into();
let inner: f32 = inner.into();
Self {
in_inner_anti: ((inner - 0.5) * (inner - 0.5)) as i32,
inner_min: ((inner + 0.5) * (inner + 0.5)) as i32,
inner_max: ((inner + 1.5) * (inner + 1.5)) as i32,
inner_outer_anti: ((inner + 2.5) * (inner + 2.5)) as i32,
outer_out_anti: ((outer - 1.5) * (outer - 1.5)) as i32,
outer_max: ((outer - 0.5) * (outer - 0.5)) as i32,
}
}
}
pub fn loader_circular_uncompress(
dim: LoaderDimensions,
y_offset: i16,
fg_color: Color,
bg_color: Color,
@ -36,20 +48,42 @@ pub fn loader_circular_uncompress(
indeterminate: bool,
icon: Option<(Icon, Color)>,
) {
const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE;
if let Some((icon, color)) = icon {
let toif_size = icon.toif.size();
if toif_size.x <= ICON_MAX_SIZE && toif_size.y <= ICON_MAX_SIZE {
let mut icon_data = [0_u8; ((ICON_MAX_SIZE * ICON_MAX_SIZE) / 2) as usize];
icon.toif.uncompress(&mut icon_data);
let i = Some((icon_data.as_ref(), color, toif_size));
loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, i);
loader_rust(
dim,
y_offset,
fg_color,
bg_color,
progress,
indeterminate,
i,
);
} else {
loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None);
loader_rust(
dim,
y_offset,
fg_color,
bg_color,
progress,
indeterminate,
None,
);
}
} else {
loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None);
loader_rust(
dim,
y_offset,
fg_color,
bg_color,
progress,
indeterminate,
None,
);
}
}
@ -60,7 +94,15 @@ pub fn loader_circular(
bg_color: Color,
icon: Option<(Icon, Color)>,
) {
loader_circular_uncompress(y_offset, fg_color, bg_color, progress, false, icon);
loader_circular_uncompress(
LoaderDimensions::new(LOADER_OUTER, LOADER_INNER),
y_offset,
fg_color,
bg_color,
progress,
false,
icon,
);
}
pub fn loader_circular_indeterminate(
@ -70,7 +112,15 @@ pub fn loader_circular_indeterminate(
bg_color: Color,
icon: Option<(Icon, Color)>,
) {
loader_circular_uncompress(y_offset, fg_color, bg_color, progress, true, icon);
loader_circular_uncompress(
LoaderDimensions::new(LOADER_OUTER, LOADER_INNER),
y_offset,
fg_color,
bg_color,
progress,
true,
icon,
);
}
#[inline(always)]
@ -78,8 +128,8 @@ fn get_loader_vectors(indeterminate: bool, progress: u16) -> (Point, Point) {
let (start_progress, end_progress) = if indeterminate {
const LOADER_INDETERMINATE_WIDTH: u16 = 100;
(
progress - LOADER_INDETERMINATE_WIDTH,
progress + LOADER_INDETERMINATE_WIDTH,
(progress + 1000 - LOADER_INDETERMINATE_WIDTH) % 1000,
(progress + LOADER_INDETERMINATE_WIDTH) % 1000,
)
} else {
(0, progress)
@ -114,12 +164,12 @@ fn loader_get_pixel_color_idx(
inverted: bool,
end_vector: Point,
n_start: Point,
x_c: i16,
y_c: i16,
c: Point,
center: Point,
dim: LoaderDimensions,
) -> u8 {
let y_p = -(y_c - center.y);
let x_p = x_c - center.x;
let y_p = -(c.y - center.y);
let x_p = c.x - center.x;
let vx = Point::new(x_p, y_p);
let n_vx = Point::new(-y_p, x_p);
@ -142,31 +192,31 @@ fn loader_get_pixel_color_idx(
// - r_inner)/(r_outer-r_inner) is negligible
if show_all || included {
//active part
if d <= IN_INNER_ANTI {
if d <= dim.in_inner_anti {
0
} else if d <= INNER_MIN {
((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8
} else if d <= OUTER_OUT_ANTI {
} else if d <= dim.inner_min {
((15 * (d - dim.in_inner_anti)) / (dim.inner_min - dim.in_inner_anti)) as u8
} else if d <= dim.outer_out_anti {
15
} else if d <= OUTER_MAX {
(15 - ((15 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI))) as u8
} else if d <= dim.outer_max {
(15 - ((15 * (d - dim.outer_out_anti)) / (dim.outer_max - dim.outer_out_anti))) as u8
} else {
0
}
} else {
//inactive part
if d <= IN_INNER_ANTI {
if d <= dim.in_inner_anti {
0
} else if d <= INNER_MIN {
((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8
} else if d <= INNER_MAX {
} else if d <= dim.inner_min {
((15 * (d - dim.in_inner_anti)) / (dim.inner_min - dim.in_inner_anti)) as u8
} else if d <= dim.inner_max {
15
} else if d <= INNER_OUTER_ANTI {
(15 - ((10 * (d - INNER_MAX)) / (INNER_OUTER_ANTI - INNER_MAX))) as u8
} else if d <= OUTER_OUT_ANTI {
} else if d <= dim.inner_outer_anti {
(15 - ((10 * (d - dim.inner_max)) / (dim.inner_outer_anti - dim.inner_max))) as u8
} else if d <= dim.outer_out_anti {
5
} else if d <= OUTER_MAX {
5 - ((5 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI)) as u8
} else if d <= dim.outer_max {
5 - ((5 * (d - dim.outer_out_anti)) / (dim.outer_max - dim.outer_out_anti)) as u8
} else {
0
}
@ -175,6 +225,7 @@ fn loader_get_pixel_color_idx(
#[cfg(not(feature = "dma2d"))]
pub fn loader_rust(
dim: LoaderDimensions,
y_offset: i16,
fg_color: Color,
bg_color: Color,
@ -225,7 +276,7 @@ pub fn loader_rust(
if use_icon && icon_area_clamped.contains(p) {
let x = x_c - center.x;
let y = y_c - center.y;
if (x as i32 * x as i32 + y as i32 * y as i32) <= IN_INNER_ANTI {
if (x as i32 * x as i32 + y as i32 * y as i32) <= dim.in_inner_anti {
let x_i = x_c - icon_area.x0;
let y_i = y_c - icon_area.y0;
@ -241,7 +292,13 @@ pub fn loader_rust(
if clamped.contains(p) && !icon_pixel {
let pix_c_idx = loader_get_pixel_color_idx(
show_all, inverted, end_vector, n_start, x_c, y_c, center,
show_all,
inverted,
end_vector,
n_start,
Point::new(x_c, y_c),
center,
dim,
);
underlying_color = colortable[pix_c_idx as usize];
}
@ -255,6 +312,7 @@ pub fn loader_rust(
#[cfg(feature = "dma2d")]
pub fn loader_rust(
dim: LoaderDimensions,
y_offset: i16,
fg_color: Color,
bg_color: Color,
@ -341,7 +399,13 @@ pub fn loader_rust(
let pix_c_idx = if clamped.contains(p) {
loader_get_pixel_color_idx(
show_all, inverted, end_vector, n_start, x_c, y_c, center,
show_all,
inverted,
end_vector,
n_start,
Point::new(x_c, y_c),
center,
dim,
)
} else {
0

View File

@ -9,6 +9,9 @@ use core::slice::from_raw_parts;
use crate::ui::display::loader::circular::{
loader_circular as determinate, loader_circular_indeterminate as indeterminate,
};
#[cfg(feature = "model_tt")]
pub use crate::ui::display::loader::circular::{loader_circular_uncompress, LoaderDimensions};
#[cfg(not(feature = "model_tt"))]
use crate::ui::display::loader::rectangular::loader_rectangular as determinate;
#[cfg(not(feature = "model_tt"))]

View File

@ -5,8 +5,8 @@ pub const HEIGHT: i16 = 128;
pub const LINE_SPACE: i16 = 1;
pub const FONT_BPP: i16 = 1;
pub const LOADER_OUTER: f32 = 32_f32;
pub const LOADER_INNER: f32 = 18_f32;
pub const LOADER_OUTER: i32 = 32;
pub const LOADER_INNER: i32 = 18;
pub const LOADER_ICON_MAX_SIZE: i16 = 8;
pub const BACKLIGHT_NORMAL: i32 = 150;

View File

@ -391,7 +391,7 @@ impl<T> Button<T> {
} else {
0
};
theme::button_bar(Split::vertical(
theme::button_bar(Split::left(
width,
theme::BUTTON_SPACING,
left.map(|msg| {
@ -456,11 +456,11 @@ impl<T> Button<T> {
});
let total_height = theme::BUTTON_HEIGHT + theme::BUTTON_SPACING + theme::INFO_BUTTON_HEIGHT;
FixedHeightBar::bottom(
Split::horizontal(
Split::top(
theme::INFO_BUTTON_HEIGHT,
theme::BUTTON_SPACING,
top,
Split::vertical(theme::BUTTON_WIDTH, theme::BUTTON_SPACING, left, right),
Split::left(theme::BUTTON_WIDTH, theme::BUTTON_SPACING, left, right),
),
total_height,
)
@ -488,11 +488,11 @@ impl<T> Button<T> {
let [top, middle, bottom] = words;
let total_height = 3 * theme::BUTTON_HEIGHT + 2 * theme::BUTTON_SPACING;
FixedHeightBar::bottom(
Split::horizontal(
Split::top(
theme::BUTTON_HEIGHT,
theme::BUTTON_SPACING,
btn(0, top),
Split::horizontal(
Split::top(
theme::BUTTON_HEIGHT,
theme::BUTTON_SPACING,
btn(1, middle),

View File

@ -0,0 +1,131 @@
use core::mem;
use crate::ui::{
component::{
base::Never, painter, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split,
},
display::loader::{loader_circular_uncompress, LoaderDimensions},
geometry::{Alignment, Insets, Rect},
util::animation_disabled,
};
use super::{theme, Frame};
const RECTANGLE_HEIGHT: i16 = 56;
const LABEL_TOP: i16 = 135;
const LOADER_OUTER: i16 = 39;
const LOADER_INNER: i16 = 28;
const LOADER_OFFSET: i16 = -34;
const LOADER_SPEED: u16 = 5;
pub struct CoinJoinProgress<T, U> {
value: u16,
indeterminate: bool,
content: Child<Frame<Split<Empty, U>, &'static str>>,
// Label is not a child since circular loader paints large black rectangle which overlaps it.
// To work around this, draw label every time loader is drawn.
label: Label<T>,
}
impl<T, U> CoinJoinProgress<T, U>
where
T: AsRef<str>,
{
pub fn new(text: T, indeterminate: bool) -> CoinJoinProgress<T, impl Component<Msg = Never>>
where
T: AsRef<str>,
{
let style = theme::label_coinjoin_progress();
let label = Label::centered("DO NOT DISCONNECT YOUR TREZOR!", style)
.vertically_aligned(Alignment::Center);
let bg = painter::rect_painter(style.background_color, theme::BG);
let inner = (bg, label);
CoinJoinProgress::with_background(text, inner, indeterminate)
}
}
impl<T, U> CoinJoinProgress<T, U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
pub fn with_background(text: T, inner: U, indeterminate: bool) -> Self {
Self {
value: 0,
indeterminate,
content: Frame::left_aligned(
theme::label_title(),
"COINJOIN IN PROGRESS",
Split::bottom(RECTANGLE_HEIGHT, 0, Empty, inner),
)
.into_child(),
label: Label::centered(text, theme::TEXT_NORMAL),
}
}
}
impl<T, U> Component for CoinJoinProgress<T, U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.content.place(bounds);
let label_bounds = bounds.inset(Insets::top(LABEL_TOP));
self.label.place(label_bounds);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.content.event(ctx, event);
self.label.event(ctx, event);
match event {
_ if animation_disabled() => {
return None;
}
Event::Attach if self.indeterminate => {
ctx.request_anim_frame();
}
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
self.value = (self.value + LOADER_SPEED) % 1000;
ctx.request_anim_frame();
ctx.request_paint();
}
Event::Progress(new_value, _new_description) => {
if mem::replace(&mut self.value, new_value) != new_value {
ctx.request_paint();
}
}
_ => {}
}
None
}
fn paint(&mut self) {
self.content.paint();
loader_circular_uncompress(
LoaderDimensions::new(LOADER_OUTER, LOADER_INNER),
LOADER_OFFSET,
theme::FG,
theme::BG,
self.value,
self.indeterminate,
None,
);
self.label.paint();
}
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for CoinJoinProgress<T, U>
where
T: crate::trace::Trace,
U: Component,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("CoinJoinProgress");
t.close();
}
}

View File

@ -62,6 +62,7 @@ where
fn level_to_style(level: u8) -> (Color, Icon) {
match level {
3 => (theme::YELLOW, Icon::new(theme::ICON_COINJOIN)),
2 => (theme::VIOLET, Icon::new(theme::ICON_MAGIC)),
1 => (theme::YELLOW, Icon::new(theme::ICON_WARN)),
_ => (theme::RED, Icon::new(theme::ICON_WARN)),

View File

@ -1,5 +1,6 @@
mod address_details;
mod button;
mod coinjoin_progress;
mod dialog;
mod fido;
#[rustfmt::skip]
@ -24,6 +25,7 @@ pub use button::{
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg,
CancelInfoConfirmMsg, IconText, SelectWordMsg,
};
pub use coinjoin_progress::CoinJoinProgress;
pub use dialog::{Dialog, DialogMsg, IconDialog};
pub use fido::{FidoConfirm, FidoMsg};
pub use frame::{Frame, FrameMsg, NotificationFrame};

View File

@ -5,8 +5,8 @@ pub const HEIGHT: i16 = 240;
pub const LINE_SPACE: i16 = 4;
pub const FONT_BPP: i16 = 4;
pub const LOADER_OUTER: f32 = 60_f32;
pub const LOADER_INNER: f32 = 42_f32;
pub const LOADER_OUTER: i16 = 60;
pub const LOADER_INNER: i16 = 42;
pub const LOADER_ICON_MAX_SIZE: i16 = 64;
pub const BACKLIGHT_NORMAL: i32 = 150;

View File

@ -28,7 +28,7 @@ use crate::{
},
TextStyle,
},
Border, Component, Empty, FormattedText, Qr, Timeout, TimeoutMsg,
Border, Component, Empty, FormattedText, Never, Qr, Timeout, TimeoutMsg,
},
display::{self, tjpgd::jpeg_info, toif::Icon},
geometry,
@ -46,12 +46,12 @@ use crate::{
use super::{
component::{
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg,
CancelInfoConfirmMsg, Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame, FrameMsg,
HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg, HorizontalPage, IconDialog,
Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NotificationFrame,
NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg,
PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount, SelectWordCountMsg, SelectWordMsg,
Slip39Input, SwipeHoldPage, SwipePage, WelcomeScreen,
CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame,
FrameMsg, HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg, HorizontalPage,
IconDialog, Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg,
NotificationFrame, NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard,
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount,
SelectWordCountMsg, SelectWordMsg, Slip39Input, SwipeHoldPage, SwipePage, WelcomeScreen,
},
constant, theme,
};
@ -336,6 +336,17 @@ where
}
}
impl<T> ComponentMsgObj for (Timeout, T)
where
T: Component<Msg = TimeoutMsg>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
TimeoutMsg::TimedOut => Ok(CANCELLED.as_obj()),
}
}
}
impl ComponentMsgObj for Qr {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!();
@ -365,6 +376,16 @@ where
}
}
impl<T, U> ComponentMsgObj for CoinJoinProgress<T, U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -1064,9 +1085,9 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut
let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?;
let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, "Maximum rounds:".into()),
Paragraph::new(&theme::TEXT_NORMAL, "Max rounds".into()),
Paragraph::new(&theme::TEXT_MONO, max_rounds),
Paragraph::new(&theme::TEXT_NORMAL, "Maximum mining fee:".into()),
Paragraph::new(&theme::TEXT_NORMAL, "Max mining fee".into()),
Paragraph::new(&theme::TEXT_MONO, max_feerate),
]);
@ -1390,6 +1411,30 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_progress_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?;
let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?;
let skip_first_paint: bool = kwargs.get_or(Qstr::MP_QSTR_skip_first_paint, false)?;
// The second type parameter is actually not used in `new()` but we need to
// provide it.
let progress = CoinJoinProgress::<_, Never>::new(title, indeterminate);
let obj = if time_ms > 0 && indeterminate {
let timeout = Timeout::new(time_ms);
LayoutObj::new((timeout, progress.map(|_msg| None)))?
} else {
LayoutObj::new(progress)?
};
if skip_first_paint {
obj.skip_first_paint();
}
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let label: StrBuffer = kwargs.get(Qstr::MP_QSTR_label)?.try_into()?;
@ -1424,31 +1469,6 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_busyscreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let time_ms: u32 = kwargs.get(Qstr::MP_QSTR_time_ms)?.try_into()?;
let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?;
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
Dialog::new(
Paragraphs::new(Paragraph::new(&theme::TEXT_NORMAL, description).centered()),
Timeout::new(time_ms).map(|msg| {
(matches!(msg, TimeoutMsg::TimedOut)).then(|| CancelConfirmMsg::Cancelled)
}),
),
))?;
if skip_first_paint {
obj.skip_first_paint();
}
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn draw_welcome_screen() -> Obj {
// No need of util::try_or_raise, this does not allocate
let mut screen = WelcomeScreen::new();
@ -1822,6 +1842,17 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// make sure the initial desciption has at least that amount of lines."""
Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(),
/// def show_progress_coinjoin(
/// *,
/// title: str,
/// indeterminate: bool = False,
/// time_ms: int = 0,
/// skip_first_paint: bool = False,
/// ) -> object:
/// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when
/// time_ms timeout is passed."""
Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(),
/// def show_homescreen(
/// *,
/// label: str,
@ -1842,16 +1873,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Homescreen for locked device."""
Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(),
/// def show_busyscreen(
/// *,
/// title: str,
/// description: str,
/// time_ms: int,
/// skip_first_paint: bool,
/// ) -> CANCELLED:
/// """Homescreen used for indicating coinjoin in progress."""
Qstr::MP_QSTR_show_busyscreen => obj_fn_kw!(0, new_show_busyscreen).as_obj(),
/// def draw_welcome_screen() -> None:
/// """Show logo icon with the model name at the bottom and return."""
Qstr::MP_QSTR_draw_welcome_screen => obj_fn_0!(draw_welcome_screen).as_obj(),

View File

@ -152,6 +152,10 @@ pub const fn label_title() -> TextStyle {
TextStyle::new(Font::BOLD, GREY_LIGHT, BG, GREY_LIGHT, GREY_LIGHT)
}
pub const fn label_coinjoin_progress() -> TextStyle {
TextStyle::new(Font::BOLD, FG, YELLOW, FG, FG)
}
pub const fn button_default() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {

View File

@ -422,6 +422,18 @@ def show_progress(
make sure the initial desciption has at least that amount of lines."""
# rust/src/ui/model_tt/layout.rs
def show_progress_coinjoin(
*,
title: str,
indeterminate: bool = False,
time_ms: int = 0,
skip_first_paint: bool = False,
) -> object:
"""Show progress loader for coinjoin. Returns CANCELLED after a specified time when
time_ms timeout is passed."""
# rust/src/ui/model_tt/layout.rs
def show_homescreen(
*,
@ -444,17 +456,6 @@ def show_lockscreen(
"""Homescreen for locked device."""
# rust/src/ui/model_tt/layout.rs
def show_busyscreen(
*,
title: str,
description: str,
time_ms: int,
skip_first_paint: bool,
) -> CANCELLED:
"""Homescreen used for indicating coinjoin in progress."""
# rust/src/ui/model_tt/layout.rs
def draw_welcome_screen() -> None:
"""Show logo icon with the model name at the bottom and return."""

View File

@ -263,8 +263,8 @@ async def handle_UnlockPath(ctx: wire.Context, msg: UnlockPath) -> protobuf.Mess
await confirm_action(
ctx,
"confirm_coinjoin_access",
title="Coinjoin account",
description="Do you want to allow access to your coinjoin account?",
title="Coinjoin",
description="Do you want to access your coinjoin account?",
)
wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx)

View File

@ -13,6 +13,7 @@ from ..common import SigHashType, ecdsa_sign, input_is_external
from ..ownership import verify_nonownership
from ..verification import SignatureVerifier
from . import helpers
from .approvers import CoinJoinApprover
from .helpers import request_tx_input, request_tx_output
from .progress import progress
from .tx_info import OriginalTxInfo
@ -47,7 +48,9 @@ _SERIALIZED_TX_BUFFER = empty_bytearray(_MAX_SERIALIZED_CHUNK_SIZE)
class Bitcoin:
async def signer(self) -> None:
progress.init(self.tx_info.tx)
progress.init(
self.tx_info.tx, is_coinjoin=isinstance(self.approver, CoinJoinApprover)
)
# Add inputs to sig_hasher and h_tx_check and compute the sum of input amounts.
await self.step1_process_inputs()

View File

@ -16,6 +16,7 @@ class Progress:
self.progress = 0
self.steps = 0
self.signing = False
self.is_coinjoin = False
# We don't know how long it will take to fetch the previous transactions,
# so for each one we reserve _PREV_TX_MULTIPLIER steps in the signing
@ -24,9 +25,10 @@ class Progress:
# prev_tx input or output in the overall signing progress.
self.prev_tx_step = 0
def init(self, tx: SignTx) -> None:
def init(self, tx: SignTx, is_coinjoin: bool = False) -> None:
self.progress = 0
self.signing = False
self.is_coinjoin = is_coinjoin
# Step 1 and 2 - load inputs and outputs
self.steps = tx.inputs_count + tx.outputs_count
@ -109,13 +111,16 @@ class Progress:
def report_init(self) -> None:
from trezor import workflow
from trezor.ui.layouts import bitcoin_progress
from trezor.ui.layouts import bitcoin_progress, coinjoin_progress
progress_layout = bitcoin_progress
if self.is_coinjoin:
progress_layout = coinjoin_progress
workflow.close_others()
if self.signing:
self.progress_layout = bitcoin_progress("Signing transaction")
self.progress_layout = progress_layout("Signing transaction")
else:
self.progress_layout = bitcoin_progress("Loading transaction")
self.progress_layout = progress_layout("Loading transaction")
def report(self) -> None:
from trezor import utils

View File

@ -2,9 +2,11 @@ import storage
import storage.cache
import storage.device
from trezor import config, wire
from trezor.enums import MessageType
from trezor.ui.layouts.homescreen import Busyscreen, Homescreen, Lockscreen
from apps.base import busy_expiry_ms, lock_device
from apps.common.authorization import is_set_any_session
async def busyscreen() -> None:
@ -19,18 +21,20 @@ async def homescreen() -> None:
notification = None
notification_is_error = False
if storage.device.is_initialized() and storage.device.no_backup():
if is_set_any_session(MessageType.AuthorizeCoinJoin):
notification = "COINJOIN AUTHORIZED"
elif storage.device.is_initialized() and storage.device.no_backup():
notification = "SEEDLESS"
notification_is_error = True
elif storage.device.is_initialized() and storage.device.unfinished_backup():
notification = "BACKUP FAILED!"
notification = "BACKUP FAILED"
notification_is_error = True
elif storage.device.is_initialized() and storage.device.needs_backup():
notification = "NEEDS BACKUP!"
notification = "NEEDS BACKUP"
elif storage.device.is_initialized() and not config.has_pin():
notification = "PIN NOT SET!"
notification = "PIN NOT SET"
elif storage.device.get_experimental_features():
notification = "EXPERIMENTAL MODE!"
notification = "EXPERIMENTAL MODE"
await Homescreen(
label=label,

View File

@ -1135,15 +1135,9 @@ async def request_pin_on_device(
class RustProgress:
def __init__(
self,
title: str,
description: str | None = None,
indeterminate: bool = False,
layout: Any,
):
self.layout: Any = trezorui2.show_progress(
title=title.upper(),
indeterminate=indeterminate,
description=description or "",
)
self.layout = layout
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.display.clear()
self.layout.attach_timer_fn(self.set_timer)
@ -1160,25 +1154,41 @@ class RustProgress:
ui.refresh()
def progress(message: str = "PLEASE WAIT") -> ProgressLayout:
return RustProgress(message.upper())
def progress(
message: str = "PLEASE WAIT",
description: str | None = None,
indeterminate: bool = False,
) -> ProgressLayout:
return RustProgress(
layout=trezorui2.show_progress(
title=message.upper(),
indeterminate=indeterminate,
description=description or "",
)
)
def bitcoin_progress(message: str) -> ProgressLayout:
return RustProgress(message.upper())
return progress(message)
def coinjoin_progress(message: str) -> ProgressLayout:
return RustProgress(
layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False)
)
def pin_progress(message: str, description: str) -> ProgressLayout:
return RustProgress(message.upper(), description=description)
return progress(message, description=description)
def monero_keyimage_sync_progress() -> ProgressLayout:
return RustProgress("SYNCING")
return progress("SYNCING")
def monero_live_refresh_progress() -> ProgressLayout:
return RustProgress("REFRESHING", description="", indeterminate=True)
return progress("REFRESHING", indeterminate=True)
def monero_transaction_progress_inner() -> ProgressLayout:
return RustProgress("SIGNING TRANSACTION", description="")
return progress("SIGNING TRANSACTION")

View File

@ -47,7 +47,9 @@ class Homescreen(HomescreenBase):
level = 1
if notification is not None:
notification = notification.rstrip("!")
if "EXPERIMENTAL" in notification:
if "COINJOIN" in notification.upper():
level = 3
elif "EXPERIMENTAL" in notification.upper():
level = 2
elif notification_is_error:
level = 0
@ -114,9 +116,9 @@ class Busyscreen(HomescreenBase):
def __init__(self, delay_ms: int) -> None:
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
super().__init__(
layout=trezorui2.show_busyscreen(
title="PLEASE WAIT",
description="Coinjoin in progress.\n\nDo not disconnect your\nTrezor.",
layout=trezorui2.show_progress_coinjoin(
title="Waiting for others",
indeterminate=True,
time_ms=delay_ms,
skip_first_paint=skip,
)

View File

@ -715,16 +715,16 @@
"TT_binance-test_sign_tx.py::test_binance_sign_message[message0-expected_response0]": "6367706336b7063b5f8850034afb948c8e9cda571d3b40f0d69f73d743eb72e2",
"TT_binance-test_sign_tx.py::test_binance_sign_message[message1-expected_response1]": "78b3b0efe134085ed595dcc859f53e39b2f044c3ae17e52ef7ff74d33303f5a9",
"TT_binance-test_sign_tx.py::test_binance_sign_message[message2-expected_response2]": "9f6d84a0081925d4d7ffc43765394e2e3c065a34c3fc00b4c9d8f6dbb108796e",
"TT_bitcoin-test_authorize_coinjoin.py::test_cancel_authorization": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202",
"TT_bitcoin-test_authorize_coinjoin.py::test_get_address": "5556ff2531268efa0eda447a1960324c78ff43d79c1f7d91d2d6ddd772275799",
"TT_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "f79cc8fce59d48aa49bc791cbb00d63676dbbd616eb94b4aa0ca56a1c89cf3a0",
"TT_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "8e0a38d46f5a28ed8c7b4608ba5f02b9c3b82a70b01c9bd7319392b0eea272dd",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx": "21954223b2382156eb2926136239cd983ab94fc0d7060ad87e14d35a098276ea",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "eb9e47aaee7b932845a0f94a9f52b3107ce1eb19ba12f63f3d7b464335508634",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "d619b7d8ff6124885969c543603c49952ea07917a58fb5d80592b4f5bb5c8495",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "69649e183ca47887d8363d63996a4ddac2dfc9ebd23e53c9855bb8d30bcc868a",
"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_account_type": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202",
"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_coordinator": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202",
"TT_bitcoin-test_authorize_coinjoin.py::test_cancel_authorization": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc",
"TT_bitcoin-test_authorize_coinjoin.py::test_get_address": "a02623c612e54300d8f9f9d0760282f8ea9be9663b30257797eade328fa30a75",
"TT_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "064eb02d220e974352247d3b7065c38dd3d4fa3d0c5571574bf0d80f369b69d1",
"TT_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "b28769468af2ddf2e4b2e45b0bc89806a6392b082cc8953013795eb32b692890",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx": "a7a3307a96425a843617d341cf559d0898813ba1eec8221e911a153141aaade8",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "a45463660d3c6f920c7d31f6c054a39e504cb152e9eacd0f817aa081a3c7502b",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "b92179f9e59ea76c6410cdfb3402efc33f05a1e75abfee842cb63380cfa4ad94",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "2a086b16a7339f09db438f9a6416a48d2b4e99945a8f1365800d4373158d09a8",
"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_account_type": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc",
"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_coordinator": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc",
"TT_bitcoin-test_bcash.py::test_attack_change_input": "01c388c7836bf584f7d463412ed8dfc18267173759779572b59f8a92831d587d",
"TT_bitcoin-test_bcash.py::test_send_bch_change": "b33fb94f9b7a27db131a025a58e7d1e9130455b56e2a9664a6601126798c5faa",
"TT_bitcoin-test_bcash.py::test_send_bch_external_presigned": "be3c96271c3fa8084f94f3108a283059b625240c8479ae8509e4e61e2e7801b1",
@ -748,7 +748,7 @@
"TT_bitcoin-test_decred.py::test_send_decred": "7aea7d3be283f95c73494d1298cf45f0914e4bd2b563b6ee06822e5475c7cdee",
"TT_bitcoin-test_decred.py::test_send_decred_change": "f1ba6140197b01fcdc6f5e7657dfec8608e19cfd161ddf94273cd057afee7fe5",
"TT_bitcoin-test_decred.py::test_spend_from_stake_generation_and_revocation_decred": "925e48742feacc62b02e4fcab707a3f7fe6ea8779e77e49e94a452416c844579",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--ad9e3c78": "29bd3ff77013039b56947adecc7ca4971d9cdc3c6c084232a2e6cb7b61cdf322",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--ad9e3c78": "9ef6e1b1d30c95b00c52957eda428f0135b67b3ac51febb571b0b37c7959a201",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-44-InputScriptType.SPENDADDRESS-pkh-efa37663": "5a7da5c231515010c7c9355f571c2ea44743e06f4e603b11475790ac47620369",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-49-InputScriptType.SPENDP2SHWITNESS-77f1e2d2": "16d7bd9d6a2d7f3fbd3cd4f57b4d41bd22181d1815f3f3e8ceab8c71c7a1e217",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-84-InputScriptType.SPENDWITNESS-wpk-16507754": "33742a9a1db97e160bae2399272f9ed2d1e4457efb9f1cdda5724bda09d285e8",
@ -757,7 +757,7 @@
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-49-InputScriptType.SPENDP2SHWITNESS-965f4fa3": "70e6bbb4990ce9185fad968524081d31f2fdd6d4663223fb798cf0f186dfef70",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-84-InputScriptType.SPENDWITNESS-wpk-c761bcc8": "b07033a54a4115479d1fb1227ea6895d9661d4f304ca39c0636c2f32504e6eb0",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-86-InputScriptType.SPENDTAPROOT-tr(-686799ea": "c55d9c0796573c26c4ab2ab42805fcffc51c74349ee15da19d45f849443a7c09",
"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--77d3a71b": "37b2f77f05f6b55ae4af5da2343e1ddad44afe52e84b6d63d00effd233a464ee",
"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--77d3a71b": "b136a9f101bd3c1f462dba38211ce88555b57f217216918388b5dda397b71f9d",
"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-44-InputScriptType.SPENDADDRESS-pkh-a26143a7": "c1510c3bfff3a635ba9f01d3d292b795b135fd32f89edfbd211609bfb3b9fb18",
"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-49-InputScriptType.SPENDP2SHWITNESS-195ebda5": "6a44ff76861f8b45fcf02d8231eba4c7d7be2e36acbc83245daab8bf1e139e69",
"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-84-InputScriptType.SPENDWITNESS-wpk-68f8b526": "4668aded2297e0aaa149c1e3c81016add52c6a273df6709360f3e3f239b59be0",
@ -1729,8 +1729,8 @@
"TT_test_basic.py::test_device_id_same": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_test_basic.py::test_features": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_test_basic.py::test_ping": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_test_busy_state.py::test_busy_expiry": "b67d51c7c3e2ebf4318f249c48badc0fceb907fedeb99de4fc6b6d196389bf69",
"TT_test_busy_state.py::test_busy_state": "b549edd509bde87496aea924d9b9df251893d3a901afc46a5a61605abe7022fb",
"TT_test_busy_state.py::test_busy_expiry": "ba86a3b442bcc2709762a008734511cf5d7ddd715f7184e9006061b8046e2d56",
"TT_test_busy_state.py::test_busy_state": "201b1a28a61556a030f1991e5f88925f1bc3db9ef421e4bc99a323505212f7ef",
"TT_test_cancel.py::test_cancel_message_via_cancel[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5",
"TT_test_cancel.py::test_cancel_message_via_cancel[message1]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5",
"TT_test_cancel.py::test_cancel_message_via_initialize[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5",