mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-19 05:58:09 +00:00
fix(core/mercury): homescreen attach animation, resume animations after an interrupt from workflow
This commit is contained in:
parent
6b3aa768bd
commit
2994317dcd
@ -1 +1,2 @@
|
||||
[T3T1] Smoothened screen transitions by removing backlight fading
|
||||
[T3T1] Improved resuming of interrupted animations
|
||||
|
@ -6,11 +6,13 @@
|
||||
|
||||
static void _librust_qstrs(void) {
|
||||
MP_QSTR_;
|
||||
MP_QSTR_AttachType;
|
||||
MP_QSTR_BacklightLevels;
|
||||
MP_QSTR_CANCELLED;
|
||||
MP_QSTR_CONFIRMED;
|
||||
MP_QSTR_DIM;
|
||||
MP_QSTR_INFO;
|
||||
MP_QSTR_INITIAL;
|
||||
MP_QSTR_LOW;
|
||||
MP_QSTR_LayoutObj;
|
||||
MP_QSTR_MAX;
|
||||
@ -20,6 +22,11 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_MsgDef;
|
||||
MP_QSTR_NONE;
|
||||
MP_QSTR_NORMAL;
|
||||
MP_QSTR_RESUME;
|
||||
MP_QSTR_SWIPE_DOWN;
|
||||
MP_QSTR_SWIPE_LEFT;
|
||||
MP_QSTR_SWIPE_RIGHT;
|
||||
MP_QSTR_SWIPE_UP;
|
||||
MP_QSTR_TR;
|
||||
MP_QSTR_TranslationsHeader;
|
||||
MP_QSTR___del__;
|
||||
|
@ -348,7 +348,12 @@ where
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))]
|
||||
pub enum AttachType {
|
||||
/// Initial attach, redraw the whole screen
|
||||
Initial,
|
||||
/// The layout is already rendered on display, resume any animation
|
||||
/// where we left off. The animation state is expected to be stored locally
|
||||
/// in the given component.
|
||||
Resume,
|
||||
#[cfg(feature = "touch")]
|
||||
Swipe(SwipeDirection),
|
||||
}
|
||||
|
@ -5,6 +5,12 @@ use core::{
|
||||
};
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
|
||||
#[cfg(feature = "button")]
|
||||
use crate::ui::event::ButtonEvent;
|
||||
#[cfg(feature = "touch")]
|
||||
use crate::ui::{component::SwipeDirection, event::TouchEvent};
|
||||
#[cfg(feature = "new_rendering")]
|
||||
use crate::ui::{display::Color, shape::render_on_display};
|
||||
use crate::{
|
||||
error::Error,
|
||||
maybe_trace::MaybeTrace,
|
||||
@ -15,52 +21,61 @@ use crate::{
|
||||
map::Map,
|
||||
obj::{Obj, ObjBase},
|
||||
qstr::Qstr,
|
||||
simple_type::SimpleTypeObj,
|
||||
typ::Type,
|
||||
util,
|
||||
},
|
||||
time::Duration,
|
||||
ui::{
|
||||
button_request::ButtonRequest,
|
||||
component::{Component, Event, EventCtx, Never, TimerToken},
|
||||
component::{base::AttachType, Component, Event, EventCtx, Never, TimerToken},
|
||||
constant, display,
|
||||
event::USBEvent,
|
||||
geometry::Rect,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::ui::component::base::AttachType;
|
||||
#[cfg(feature = "new_rendering")]
|
||||
use crate::ui::{display::Color, shape::render_on_display};
|
||||
|
||||
#[cfg(feature = "button")]
|
||||
use crate::ui::event::ButtonEvent;
|
||||
use crate::ui::event::USBEvent;
|
||||
#[cfg(feature = "touch")]
|
||||
use crate::ui::{component::SwipeDirection, event::TouchEvent};
|
||||
|
||||
impl AttachType {
|
||||
fn to_obj(self) -> Obj {
|
||||
match self {
|
||||
Self::Initial => Obj::const_none(),
|
||||
Self::Resume => 1u8.into(),
|
||||
#[cfg(feature = "touch")]
|
||||
Self::Swipe(dir) => dir.to_u8().into(),
|
||||
Self::Swipe(dir) => (2u8 + unwrap!(dir.to_u8())).into(),
|
||||
}
|
||||
}
|
||||
fn try_from_obj(obj: Obj) -> Result<Self, Error> {
|
||||
if obj == Obj::const_none() {
|
||||
return Ok(Self::Initial);
|
||||
}
|
||||
#[cfg(feature = "touch")]
|
||||
{
|
||||
let dir: u8 = obj.try_into()?;
|
||||
return Ok(AttachType::Swipe(
|
||||
SwipeDirection::from_u8(dir).ok_or(Error::TypeError)?,
|
||||
));
|
||||
let val: u8 = obj.try_into()?;
|
||||
|
||||
match val {
|
||||
0 => Ok(Self::Initial),
|
||||
1 => Ok(Self::Resume),
|
||||
#[cfg(feature = "touch")]
|
||||
2..=5 => Ok(Self::Swipe(
|
||||
SwipeDirection::from_u8(val - 2).ok_or(Error::TypeError)?,
|
||||
)),
|
||||
_ => Err(Error::TypeError),
|
||||
}
|
||||
#[allow(unreachable_code)]
|
||||
Err(Error::TypeError)
|
||||
}
|
||||
}
|
||||
|
||||
static ATTACH_TYPE: Type = obj_type! {
|
||||
name: Qstr::MP_QSTR_AttachType,
|
||||
locals: &obj_dict!(obj_map! {
|
||||
Qstr::MP_QSTR_INITIAL => Obj::small_int(0u16),
|
||||
Qstr::MP_QSTR_RESUME => Obj::small_int(1u16),
|
||||
Qstr::MP_QSTR_SWIPE_UP => Obj::small_int(2u16),
|
||||
Qstr::MP_QSTR_SWIPE_DOWN => Obj::small_int(3u16),
|
||||
Qstr::MP_QSTR_SWIPE_LEFT => Obj::small_int(4u16),
|
||||
Qstr::MP_QSTR_SWIPE_RIGHT => Obj::small_int(5u16),
|
||||
}),
|
||||
};
|
||||
|
||||
pub static ATTACH_TYPE_OBJ: SimpleTypeObj = SimpleTypeObj::new(&ATTACH_TYPE);
|
||||
|
||||
/// Conversion trait implemented by components that know how to convert their
|
||||
/// message values into MicroPython `Obj`s.
|
||||
pub trait ComponentMsgObj: Component {
|
||||
|
@ -16,7 +16,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use crate::ui::{
|
||||
component::Label,
|
||||
component::{base::AttachType, Label},
|
||||
constant::{screen, HEIGHT, WIDTH},
|
||||
lerp::Lerp,
|
||||
model_mercury::{
|
||||
@ -118,6 +118,122 @@ fn render_default_hs<'a>(target: &mut impl Renderer<'a>) {
|
||||
.render(target);
|
||||
}
|
||||
|
||||
struct HomescreenState {
|
||||
attach: AttachAnimationState,
|
||||
label: HideLabelAnimationState,
|
||||
}
|
||||
|
||||
static mut HOMESCREEN_STATE: HomescreenState = HomescreenState {
|
||||
attach: AttachAnimation::DEFAULT_STATE,
|
||||
label: HideLabelAnimation::DEFAULT_STATE,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct AttachAnimation {
|
||||
pub timer: Stopwatch,
|
||||
pub active: bool,
|
||||
pub duration: Duration,
|
||||
start_opacity: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct AttachAnimationState {
|
||||
opacity: u8,
|
||||
}
|
||||
|
||||
impl AttachAnimation {
|
||||
const DURATION_MS: u32 = 500;
|
||||
|
||||
pub const DEFAULT_STATE: AttachAnimationState = AttachAnimationState { opacity: 255 };
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
if animation_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.timer.is_running_within(self.duration)
|
||||
}
|
||||
|
||||
fn eval(&self) -> f32 {
|
||||
if animation_disabled() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
self.timer.elapsed().to_millis() as f32 / 1000.0
|
||||
}
|
||||
|
||||
fn opacity(&self, t: f32) -> u8 {
|
||||
if animation_disabled() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
let f = pareen::constant(self.start_opacity)
|
||||
.seq_ease_in_out(
|
||||
0.0,
|
||||
easer::functions::Linear,
|
||||
self.duration.to_millis() as f32 / 1000.0,
|
||||
pareen::constant(1.0),
|
||||
)
|
||||
.eval(t);
|
||||
|
||||
(f * 255.0) as u8
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
self.active = true;
|
||||
self.timer.start();
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.active = false;
|
||||
self.timer = Stopwatch::new_stopped();
|
||||
}
|
||||
|
||||
fn lazy_start(&mut self, ctx: &mut EventCtx, event: Event, resume: AttachAnimationState) {
|
||||
match event {
|
||||
Event::Attach(AttachType::Initial) => {
|
||||
self.duration = Duration::from_millis(Self::DURATION_MS);
|
||||
self.reset();
|
||||
if !animation_disabled() {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
Event::Attach(AttachType::Resume) => {
|
||||
let start_opacity = resume.opacity as f32 / 255.0;
|
||||
let duration = start_opacity * Self::DURATION_MS as f32;
|
||||
self.start_opacity = start_opacity;
|
||||
self.duration = Duration::from_millis(duration as u32);
|
||||
self.timer = Stopwatch::new_stopped();
|
||||
self.active = resume.opacity < 255;
|
||||
|
||||
if !animation_disabled() {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
|
||||
if !self.timer.is_running() {
|
||||
self.start();
|
||||
}
|
||||
if self.is_active() {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
} else if self.active {
|
||||
self.active = false;
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state(&self, t: f32) -> AttachAnimationState {
|
||||
AttachAnimationState {
|
||||
opacity: self.opacity(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HideLabelAnimation {
|
||||
pub timer: Stopwatch,
|
||||
token: TimerToken,
|
||||
@ -125,9 +241,23 @@ struct HideLabelAnimation {
|
||||
hidden: bool,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct HideLabelAnimationState {
|
||||
animating: bool,
|
||||
hidden: bool,
|
||||
elapsed: u32,
|
||||
}
|
||||
|
||||
impl HideLabelAnimation {
|
||||
const HIDE_AFTER: Duration = Duration::from_millis(3000);
|
||||
|
||||
pub const DEFAULT_STATE: HideLabelAnimationState = HideLabelAnimationState {
|
||||
animating: false,
|
||||
hidden: false,
|
||||
elapsed: 0,
|
||||
};
|
||||
|
||||
fn new(label_width: i16) -> Self {
|
||||
Self {
|
||||
timer: Stopwatch::default(),
|
||||
@ -146,6 +276,10 @@ impl HideLabelAnimation {
|
||||
self.timer = Stopwatch::default();
|
||||
}
|
||||
|
||||
fn elapsed(&self) -> Duration {
|
||||
self.timer.elapsed()
|
||||
}
|
||||
|
||||
fn change_dir(&mut self) {
|
||||
let elapsed = self.timer.elapsed();
|
||||
|
||||
@ -191,53 +325,81 @@ impl HideLabelAnimation {
|
||||
Offset::x(i16::lerp(-(label_width + 12), 0, pos))
|
||||
}
|
||||
|
||||
fn process_event(&mut self, ctx: &mut EventCtx, event: Event) {
|
||||
if let Event::Attach(_) = event {
|
||||
ctx.request_anim_frame();
|
||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
||||
}
|
||||
|
||||
if let Event::Timer(token) = event {
|
||||
if token == self.token && !animation_disabled() {
|
||||
self.timer.start();
|
||||
fn process_event(&mut self, ctx: &mut EventCtx, event: Event, resume: HideLabelAnimationState) {
|
||||
match event {
|
||||
Event::Attach(AttachType::Initial) => {
|
||||
ctx.request_anim_frame();
|
||||
self.animating = true;
|
||||
self.hidden = false;
|
||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
||||
}
|
||||
}
|
||||
Event::Attach(AttachType::Resume) => {
|
||||
self.hidden = resume.hidden;
|
||||
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if self.is_active() {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
} else if self.animating {
|
||||
self.animating = false;
|
||||
self.hidden = !self.hidden;
|
||||
self.reset();
|
||||
ctx.request_paint();
|
||||
let start = Instant::now()
|
||||
.checked_sub(Duration::from_millis(resume.elapsed))
|
||||
.unwrap_or(Instant::now());
|
||||
|
||||
if !self.hidden {
|
||||
self.animating = resume.animating;
|
||||
|
||||
if self.animating {
|
||||
self.timer = Stopwatch::Running(start);
|
||||
ctx.request_anim_frame();
|
||||
} else {
|
||||
self.timer = Stopwatch::new_stopped();
|
||||
}
|
||||
if !self.animating && !self.hidden {
|
||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Event::Touch(TouchEvent::TouchStart(_)) = event {
|
||||
if !self.animating {
|
||||
if self.hidden {
|
||||
self.timer.start();
|
||||
self.animating = true;
|
||||
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
|
||||
if self.is_active() {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
} else {
|
||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
||||
} else if self.animating {
|
||||
self.animating = false;
|
||||
self.hidden = !self.hidden;
|
||||
self.reset();
|
||||
ctx.request_paint();
|
||||
|
||||
if !self.hidden {
|
||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
||||
}
|
||||
}
|
||||
} else if !self.hidden {
|
||||
self.change_dir();
|
||||
self.hidden = true;
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
Event::Timer(token) => {
|
||||
if token == self.token && !animation_disabled() {
|
||||
self.timer.start();
|
||||
ctx.request_anim_frame();
|
||||
self.animating = true;
|
||||
self.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
Event::Touch(TouchEvent::TouchStart(_)) => {
|
||||
if !self.animating {
|
||||
if self.hidden {
|
||||
self.timer.start();
|
||||
self.animating = true;
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
} else {
|
||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
||||
}
|
||||
} else if !self.hidden {
|
||||
self.change_dir();
|
||||
self.hidden = true;
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state(&self) -> HideLabelAnimationState {
|
||||
HideLabelAnimationState {
|
||||
animating: self.animating,
|
||||
hidden: self.hidden,
|
||||
elapsed: self.timer.elapsed().to_millis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,6 +421,7 @@ pub struct Homescreen {
|
||||
hold_to_lock: bool,
|
||||
loader: Loader,
|
||||
delay: Option<TimerToken>,
|
||||
attach_animation: AttachAnimation,
|
||||
label_anim: HideLabelAnimation,
|
||||
}
|
||||
|
||||
@ -296,6 +459,7 @@ impl Homescreen {
|
||||
hold_to_lock,
|
||||
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
||||
delay: None,
|
||||
attach_animation: AttachAnimation::default(),
|
||||
label_anim: HideLabelAnimation::new(label_width),
|
||||
}
|
||||
}
|
||||
@ -395,9 +559,15 @@ impl Component for Homescreen {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
// SAFETY: Single threaded access
|
||||
let resume_attach = unsafe { HOMESCREEN_STATE.attach };
|
||||
self.attach_animation.lazy_start(ctx, event, resume_attach);
|
||||
|
||||
Self::event_usb(self, ctx, event);
|
||||
|
||||
self.label_anim.process_event(ctx, event);
|
||||
let resume_label = unsafe { HOMESCREEN_STATE.label };
|
||||
|
||||
self.label_anim.process_event(ctx, event, resume_label);
|
||||
|
||||
if self.hold_to_lock {
|
||||
Self::event_hold(self, ctx, event).then_some(HomescreenMsg::Dismissed)
|
||||
@ -414,7 +584,18 @@ impl Component for Homescreen {
|
||||
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
|
||||
self.render_loader(target);
|
||||
} else {
|
||||
shape::RawImage::new(AREA, self.bg_image.view()).render(target);
|
||||
let t = self.attach_animation.eval();
|
||||
let opacity = self.attach_animation.opacity(t);
|
||||
|
||||
if let Some(image) = self.image {
|
||||
if let ImageInfo::Jpeg(_) = ImageInfo::parse(image) {
|
||||
shape::JpegImage::new_image(AREA.center(), image)
|
||||
.with_align(Alignment2D::CENTER)
|
||||
.render(target);
|
||||
}
|
||||
} else {
|
||||
render_default_hs(target);
|
||||
}
|
||||
|
||||
let y_offset = self.label_anim.eval(self.label_width);
|
||||
|
||||
@ -437,6 +618,17 @@ impl Component for Homescreen {
|
||||
if let Some(notif) = self.get_notification() {
|
||||
render_notif(notif, NOTIFICATION_TOP, target);
|
||||
}
|
||||
|
||||
shape::Bar::new(AREA)
|
||||
.with_bg(Color::black())
|
||||
.with_alpha(255 - opacity)
|
||||
.render(target);
|
||||
|
||||
// SAFETY: Single threaded access
|
||||
unsafe {
|
||||
HOMESCREEN_STATE.attach = self.attach_animation.get_state(t);
|
||||
HOMESCREEN_STATE.label = self.label_anim.get_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -449,13 +641,34 @@ impl crate::trace::Trace for Homescreen {
|
||||
}
|
||||
}
|
||||
|
||||
struct LockscreenState {
|
||||
attach: AttachAnimationState,
|
||||
overlay: LockscreenAnimState,
|
||||
label: HideLabelAnimationState,
|
||||
}
|
||||
|
||||
static mut LOCKSCREEN_STATE: LockscreenState = LockscreenState {
|
||||
attach: AttachAnimation::DEFAULT_STATE,
|
||||
overlay: LockscreenAnim::DEFAULT_STATE,
|
||||
label: HideLabelAnimation::DEFAULT_STATE,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct LockscreenAnim {
|
||||
pub start: f32,
|
||||
pub timer: Stopwatch,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct LockscreenAnimState {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl LockscreenAnim {
|
||||
const DURATION_MS: u32 = 1500;
|
||||
|
||||
pub const DEFAULT_STATE: LockscreenAnimState = LockscreenAnimState { angle: 0.0 };
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@ -468,12 +681,44 @@ impl LockscreenAnim {
|
||||
|
||||
let t: f32 = self.timer.elapsed().to_millis() as f32 / 1000.0;
|
||||
|
||||
anim.eval(t)
|
||||
self.start + anim.eval(t)
|
||||
}
|
||||
|
||||
pub fn lazy_start(&mut self, ctx: &mut EventCtx, event: Event, resume: LockscreenAnimState) {
|
||||
match event {
|
||||
Event::Attach(AttachType::Initial) => {
|
||||
self.start = 0.0;
|
||||
if !animation_disabled() {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
Event::Attach(AttachType::Resume) => {
|
||||
self.start = resume.angle;
|
||||
if !animation_disabled() {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
|
||||
if !animation_disabled() {
|
||||
if !self.timer.is_running() {
|
||||
self.timer.start();
|
||||
}
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> LockscreenAnimState {
|
||||
LockscreenAnimState { angle: self.eval() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lockscreen {
|
||||
anim: LockscreenAnim,
|
||||
attach_animation: AttachAnimation,
|
||||
label: Label<'static>,
|
||||
name_width: i16,
|
||||
label_width: i16,
|
||||
@ -512,6 +757,7 @@ impl Lockscreen {
|
||||
|
||||
Lockscreen {
|
||||
anim: LockscreenAnim::default(),
|
||||
attach_animation: AttachAnimation::default(),
|
||||
label: Label::new(label, Alignment::Center, theme::TEXT_DEMIBOLD),
|
||||
name_width,
|
||||
label_width,
|
||||
@ -535,21 +781,18 @@ impl Component for Lockscreen {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Event::Attach(_) = event {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
// SAFETY: Single threaded access
|
||||
let resume_attach = unsafe { LOCKSCREEN_STATE.attach };
|
||||
self.attach_animation.lazy_start(ctx, event, resume_attach);
|
||||
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if !animation_disabled() {
|
||||
if !self.anim.timer.is_running() {
|
||||
self.anim.timer.start();
|
||||
}
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
// SAFETY: Single threaded access
|
||||
let resume_overlay = unsafe { LOCKSCREEN_STATE.overlay };
|
||||
self.anim.lazy_start(ctx, event, resume_overlay);
|
||||
|
||||
self.label_anim.process_event(ctx, event);
|
||||
// SAFETY: Single threaded access
|
||||
let resume_label = unsafe { LOCKSCREEN_STATE.label };
|
||||
|
||||
self.label_anim.process_event(ctx, event, resume_label);
|
||||
|
||||
if let Event::Touch(TouchEvent::TouchEnd(_)) = event {
|
||||
return Some(HomescreenMsg::Dismissed);
|
||||
@ -569,7 +812,8 @@ impl Component for Lockscreen {
|
||||
|
||||
shape::RawImage::new(AREA, self.bg_image.view()).render(target);
|
||||
|
||||
cshape::UnlockOverlay::new(center, self.anim.eval()).render(target);
|
||||
let overlay_rotation = self.anim.eval();
|
||||
cshape::UnlockOverlay::new(center, overlay_rotation).render(target);
|
||||
|
||||
shape::Bar::new(AREA.split_top(OVERLAY_BORDER).0)
|
||||
.with_bg(Color::black())
|
||||
@ -651,6 +895,22 @@ impl Component for Lockscreen {
|
||||
|
||||
render_notif(notif, NOTIFICATION_LOCKSCREEN_TOP, target);
|
||||
}
|
||||
|
||||
let t = self.attach_animation.eval();
|
||||
let opacity = self.attach_animation.opacity(t);
|
||||
|
||||
shape::Bar::new(AREA)
|
||||
.with_bg(Color::black())
|
||||
.with_fg(Color::black())
|
||||
.with_alpha(255 - opacity)
|
||||
.render(target);
|
||||
|
||||
// SAFETY: Single threaded access
|
||||
unsafe {
|
||||
LOCKSCREEN_STATE.attach = self.attach_animation.get_state(t);
|
||||
LOCKSCREEN_STATE.overlay = self.anim.get_state();
|
||||
LOCKSCREEN_STATE.label = self.label_anim.get_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,20 +124,28 @@ impl Loader {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn progress(&self, now: Instant) -> Option<u16> {
|
||||
pub fn progress_raw(&self, now: Instant) -> Option<u16> {
|
||||
self.animation().map(|a| a.value(now))
|
||||
}
|
||||
|
||||
pub fn progress(&self, now: Instant) -> Option<u16> {
|
||||
if animation_disabled() {
|
||||
self.progress_raw(now).map(|_a| display::LOADER_MIN)
|
||||
} else {
|
||||
self.progress_raw(now)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_animating(&self) -> bool {
|
||||
self.animation().is_some()
|
||||
}
|
||||
|
||||
pub fn is_completely_grown(&self, now: Instant) -> bool {
|
||||
matches!(self.progress(now), Some(display::LOADER_MAX))
|
||||
matches!(self.progress_raw(now), Some(display::LOADER_MAX))
|
||||
}
|
||||
|
||||
pub fn is_completely_shrunk(&self, now: Instant) -> bool {
|
||||
matches!(self.progress(now), Some(display::LOADER_MIN))
|
||||
matches!(self.progress_raw(now), Some(display::LOADER_MIN))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,16 @@
|
||||
use core::{cmp::Ordering, convert::TryInto};
|
||||
|
||||
use super::{
|
||||
component::{
|
||||
AddressDetails, Bip39Input, Button, CancelConfirmMsg, CancelInfoConfirmMsg,
|
||||
CoinJoinProgress, FidoConfirm, FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg,
|
||||
Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
|
||||
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, PromptScreen,
|
||||
SelectWordCount, SelectWordCountMsg, SetBrightnessDialog, Slip39Input, StatusScreen,
|
||||
SwipeUpScreen, SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg,
|
||||
},
|
||||
flow, theme,
|
||||
};
|
||||
use crate::{
|
||||
error::{value_error, Error},
|
||||
io::BinaryData,
|
||||
@ -36,7 +47,7 @@ use crate::{
|
||||
flow::Swipable,
|
||||
geometry,
|
||||
layout::{
|
||||
obj::{ComponentMsgObj, LayoutObj},
|
||||
obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ},
|
||||
result::{CANCELLED, CONFIRMED, INFO},
|
||||
util::{upy_disable_animation, ConfirmBlob, PropsList},
|
||||
},
|
||||
@ -47,18 +58,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
component::{
|
||||
AddressDetails, Bip39Input, Button, CancelConfirmMsg, CancelInfoConfirmMsg,
|
||||
CoinJoinProgress, FidoConfirm, FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg,
|
||||
Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
|
||||
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, PromptScreen,
|
||||
SelectWordCount, SelectWordCountMsg, SetBrightnessDialog, Slip39Input, StatusScreen,
|
||||
SwipeUpScreen, SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg,
|
||||
},
|
||||
flow, theme,
|
||||
};
|
||||
|
||||
impl TryFrom<CancelConfirmMsg> for Obj {
|
||||
type Error = Error;
|
||||
|
||||
@ -1313,9 +1312,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
///
|
||||
/// class AttachType:
|
||||
/// ...
|
||||
///
|
||||
/// class LayoutObj(Generic[T]):
|
||||
/// """Representation of a Rust-based layout object.
|
||||
/// see `trezor::ui::layout::obj::LayoutObj`.
|
||||
@ -1905,4 +1901,14 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
///
|
||||
/// mock:global
|
||||
Qstr::MP_QSTR_BacklightLevels => BACKLIGHT_LEVELS_OBJ.as_obj(),
|
||||
|
||||
/// class AttachType:
|
||||
/// INITIAL: ClassVar[int]
|
||||
/// RESUME: ClassVar[int]
|
||||
/// SWIPE_UP: ClassVar[int]
|
||||
/// SWIPE_DOWN: ClassVar[int]
|
||||
/// SWIPE_LEFT: ClassVar[int]
|
||||
/// SWIPE_RIGHT: ClassVar[int]
|
||||
Qstr::MP_QSTR_AttachType => ATTACH_TYPE_OBJ.as_obj(),
|
||||
|
||||
};
|
||||
|
@ -2,6 +2,16 @@ use core::{cmp::Ordering, convert::TryInto};
|
||||
|
||||
use heapless::Vec;
|
||||
|
||||
use super::{
|
||||
component::{
|
||||
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, CancelConfirmMsg,
|
||||
CancelInfoConfirmMsg, CoinJoinProgress, ConfirmHomescreen, Flow, FlowPages, Frame,
|
||||
Homescreen, Lockscreen, NumberInput, Page, PassphraseEntry, PinEntry, Progress,
|
||||
ScrollableContent, ScrollableFrame, ShareWords, ShowMore, SimpleChoice, WordlistEntry,
|
||||
WordlistType,
|
||||
},
|
||||
constant, theme,
|
||||
};
|
||||
use crate::{
|
||||
error::Error,
|
||||
maybe_trace::MaybeTrace,
|
||||
@ -37,7 +47,7 @@ use crate::{
|
||||
},
|
||||
geometry,
|
||||
layout::{
|
||||
obj::{ComponentMsgObj, LayoutObj},
|
||||
obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ},
|
||||
result::{CANCELLED, CONFIRMED, INFO},
|
||||
util::{upy_disable_animation, ConfirmBlob},
|
||||
},
|
||||
@ -45,17 +55,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
component::{
|
||||
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, CancelConfirmMsg,
|
||||
CancelInfoConfirmMsg, CoinJoinProgress, ConfirmHomescreen, Flow, FlowPages, Frame,
|
||||
Homescreen, Lockscreen, NumberInput, Page, PassphraseEntry, PinEntry, Progress,
|
||||
ScrollableContent, ScrollableFrame, ShareWords, ShowMore, SimpleChoice, WordlistEntry,
|
||||
WordlistType,
|
||||
},
|
||||
constant, theme,
|
||||
};
|
||||
|
||||
impl From<CancelConfirmMsg> for Obj {
|
||||
fn from(value: CancelConfirmMsg) -> Self {
|
||||
match value {
|
||||
@ -2063,4 +2062,13 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
///
|
||||
/// mock:global
|
||||
Qstr::MP_QSTR_BacklightLevels => BACKLIGHT_LEVELS_OBJ.as_obj(),
|
||||
|
||||
/// class AttachType:
|
||||
/// INITIAL: ClassVar[int]
|
||||
/// RESUME: ClassVar[int]
|
||||
/// SWIPE_UP: ClassVar[int]
|
||||
/// SWIPE_DOWN: ClassVar[int]
|
||||
/// SWIPE_LEFT: ClassVar[int]
|
||||
/// SWIPE_RIGHT: ClassVar[int]
|
||||
Qstr::MP_QSTR_AttachType => ATTACH_TYPE_OBJ.as_obj(),
|
||||
};
|
||||
|
@ -1,5 +1,17 @@
|
||||
use core::{cmp::Ordering, convert::TryInto};
|
||||
|
||||
use super::{
|
||||
component::{
|
||||
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet,
|
||||
CancelConfirmMsg, CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm,
|
||||
FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
|
||||
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
|
||||
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
|
||||
SelectWordCount, SelectWordCountMsg, SelectWordMsg, SetBrightnessDialog, SimplePage,
|
||||
Slip39Input,
|
||||
},
|
||||
theme,
|
||||
};
|
||||
use crate::{
|
||||
error::{value_error, Error},
|
||||
io::BinaryData,
|
||||
@ -38,7 +50,7 @@ use crate::{
|
||||
},
|
||||
geometry,
|
||||
layout::{
|
||||
obj::{ComponentMsgObj, LayoutObj},
|
||||
obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ},
|
||||
result::{CANCELLED, CONFIRMED, INFO},
|
||||
util::{upy_disable_animation, ConfirmBlob, PropsList},
|
||||
},
|
||||
@ -46,19 +58,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
component::{
|
||||
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet,
|
||||
CancelConfirmMsg, CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm,
|
||||
FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
|
||||
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
|
||||
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
|
||||
SelectWordCount, SelectWordCountMsg, SelectWordMsg, SetBrightnessDialog, SimplePage,
|
||||
Slip39Input,
|
||||
},
|
||||
theme,
|
||||
};
|
||||
|
||||
impl TryFrom<CancelConfirmMsg> for Obj {
|
||||
type Error = Error;
|
||||
|
||||
@ -1625,9 +1624,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
///
|
||||
/// class AttachType:
|
||||
/// ...
|
||||
///
|
||||
/// class LayoutObj(Generic[T]):
|
||||
/// """Representation of a Rust-based layout object.
|
||||
/// see `trezor::ui::layout::obj::LayoutObj`.
|
||||
@ -2153,6 +2149,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
///
|
||||
/// mock:global
|
||||
Qstr::MP_QSTR_BacklightLevels => BACKLIGHT_LEVELS_OBJ.as_obj(),
|
||||
|
||||
/// class AttachType:
|
||||
/// INITIAL: ClassVar[int]
|
||||
/// RESUME: ClassVar[int]
|
||||
/// SWIPE_UP: ClassVar[int]
|
||||
/// SWIPE_DOWN: ClassVar[int]
|
||||
/// SWIPE_LEFT: ClassVar[int]
|
||||
/// SWIPE_RIGHT: ClassVar[int]
|
||||
Qstr::MP_QSTR_AttachType => ATTACH_TYPE_OBJ.as_obj(),
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -3,11 +3,6 @@ from trezor import utils
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
class AttachType:
|
||||
...
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
class LayoutObj(Generic[T]):
|
||||
"""Representation of a Rust-based layout object.
|
||||
@ -621,6 +616,16 @@ class BacklightLevels:
|
||||
LOW: ClassVar[int]
|
||||
DIM: ClassVar[int]
|
||||
NONE: ClassVar[int]
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
class AttachType:
|
||||
INITIAL: ClassVar[int]
|
||||
RESUME: ClassVar[int]
|
||||
SWIPE_UP: ClassVar[int]
|
||||
SWIPE_DOWN: ClassVar[int]
|
||||
SWIPE_LEFT: ClassVar[int]
|
||||
SWIPE_RIGHT: ClassVar[int]
|
||||
CONFIRMED: UiResult
|
||||
CANCELLED: UiResult
|
||||
INFO: UiResult
|
||||
@ -1091,15 +1096,20 @@ class BacklightLevels:
|
||||
LOW: ClassVar[int]
|
||||
DIM: ClassVar[int]
|
||||
NONE: ClassVar[int]
|
||||
|
||||
|
||||
# rust/src/ui/model_tr/layout.rs
|
||||
class AttachType:
|
||||
INITIAL: ClassVar[int]
|
||||
RESUME: ClassVar[int]
|
||||
SWIPE_UP: ClassVar[int]
|
||||
SWIPE_DOWN: ClassVar[int]
|
||||
SWIPE_LEFT: ClassVar[int]
|
||||
SWIPE_RIGHT: ClassVar[int]
|
||||
from trezor import utils
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
class AttachType:
|
||||
...
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
class LayoutObj(Generic[T]):
|
||||
"""Representation of a Rust-based layout object.
|
||||
@ -1642,3 +1652,13 @@ class BacklightLevels:
|
||||
LOW: ClassVar[int]
|
||||
DIM: ClassVar[int]
|
||||
NONE: ClassVar[int]
|
||||
|
||||
|
||||
# rust/src/ui/model_tt/layout.rs
|
||||
class AttachType:
|
||||
INITIAL: ClassVar[int]
|
||||
RESUME: ClassVar[int]
|
||||
SWIPE_UP: ClassVar[int]
|
||||
SWIPE_DOWN: ClassVar[int]
|
||||
SWIPE_LEFT: ClassVar[int]
|
||||
SWIPE_RIGHT: ClassVar[int]
|
||||
|
@ -4,12 +4,12 @@ from trezorui import Display
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Generator
|
||||
|
||||
from trezor import loop, utils
|
||||
from trezorui2 import BacklightLevels
|
||||
from trezorui2 import AttachType, BacklightLevels
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from trezorui2 import AttachType, UiResult # noqa: F401
|
||||
from trezorui2 import UiResult # noqa: F401
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
@ -36,10 +36,13 @@ class RustLayout(ui.Layout):
|
||||
self.br_chan = loop.chan()
|
||||
self.layout = layout
|
||||
self.timer = loop.Timer()
|
||||
self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
|
||||
self._attach()
|
||||
self._send_button_request()
|
||||
self.backlight_level = ui.BacklightLevels.NORMAL
|
||||
|
||||
def _attach(self) -> None:
|
||||
self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
|
||||
|
||||
def __del__(self):
|
||||
self.layout.__del__()
|
||||
|
||||
|
@ -18,6 +18,12 @@ class HomescreenBase(RustLayout):
|
||||
def __init__(self, layout: Any) -> None:
|
||||
super().__init__(layout=layout)
|
||||
|
||||
def _attach(self) -> None:
|
||||
if storage_cache.homescreen_shown is self.RENDER_INDICATOR:
|
||||
self.layout.attach_timer_fn(self.set_timer, ui.AttachType.RESUME)
|
||||
else:
|
||||
self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
|
||||
|
||||
def _paint(self) -> None:
|
||||
if self.layout.paint():
|
||||
ui.refresh()
|
||||
|
@ -15334,7 +15334,7 @@
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "eb98ca2a5a32eafe3f900478beb4258e67c896baec5ee44b6bed4bb0b0f6b606",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "503270a6bef3f40595776027e60c23297cbb043e9d0d68e0aef1812f75ade20e",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "0541c248157f4c0f10dcaa338aea083ec15a7293c865a5de3d9cb13df8ecb41e",
|
||||
"T3T1_cs_test_lock.py::test_hold_to_lock": "dad08e836117c018d5c91d42a51195ab3784b9109bf8feb88c9b16b868951b56",
|
||||
"T3T1_cs_test_lock.py::test_hold_to_lock": "1824eb2d9867a82fb99174bac6ef8a906a0d6bf11381e0939091fee8a9c10b45",
|
||||
"T3T1_cs_test_passphrase_mercury.py::test_cycle_through_last_character": "3ad09c8d05197cb0ac1627c792bebda14e1cd031c0c1414c9ace143c60f79fae",
|
||||
"T3T1_cs_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "956e3bb892ca76d13da3bb45556dc19789d4fbfdde1052c4c954139f503f0c81",
|
||||
"T3T1_cs_test_passphrase_mercury.py::test_passphrase_delete": "6f496d80c2ab52faf30679ceffd8915926d098be42f47123aaf18345e23baeeb",
|
||||
@ -15383,7 +15383,7 @@
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "93332879c26051be67399b47ce6135e729783ac35be14bba8a18f4bd7fad0163",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "1c2c503e0ecf55359e7dc7800b190d71b01c6dc953d65494e9dd6940f40e4341",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "84895c70dcbeacd69227294e245f4a4aadd89aa25a33c2af70e40e88251fbc54",
|
||||
"T3T1_de_test_lock.py::test_hold_to_lock": "dfadb3325a3352d33b4048c414c2c1a5eca35b9bfb1412d0cde926e36b184ef0",
|
||||
"T3T1_de_test_lock.py::test_hold_to_lock": "b2136906b71a65f5e949f79e80f381b44a5b87e59f67f23ee22767abb588dfcf",
|
||||
"T3T1_de_test_passphrase_mercury.py::test_cycle_through_last_character": "63c08446669db6dd58a639b5fd2e18ca75f8f0309dd3588dbd7fe8d96352a2b4",
|
||||
"T3T1_de_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "bf9d0f3b2d019fff0af115958eed8b6aed3b8794b10a18ce02dd9dfb35037009",
|
||||
"T3T1_de_test_passphrase_mercury.py::test_passphrase_delete": "79efacad4f082f5e54879a81dcac263a01a7583a8d645e5f2102f61e308671bc",
|
||||
@ -15432,7 +15432,7 @@
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "37ee82e8643b9e226a332f8d8f644d38efa11abee4d5fa01cc7aeef259efdf48",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "abd96d32b92c8ef5c8ac4cac59ea78f623f7aef14392f98b6608c4ff62eabf19",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "6ee94a1eb6437c83569d05695e0c224398f8b074d2e1a4c8a8b5b3496af87f55",
|
||||
"T3T1_en_test_lock.py::test_hold_to_lock": "1bd07afb3ce583aa8b7afd2a1b9b72c64f81e12ecc3382fef8306a90e8f33624",
|
||||
"T3T1_en_test_lock.py::test_hold_to_lock": "0dd53e59ef70d6b20ad463f16ae28ad18472cd7e26e5bf61eae78b05cb395009",
|
||||
"T3T1_en_test_passphrase_mercury.py::test_cycle_through_last_character": "77fe6f8bfb1ddc6a8516913c2ebc669e6b988e6ef9ea6846ef6d94e921fa65af",
|
||||
"T3T1_en_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "c5540d1c33265bcd397b8a72fe949eef9b514c708fb3bbc2d3d2aef804cc8a23",
|
||||
"T3T1_en_test_passphrase_mercury.py::test_passphrase_delete": "ce2aaa6bc1c26c65a4278ebd3e23dac4adf81fb71dd72af685db34ba766c3935",
|
||||
@ -15481,7 +15481,7 @@
|
||||
"T3T1_es_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "1f535bab5bdf06f820fb00b278cd8e10a686c9162f4d16e068e2da01e5d01281",
|
||||
"T3T1_es_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "fb5f271ecf82ce97bdf7a10c0a8b6d158a356c0a4e5a50ce114c464695e2f3d3",
|
||||
"T3T1_es_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "3f28ed90d81f57c3c92467a8b98d4bb4362cf0fefbb7a54a79fe7653dc4a624f",
|
||||
"T3T1_es_test_lock.py::test_hold_to_lock": "78ee06d95dc3e030a2720c608a3ee90e1af5fdaf02869823089eab12cbcdf997",
|
||||
"T3T1_es_test_lock.py::test_hold_to_lock": "22121f35ab8ef5850185109c604bf540c3a7691b7a61c46ddbebc78e36e59ae4",
|
||||
"T3T1_es_test_passphrase_mercury.py::test_cycle_through_last_character": "3fc39e02f19cf7626398c62eeba26f2e36ee6449e3390283fa96c016ea85d58e",
|
||||
"T3T1_es_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "83116b38b2709f164d512a3e53dc36b2fc65cd635f8cc5dd49a7e4d01b52b554",
|
||||
"T3T1_es_test_passphrase_mercury.py::test_passphrase_delete": "7e4da3874efd61f9a37ecd276a3f104d15b511a075702de130ffc1b60ce96a2c",
|
||||
@ -15530,7 +15530,7 @@
|
||||
"T3T1_fr_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "46f84e9b2b88d4108ec7e7ae7000d8e3e9fe4fa4642955ba3e243cee5ee8caf4",
|
||||
"T3T1_fr_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "6708cae42ad5c8ebfc6fcf11dfa9ef86e761075ecd071172c3798f2c5f6ab359",
|
||||
"T3T1_fr_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "10dce76a0863c78385b97f7c1aeca8d75fb5b15a13ff8a4e630bd8862be4d890",
|
||||
"T3T1_fr_test_lock.py::test_hold_to_lock": "b0722dd84e823c408bea43a2a1a7ce00a310fa02c08e848cb2b5102337c0ee6d",
|
||||
"T3T1_fr_test_lock.py::test_hold_to_lock": "31961b0cba116a3a786e5758e4753f703fb9b8499d4c6cc7bffc8a162de70e82",
|
||||
"T3T1_fr_test_passphrase_mercury.py::test_cycle_through_last_character": "6a74ce7c04eb4b26b79b91ee0f1580e1296ebf25750f5b237451e84afabb69c5",
|
||||
"T3T1_fr_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "ce9ad70bbb739a124b7fb0e039a7c63954b04609f869c549b4ed0dbaab590c7f",
|
||||
"T3T1_fr_test_passphrase_mercury.py::test_passphrase_delete": "b4ee17ffa5f236746430884da9fafa69379ddb5c88383d517ce7e5cc1d88e7cc",
|
||||
|
Loading…
Reference in New Issue
Block a user