mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-19 05:58:09 +00:00
feat(core) screen transitions for mercury UI
[no changelog]
This commit is contained in:
parent
ed58409888
commit
a67bc19bac
@ -88,7 +88,7 @@ SECTIONS {
|
||||
} >SRAM1
|
||||
|
||||
.stack : ALIGN(8) {
|
||||
. = 16K; /* Overflow causes UsageFault */
|
||||
. = 32K; /* Overflow causes UsageFault */
|
||||
} >SRAM2
|
||||
|
||||
.confidential : ALIGN(512) {
|
||||
|
@ -4,6 +4,7 @@ use crate::{
|
||||
};
|
||||
|
||||
/// Running, time-based linear progression of a value.
|
||||
#[derive(Clone)]
|
||||
pub struct Animation<T> {
|
||||
/// Starting value.
|
||||
pub from: T,
|
||||
|
@ -16,11 +16,13 @@ use crate::{
|
||||
|
||||
#[cfg(feature = "button")]
|
||||
use crate::ui::event::ButtonEvent;
|
||||
#[cfg(feature = "touch")]
|
||||
use crate::ui::event::TouchEvent;
|
||||
use crate::ui::event::USBEvent;
|
||||
#[cfg(feature = "touch")]
|
||||
use crate::ui::event::{SwipeEvent, TouchEvent};
|
||||
|
||||
use super::Paginate;
|
||||
#[cfg(feature = "touch")]
|
||||
use super::SwipeDirection;
|
||||
|
||||
/// Type used by components that do not return any messages.
|
||||
///
|
||||
@ -466,6 +468,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum AttachType {
|
||||
Initial,
|
||||
#[cfg(feature = "touch")]
|
||||
Swipe(SwipeDirection),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
#[cfg(feature = "button")]
|
||||
@ -480,10 +489,13 @@ pub enum Event {
|
||||
Progress(u16, TString<'static>),
|
||||
/// Component has been attached to component tree. This event is sent once
|
||||
/// before any other events.
|
||||
Attach,
|
||||
Attach(AttachType),
|
||||
/// Internally-handled event to inform all `Child` wrappers in a sub-tree to
|
||||
/// get scheduled for painting.
|
||||
RequestPaint,
|
||||
/// Swipe and transition events
|
||||
#[cfg(feature = "touch")]
|
||||
Swipe(SwipeEvent),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
@ -511,6 +523,8 @@ pub struct EventCtx {
|
||||
page_count: Option<usize>,
|
||||
button_request: Option<ButtonRequest>,
|
||||
root_repaint_requested: bool,
|
||||
swipe_disable_req: bool,
|
||||
swipe_enable_req: bool,
|
||||
}
|
||||
|
||||
impl EventCtx {
|
||||
@ -538,6 +552,8 @@ impl EventCtx {
|
||||
page_count: None,
|
||||
button_request: None,
|
||||
root_repaint_requested: false,
|
||||
swipe_disable_req: false,
|
||||
swipe_enable_req: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -612,6 +628,22 @@ impl EventCtx {
|
||||
self.timers.pop()
|
||||
}
|
||||
|
||||
pub fn disable_swipe(&mut self) {
|
||||
self.swipe_disable_req = true;
|
||||
}
|
||||
|
||||
pub fn disable_swipe_requested(&self) -> bool {
|
||||
self.swipe_disable_req
|
||||
}
|
||||
|
||||
pub fn enable_swipe(&mut self) {
|
||||
self.swipe_enable_req = true;
|
||||
}
|
||||
|
||||
pub fn enable_swipe_requested(&self) -> bool {
|
||||
self.swipe_enable_req
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.place_requested = false;
|
||||
self.paint_requested = false;
|
||||
@ -621,6 +653,8 @@ impl EventCtx {
|
||||
assert!(self.button_request.is_none());
|
||||
self.button_request = None;
|
||||
self.root_repaint_requested = false;
|
||||
self.swipe_disable_req = false;
|
||||
self.swipe_enable_req = false;
|
||||
}
|
||||
|
||||
fn register_timer(&mut self, token: TimerToken, deadline: Duration) {
|
||||
|
@ -4,6 +4,9 @@ use crate::ui::{
|
||||
geometry::Rect,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
|
||||
use crate::ui::component::swipe_detect::SwipeConfig;
|
||||
|
||||
/// Component that sends a ButtonRequest after receiving Event::Attach. The
|
||||
/// request is only sent once.
|
||||
#[derive(Clone)]
|
||||
@ -29,7 +32,7 @@ impl<T: Component> Component for OneButtonRequest<T> {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if matches!(event, Event::Attach) {
|
||||
if matches!(event, Event::Attach(_)) {
|
||||
if let Some(button_request) = self.button_request.take() {
|
||||
ctx.send_button_request(button_request.code, button_request.br_type)
|
||||
}
|
||||
@ -46,6 +49,17 @@ impl<T: Component> Component for OneButtonRequest<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
|
||||
impl<T: crate::ui::flow::Swipable> crate::ui::flow::Swipable for OneButtonRequest<T> {
|
||||
fn get_swipe_config(&self) -> SwipeConfig {
|
||||
self.inner.get_swipe_config()
|
||||
}
|
||||
|
||||
fn get_internal_page_count(&self) -> usize {
|
||||
self.inner.get_internal_page_count()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: crate::trace::Trace> crate::trace::Trace for OneButtonRequest<T> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
@ -63,21 +77,3 @@ pub trait ButtonRequestExt {
|
||||
}
|
||||
|
||||
impl<T: Component> ButtonRequestExt for T {}
|
||||
|
||||
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
|
||||
impl<T> crate::ui::flow::Swipable<T::Msg> for OneButtonRequest<T>
|
||||
where
|
||||
T: Component + crate::ui::flow::Swipable<T::Msg>,
|
||||
{
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
direction: crate::ui::component::SwipeDirection,
|
||||
) -> crate::ui::flow::SwipableResult<T::Msg> {
|
||||
self.inner.swipe_start(ctx, direction)
|
||||
}
|
||||
|
||||
fn swipe_finished(&self) -> bool {
|
||||
self.inner.swipe_finished()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
use super::{Component, Event, EventCtx};
|
||||
use crate::ui::{geometry::Rect, shape::Renderer};
|
||||
|
||||
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
|
||||
use crate::ui::component::swipe_detect::SwipeConfig;
|
||||
|
||||
pub struct MsgMap<T, F> {
|
||||
inner: T,
|
||||
func: F,
|
||||
@ -41,6 +44,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
|
||||
impl<T, F> crate::ui::flow::Swipable for MsgMap<T, F>
|
||||
where
|
||||
T: Component + crate::ui::flow::Swipable,
|
||||
{
|
||||
fn get_swipe_config(&self) -> SwipeConfig {
|
||||
self.inner.get_swipe_config()
|
||||
}
|
||||
fn get_internal_page_count(&self) -> usize {
|
||||
self.inner.get_internal_page_count()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, F> crate::trace::Trace for MsgMap<T, F>
|
||||
where
|
||||
@ -51,25 +67,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
|
||||
impl<T, F, U> crate::ui::flow::Swipable<U> for MsgMap<T, F>
|
||||
where
|
||||
T: Component + crate::ui::flow::Swipable<T::Msg>,
|
||||
F: Fn(T::Msg) -> Option<U>,
|
||||
{
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
direction: super::SwipeDirection,
|
||||
) -> crate::ui::flow::SwipableResult<U> {
|
||||
self.inner.swipe_start(ctx, direction).map(&self.func)
|
||||
}
|
||||
|
||||
fn swipe_finished(&self) -> bool {
|
||||
self.inner.swipe_finished()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PageMap<T, F> {
|
||||
inner: T,
|
||||
func: F,
|
||||
@ -123,19 +120,14 @@ where
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
|
||||
impl<T, F> crate::ui::flow::Swipable<T::Msg> for PageMap<T, F>
|
||||
impl<T, F> crate::ui::flow::Swipable for PageMap<T, F>
|
||||
where
|
||||
T: Component + crate::ui::flow::Swipable<T::Msg>,
|
||||
T: Component + crate::ui::flow::Swipable,
|
||||
{
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
direction: super::SwipeDirection,
|
||||
) -> crate::ui::flow::SwipableResult<T::Msg> {
|
||||
self.inner.swipe_start(ctx, direction)
|
||||
fn get_swipe_config(&self) -> SwipeConfig {
|
||||
self.inner.get_swipe_config()
|
||||
}
|
||||
|
||||
fn swipe_finished(&self) -> bool {
|
||||
self.inner.swipe_finished()
|
||||
fn get_internal_page_count(&self) -> usize {
|
||||
self.inner.get_internal_page_count()
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ pub mod placed;
|
||||
pub mod qr_code;
|
||||
#[cfg(feature = "touch")]
|
||||
pub mod swipe;
|
||||
#[cfg(feature = "touch")]
|
||||
pub mod swipe_detect;
|
||||
pub mod text;
|
||||
pub mod timeout;
|
||||
|
||||
@ -39,6 +41,8 @@ pub use placed::{FixedHeightBar, Floating, GridPlaced, Split};
|
||||
pub use qr_code::Qr;
|
||||
#[cfg(feature = "touch")]
|
||||
pub use swipe::{Swipe, SwipeDirection};
|
||||
#[cfg(feature = "touch")]
|
||||
pub use swipe_detect::{SwipeDetect, SwipeDetectMsg};
|
||||
pub use text::{
|
||||
formatted::FormattedText,
|
||||
layout::{LineBreaking, PageBreaking, TextLayout},
|
||||
|
@ -5,7 +5,7 @@ use crate::ui::{
|
||||
shape::Renderer,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum SwipeDirection {
|
||||
Up,
|
||||
Down,
|
||||
|
415
core/embed/rust/src/ui/component/swipe_detect.rs
Normal file
415
core/embed/rust/src/ui/component/swipe_detect.rs
Normal file
@ -0,0 +1,415 @@
|
||||
use crate::{
|
||||
time::{Duration, Instant},
|
||||
ui::{
|
||||
animation::Animation,
|
||||
component::{Event, EventCtx, SwipeDirection},
|
||||
event::TouchEvent,
|
||||
geometry::{Offset, Point},
|
||||
util::animation_disabled,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SwipeSettings {
|
||||
pub duration: Duration,
|
||||
}
|
||||
|
||||
impl SwipeSettings {
|
||||
pub const fn new(duration: Duration) -> Self {
|
||||
Self { duration }
|
||||
}
|
||||
|
||||
pub const fn default() -> Self {
|
||||
Self {
|
||||
duration: Duration::from_millis(333),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn immediate() -> Self {
|
||||
Self {
|
||||
duration: Duration::from_millis(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SwipeConfig {
|
||||
pub horizontal_pages: bool,
|
||||
pub vertical_pages: bool,
|
||||
pub up: Option<SwipeSettings>,
|
||||
pub down: Option<SwipeSettings>,
|
||||
pub left: Option<SwipeSettings>,
|
||||
pub right: Option<SwipeSettings>,
|
||||
}
|
||||
|
||||
impl SwipeConfig {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
horizontal_pages: false,
|
||||
vertical_pages: false,
|
||||
up: None,
|
||||
down: None,
|
||||
left: None,
|
||||
right: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_swipe(&self, dir: SwipeDirection, settings: SwipeSettings) -> Self {
|
||||
let mut new = self.clone();
|
||||
match dir {
|
||||
SwipeDirection::Up => new.up = Some(settings),
|
||||
SwipeDirection::Down => new.down = Some(settings),
|
||||
SwipeDirection::Left => new.left = Some(settings),
|
||||
SwipeDirection::Right => new.right = Some(settings),
|
||||
}
|
||||
new
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self, dir: SwipeDirection) -> bool {
|
||||
match dir {
|
||||
SwipeDirection::Up => self.up.is_some(),
|
||||
SwipeDirection::Down => self.down.is_some(),
|
||||
SwipeDirection::Left => self.left.is_some(),
|
||||
SwipeDirection::Right => self.right.is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration(&self, dir: SwipeDirection) -> Option<Duration> {
|
||||
match dir {
|
||||
SwipeDirection::Up => self.up.as_ref().map(|s| s.duration),
|
||||
SwipeDirection::Down => self.down.as_ref().map(|s| s.duration),
|
||||
SwipeDirection::Left => self.left.as_ref().map(|s| s.duration),
|
||||
SwipeDirection::Right => self.right.as_ref().map(|s| s.duration),
|
||||
}
|
||||
}
|
||||
pub fn has_horizontal_pages(&self) -> bool {
|
||||
self.horizontal_pages
|
||||
}
|
||||
|
||||
pub fn has_vertical_pages(&self) -> bool {
|
||||
self.vertical_pages
|
||||
}
|
||||
|
||||
pub fn with_horizontal_pages(&self) -> Self {
|
||||
let mut new = self.clone();
|
||||
new.horizontal_pages = true;
|
||||
new
|
||||
}
|
||||
|
||||
pub fn with_vertical_pages(&self) -> Self {
|
||||
let mut new = self.clone();
|
||||
new.vertical_pages = true;
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum SwipeDetectMsg {
|
||||
Move(SwipeDirection, i16),
|
||||
Trigger(SwipeDirection),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SwipeDetect {
|
||||
origin: Option<Point>,
|
||||
locked: Option<SwipeDirection>,
|
||||
final_animation: Option<Animation<i16>>,
|
||||
moved: i16,
|
||||
}
|
||||
|
||||
impl SwipeDetect {
|
||||
const DISTANCE: i16 = 120;
|
||||
pub const PROGRESS_MAX: i16 = 1000;
|
||||
|
||||
const DURATION_MS: u32 = 333;
|
||||
const TRIGGER_THRESHOLD: f32 = 0.3;
|
||||
const DETECT_THRESHOLD: f32 = 0.1;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
origin: None,
|
||||
locked: None,
|
||||
final_animation: None,
|
||||
moved: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn min_lock(&self) -> i16 {
|
||||
(Self::DISTANCE as f32 * Self::DETECT_THRESHOLD) as i16
|
||||
}
|
||||
|
||||
fn min_trigger(&self) -> i16 {
|
||||
(Self::DISTANCE as f32 * Self::TRIGGER_THRESHOLD) as i16
|
||||
}
|
||||
|
||||
fn progress(&self, val: i16) -> i16 {
|
||||
((val.max(0) as f32 / Self::DISTANCE as f32) * Self::PROGRESS_MAX as f32) as i16
|
||||
}
|
||||
|
||||
pub fn trigger(&mut self, ctx: &mut EventCtx, dir: SwipeDirection, config: SwipeConfig) {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
|
||||
let duration = config
|
||||
.duration(dir)
|
||||
.unwrap_or(Duration::from_millis(Self::DURATION_MS));
|
||||
|
||||
self.locked = Some(dir);
|
||||
self.final_animation = Some(Animation::new(
|
||||
0,
|
||||
Self::PROGRESS_MAX,
|
||||
duration,
|
||||
Instant::now(),
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.origin = None;
|
||||
self.locked = None;
|
||||
self.final_animation = None;
|
||||
self.moved = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn event(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
event: Event,
|
||||
config: SwipeConfig,
|
||||
) -> Option<SwipeDetectMsg> {
|
||||
match (event, self.origin) {
|
||||
(Event::Touch(TouchEvent::TouchStart(pos)), _) => {
|
||||
// Mark the starting position of this touch.
|
||||
self.origin.replace(pos);
|
||||
}
|
||||
(Event::Touch(TouchEvent::TouchMove(pos)), Some(origin)) => {
|
||||
if self.final_animation.is_none() {
|
||||
// Compare the touch distance with our allowed directions and determine if it
|
||||
// constitutes a valid swipe.
|
||||
let ofs = pos - origin;
|
||||
let ofs_min = ofs.abs() - Offset::new(self.min_lock(), self.min_lock());
|
||||
|
||||
let mut res = None;
|
||||
if self.locked.is_none() {
|
||||
if ofs.x > 0 && ofs_min.x > 0 && config.is_allowed(SwipeDirection::Right) {
|
||||
self.locked = Some(SwipeDirection::Right);
|
||||
res = Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Right,
|
||||
self.progress(ofs_min.x),
|
||||
));
|
||||
}
|
||||
if ofs.x < 0 && ofs_min.x > 0 && config.is_allowed(SwipeDirection::Left) {
|
||||
self.locked = Some(SwipeDirection::Left);
|
||||
res = Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Left,
|
||||
self.progress(ofs_min.x),
|
||||
));
|
||||
}
|
||||
if ofs.y < 0 && ofs_min.y > 0 && config.is_allowed(SwipeDirection::Up) {
|
||||
self.locked = Some(SwipeDirection::Up);
|
||||
res = Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Up,
|
||||
self.progress(ofs_min.y),
|
||||
));
|
||||
}
|
||||
if ofs.y > 0 && ofs_min.y > 0 && config.is_allowed(SwipeDirection::Down) {
|
||||
self.locked = Some(SwipeDirection::Down);
|
||||
res = Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Down,
|
||||
self.progress(ofs_min.y),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
res = match self.locked.unwrap() {
|
||||
SwipeDirection::Left => {
|
||||
if ofs.x > 0 {
|
||||
Some(SwipeDetectMsg::Move(SwipeDirection::Left, 0))
|
||||
} else {
|
||||
Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Left,
|
||||
self.progress(ofs_min.x),
|
||||
))
|
||||
}
|
||||
}
|
||||
SwipeDirection::Right => {
|
||||
if ofs.x < 0 {
|
||||
Some(SwipeDetectMsg::Move(SwipeDirection::Right, 0))
|
||||
} else {
|
||||
Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Right,
|
||||
self.progress(ofs_min.x),
|
||||
))
|
||||
}
|
||||
}
|
||||
SwipeDirection::Up => {
|
||||
if ofs.y > 0 {
|
||||
Some(SwipeDetectMsg::Move(SwipeDirection::Up, 0))
|
||||
} else {
|
||||
Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Up,
|
||||
self.progress(ofs_min.y),
|
||||
))
|
||||
}
|
||||
}
|
||||
SwipeDirection::Down => {
|
||||
if ofs.y < 0 {
|
||||
Some(SwipeDetectMsg::Move(SwipeDirection::Down, 0))
|
||||
} else {
|
||||
Some(SwipeDetectMsg::Move(
|
||||
SwipeDirection::Down,
|
||||
self.progress(ofs_min.y),
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Todo trigger an action if distance is met
|
||||
|
||||
if let Some(SwipeDetectMsg::Move(_, ofs)) = res {
|
||||
self.moved = ofs;
|
||||
}
|
||||
|
||||
if animation_disabled() {
|
||||
return None;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
(Event::Touch(TouchEvent::TouchEnd(pos)), Some(origin)) => {
|
||||
if self.final_animation.is_none() {
|
||||
// Touch interaction is over, reset the position.
|
||||
self.origin.take();
|
||||
|
||||
// Compare the touch distance with our allowed directions and determine if it
|
||||
// constitutes a valid swipe.
|
||||
let ofs = pos - origin;
|
||||
let ofs_min = ofs.abs() - Offset::new(self.min_trigger(), self.min_trigger());
|
||||
|
||||
match self.locked {
|
||||
// advance in locked direction only
|
||||
Some(locked) if config.progress(locked, ofs, 0) > 0 => (),
|
||||
// advance in direction other than locked clears the lock -- touch ends
|
||||
// without triggering
|
||||
Some(_) => self.locked = None,
|
||||
None => {
|
||||
for dir in SwipeDirection::iter() {
|
||||
// insta-lock if the movement went at least the trigger distance
|
||||
if config.progress(dir, ofs, self.min_trigger()) > 0 {
|
||||
self.locked = Some(dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let Some(locked) = self.locked else {
|
||||
// No direction is locked. Touch ended without triggering a swipe.
|
||||
return None;
|
||||
};
|
||||
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
|
||||
if !animation_disabled() {
|
||||
let done = self.moved as f32 / Self::PROGRESS_MAX as f32;
|
||||
let ratio = 1.0 - done;
|
||||
|
||||
let duration = config
|
||||
.duration(locked)
|
||||
.unwrap_or(Duration::from_millis(Self::DURATION_MS));
|
||||
|
||||
let duration = ((duration.to_millis() as f32 * ratio) as u32).max(0);
|
||||
self.final_animation = Some(Animation::new(
|
||||
self.moved as i16,
|
||||
Self::PROGRESS_MAX,
|
||||
Duration::from_millis(duration),
|
||||
Instant::now(),
|
||||
));
|
||||
} else {
|
||||
// clear animation
|
||||
self.final_animation = None;
|
||||
self.moved = 0;
|
||||
self.locked = None;
|
||||
return Some(SwipeDetectMsg::Trigger(locked));
|
||||
}
|
||||
|
||||
if finalize {
|
||||
if !animation_disabled() {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
|
||||
let done = self.moved as f32 / Self::PROGRESS_MAX as f32;
|
||||
let ratio = 1.0 - done;
|
||||
|
||||
let duration = config
|
||||
.duration(self.locked.unwrap())
|
||||
.unwrap_or(Duration::from_millis(Self::DURATION_MS));
|
||||
|
||||
let duration = ((duration.to_millis() as f32 * ratio) as u32).max(0);
|
||||
self.final_animation = Some(Animation::new(
|
||||
self.moved,
|
||||
final_value,
|
||||
Duration::from_millis(duration),
|
||||
Instant::now(),
|
||||
));
|
||||
} else {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
self.final_animation = None;
|
||||
self.moved = 0;
|
||||
let locked = self.locked.take();
|
||||
if final_value != 0 {
|
||||
return Some(SwipeDetectMsg::Trigger(locked.unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
(Event::Timer(EventCtx::ANIM_FRAME_TIMER), _) => {
|
||||
if self.locked.is_some() {
|
||||
let mut finish = false;
|
||||
let res = if let Some(animation) = &self.final_animation {
|
||||
if animation.finished(Instant::now()) {
|
||||
finish = true;
|
||||
if animation.to != 0 {
|
||||
Some(SwipeDetectMsg::Trigger(self.locked.unwrap()))
|
||||
} else {
|
||||
Some(SwipeDetectMsg::Move(self.locked.unwrap(), 0))
|
||||
}
|
||||
} else {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
if animation_disabled() {
|
||||
None
|
||||
} else {
|
||||
Some(SwipeDetectMsg::Move(
|
||||
self.locked.unwrap(),
|
||||
animation.value(Instant::now()),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if finish {
|
||||
self.locked = None;
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
self.final_animation = None;
|
||||
self.moved = 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ impl Component for Timeout {
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
match event {
|
||||
// Set up timer.
|
||||
Event::Attach => {
|
||||
Event::Attach(_) => {
|
||||
self.timer = Some(ctx.request_timer(Duration::from_millis(self.time_ms)));
|
||||
None
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
use crate::{error, ui::geometry::Point};
|
||||
use core::convert::TryInto;
|
||||
|
||||
#[cfg(feature = "touch")]
|
||||
use crate::ui::component::SwipeDirection;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PhysicalButton {
|
||||
Left,
|
||||
@ -65,3 +68,10 @@ pub enum USBEvent {
|
||||
/// USB host has connected/disconnected.
|
||||
Connected(bool),
|
||||
}
|
||||
|
||||
#[cfg(feature = "touch")]
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum SwipeEvent {
|
||||
Move(SwipeDirection, i16),
|
||||
End(SwipeDirection),
|
||||
}
|
||||
|
@ -1,50 +1,10 @@
|
||||
use crate::ui::component::{EventCtx, SwipeDirection};
|
||||
use crate::ui::component::{swipe_detect::SwipeConfig, SwipeDirection};
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
/// Component must implement this trait in order to be part of swipe-based flow.
|
||||
///
|
||||
/// Default implementation ignores every swipe.
|
||||
pub trait Swipable<T> {
|
||||
/// Attempt a swipe. Return `Ignored` if the component in its current state
|
||||
/// doesn't accept a swipe in that direction. Return `Animating` if
|
||||
/// component accepted the swipe and started a transition animation. The
|
||||
/// `Return(x)` variant indicates that the current flow should be terminated
|
||||
/// with the result `x`.
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
_ctx: &mut EventCtx,
|
||||
_direction: SwipeDirection,
|
||||
) -> SwipableResult<T> {
|
||||
SwipableResult::Ignored
|
||||
}
|
||||
pub trait Swipable {
|
||||
fn get_swipe_config(&self) -> SwipeConfig;
|
||||
|
||||
/// Return true when transition animation is finished. SwipeFlow needs to
|
||||
/// know this in order to resume normal input processing.
|
||||
fn swipe_finished(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SwipableResult<T> {
|
||||
Ignored,
|
||||
Animating,
|
||||
Return(T),
|
||||
}
|
||||
|
||||
impl<T> SwipableResult<T> {
|
||||
pub fn map<U>(self, func: impl FnOnce(T) -> Option<U>) -> SwipableResult<U> {
|
||||
match self {
|
||||
SwipableResult::Ignored => SwipableResult::Ignored,
|
||||
SwipableResult::Animating => SwipableResult::Animating,
|
||||
SwipableResult::Return(x) => {
|
||||
if let Some(res) = func(x) {
|
||||
SwipableResult::Return(res)
|
||||
} else {
|
||||
SwipableResult::Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn get_internal_page_count(&self) -> usize;
|
||||
}
|
||||
|
||||
/// Component::Msg for component parts of a flow. Converting results of
|
||||
|
@ -3,7 +3,7 @@ pub mod page;
|
||||
mod store;
|
||||
mod swipe;
|
||||
|
||||
pub use base::{FlowMsg, FlowState, Swipable, SwipableResult};
|
||||
pub use page::{IgnoreSwipe, SwipePage};
|
||||
pub use base::{FlowMsg, FlowState, Swipable};
|
||||
pub use page::SwipePage;
|
||||
pub use store::{flow_store, FlowStore};
|
||||
pub use swipe::SwipeFlow;
|
||||
|
@ -1,25 +1,10 @@
|
||||
use crate::{
|
||||
micropython::gc::Gc,
|
||||
time::Instant,
|
||||
ui::{
|
||||
animation::Animation,
|
||||
use crate::ui::{
|
||||
component::{Component, Event, EventCtx, Paginate, SwipeDirection},
|
||||
flow::base::{Swipable, SwipableResult},
|
||||
event::SwipeEvent,
|
||||
geometry::{Axis, Rect},
|
||||
shape::Renderer,
|
||||
util,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Transition<T> {
|
||||
/// Clone of the component before page change.
|
||||
cloned: Gc<T>,
|
||||
/// Animation progress.
|
||||
animation: Animation<f32>,
|
||||
/// Direction of the slide animation.
|
||||
direction: SwipeDirection,
|
||||
}
|
||||
|
||||
/// Allows any implementor of `Paginate` to be part of `Swipable` UI flow.
|
||||
/// Renders sliding animation when changing pages.
|
||||
pub struct SwipePage<T> {
|
||||
@ -28,7 +13,6 @@ pub struct SwipePage<T> {
|
||||
axis: Axis,
|
||||
pages: usize,
|
||||
current: usize,
|
||||
transition: Option<Transition<T>>,
|
||||
}
|
||||
|
||||
impl<T: Component + Paginate + Clone> SwipePage<T> {
|
||||
@ -39,7 +23,6 @@ impl<T: Component + Paginate + Clone> SwipePage<T> {
|
||||
axis: Axis::Vertical,
|
||||
pages: 1,
|
||||
current: 0,
|
||||
transition: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,38 +33,8 @@ impl<T: Component + Paginate + Clone> SwipePage<T> {
|
||||
axis: Axis::Horizontal,
|
||||
pages: 1,
|
||||
current: 0,
|
||||
transition: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_transition(ctx: &mut EventCtx, event: Event, transition: &mut Transition<T>) -> bool {
|
||||
let mut finished = false;
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if transition.animation.finished(Instant::now()) {
|
||||
finished = true;
|
||||
} else {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
ctx.request_paint()
|
||||
}
|
||||
finished
|
||||
}
|
||||
|
||||
fn render_transition<'s>(
|
||||
&'s self,
|
||||
transition: &'s Transition<T>,
|
||||
target: &mut impl Renderer<'s>,
|
||||
) {
|
||||
target.in_clip(self.bounds, &|target| {
|
||||
util::render_slide(
|
||||
|target| transition.cloned.render(target),
|
||||
|target| self.inner.render(target),
|
||||
transition.animation.value(Instant::now()),
|
||||
transition.direction,
|
||||
target,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component + Paginate + Clone> Component for SwipePage<T> {
|
||||
@ -95,14 +48,33 @@ impl<T: Component + Paginate + Clone> Component for SwipePage<T> {
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
ctx.set_page_count(self.pages);
|
||||
if let Some(t) = &mut self.transition {
|
||||
let finished = Self::handle_transition(ctx, event, t);
|
||||
if finished {
|
||||
// FIXME: how to ensure the Gc allocation is returned?
|
||||
self.transition = None
|
||||
|
||||
if let Event::Swipe(SwipeEvent::End(direction)) = event {
|
||||
match (self.axis, direction) {
|
||||
(Axis::Vertical, SwipeDirection::Up) => {
|
||||
self.current = (self.current + 1).min(self.pages - 1);
|
||||
self.inner.change_page(self.current);
|
||||
ctx.request_paint();
|
||||
}
|
||||
return None;
|
||||
(Axis::Vertical, SwipeDirection::Down) => {
|
||||
self.current = self.current.saturating_sub(1);
|
||||
self.inner.change_page(self.current);
|
||||
ctx.request_paint();
|
||||
}
|
||||
(Axis::Horizontal, SwipeDirection::Left) => {
|
||||
self.current = (self.current + 1).min(self.pages - 1);
|
||||
self.inner.change_page(self.current);
|
||||
ctx.request_paint();
|
||||
}
|
||||
(Axis::Horizontal, SwipeDirection::Right) => {
|
||||
self.current = self.current.saturating_sub(1);
|
||||
self.inner.change_page(self.current);
|
||||
ctx.request_paint();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.inner.event(ctx, event)
|
||||
}
|
||||
|
||||
@ -111,62 +83,10 @@ impl<T: Component + Paginate + Clone> Component for SwipePage<T> {
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
if let Some(t) = &self.transition {
|
||||
return self.render_transition(t, target);
|
||||
}
|
||||
self.inner.render(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component + Paginate + Clone> Swipable<T::Msg> for SwipePage<T> {
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
direction: SwipeDirection,
|
||||
) -> SwipableResult<T::Msg> {
|
||||
match (self.axis, direction) {
|
||||
// Wrong direction
|
||||
(Axis::Horizontal, SwipeDirection::Up | SwipeDirection::Down) => {
|
||||
return SwipableResult::Ignored
|
||||
}
|
||||
(Axis::Vertical, SwipeDirection::Left | SwipeDirection::Right) => {
|
||||
return SwipableResult::Ignored
|
||||
}
|
||||
// Begin
|
||||
(_, SwipeDirection::Right | SwipeDirection::Down) if self.current == 0 => {
|
||||
return SwipableResult::Ignored
|
||||
}
|
||||
// End
|
||||
(_, SwipeDirection::Left | SwipeDirection::Up) if self.current + 1 >= self.pages => {
|
||||
return SwipableResult::Ignored;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
self.current = match direction {
|
||||
SwipeDirection::Left | SwipeDirection::Up => (self.current + 1).min(self.pages - 1),
|
||||
SwipeDirection::Right | SwipeDirection::Down => self.current.saturating_sub(1),
|
||||
};
|
||||
if util::animation_disabled() {
|
||||
self.inner.change_page(self.current);
|
||||
ctx.request_paint();
|
||||
return SwipableResult::Animating;
|
||||
}
|
||||
self.transition = Some(Transition {
|
||||
cloned: unwrap!(Gc::new(self.inner.clone())),
|
||||
animation: Animation::new(0.0f32, 1.0f32, util::SLIDE_DURATION_MS, Instant::now()),
|
||||
direction,
|
||||
});
|
||||
self.inner.change_page(self.current);
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
SwipableResult::Animating
|
||||
}
|
||||
|
||||
fn swipe_finished(&self) -> bool {
|
||||
self.transition.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for SwipePage<T>
|
||||
where
|
||||
@ -176,44 +96,3 @@ where
|
||||
self.inner.trace(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Make any component swipable by ignoring all swipe events.
|
||||
pub struct IgnoreSwipe<T>(T);
|
||||
|
||||
impl<T> IgnoreSwipe<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
IgnoreSwipe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Component for IgnoreSwipe<T> {
|
||||
type Msg = T::Msg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.0.place(bounds)
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.0.event(ctx, event)
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.0.paint()
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.0.render(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Swipable<T::Msg> for IgnoreSwipe<T> {}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for IgnoreSwipe<T>
|
||||
where
|
||||
T: crate::trace::Trace,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
self.0.trace(t)
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,21 @@ use crate::{
|
||||
maybe_trace::MaybeTrace,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx},
|
||||
flow::base::{FlowMsg, Swipable},
|
||||
flow::base::FlowMsg,
|
||||
geometry::Rect,
|
||||
shape::Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::micropython::gc::Gc;
|
||||
use crate::{
|
||||
micropython::gc::Gc,
|
||||
ui::{component::swipe_detect::SwipeConfig, flow::Swipable},
|
||||
};
|
||||
|
||||
/// `FlowStore` is essentially `Vec<Gc<dyn Component + Swipable>>` except that
|
||||
/// `trait Component` is not object-safe so it ends up being a kind of
|
||||
/// `FlowStore` is essentially `Vec<Gc<dyn Component + SimpleSwipable>>` except
|
||||
/// that `trait Component` is not object-safe so it ends up being a kind of
|
||||
/// recursively-defined tuple.
|
||||
/// Implementors are something like the V in MVC.
|
||||
pub trait FlowStore {
|
||||
/// Call `Component::place` on all elements.
|
||||
fn place(&mut self, bounds: Rect) -> Rect;
|
||||
@ -28,15 +32,15 @@ pub trait FlowStore {
|
||||
/// Call `Trace::trace` on i-th element.
|
||||
fn trace(&self, i: usize, t: &mut dyn crate::trace::Tracer);
|
||||
|
||||
/// Forward `Swipable` methods to i-th element.
|
||||
fn map_swipable<T>(
|
||||
&mut self,
|
||||
i: usize,
|
||||
func: impl FnOnce(&mut dyn Swipable<FlowMsg>) -> T,
|
||||
) -> T;
|
||||
/// Forward `SimpleSwipable` methods to i-th element.
|
||||
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T;
|
||||
|
||||
fn get_swipe_config(&self, i: usize) -> SwipeConfig;
|
||||
|
||||
fn get_internal_page_count(&mut self, i: usize) -> usize;
|
||||
|
||||
/// Add a Component to the end of a `FlowStore`.
|
||||
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>>(
|
||||
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
|
||||
self,
|
||||
elem: E,
|
||||
) -> Result<impl FlowStore, error::Error>
|
||||
@ -62,7 +66,7 @@ impl FlowStore for FlowEmpty {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, _i: usize, _target: &mut impl Renderer<'s>) {
|
||||
fn render<'s>(&self, _i: usize, _target: &mut impl Renderer<'s>) {
|
||||
panic!()
|
||||
}
|
||||
|
||||
@ -71,29 +75,31 @@ impl FlowStore for FlowEmpty {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn map_swipable<T>(
|
||||
&mut self,
|
||||
_i: usize,
|
||||
_func: impl FnOnce(&mut dyn Swipable<FlowMsg>) -> T,
|
||||
) -> T {
|
||||
fn map_swipable<T>(&mut self, _i: usize, _func: impl FnOnce(&mut dyn Swipable) -> T) -> T {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>>(
|
||||
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
|
||||
self,
|
||||
elem: E,
|
||||
) -> Result<impl FlowStore, error::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(FlowComponent {
|
||||
Ok(FlowComponent2 {
|
||||
elem: Gc::new(elem)?,
|
||||
next: Self,
|
||||
})
|
||||
}
|
||||
fn get_swipe_config(&self, _i: usize) -> SwipeConfig {
|
||||
SwipeConfig::new()
|
||||
}
|
||||
fn get_internal_page_count(&mut self, _i: usize) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
struct FlowComponent<E: Component<Msg = FlowMsg>, P> {
|
||||
struct FlowComponent2<E: Component<Msg = FlowMsg>, P> {
|
||||
/// Component allocated on micropython heap.
|
||||
pub elem: Gc<E>,
|
||||
|
||||
@ -101,7 +107,7 @@ struct FlowComponent<E: Component<Msg = FlowMsg>, P> {
|
||||
pub next: P,
|
||||
}
|
||||
|
||||
impl<E: Component<Msg = FlowMsg>, P> FlowComponent<E, P> {
|
||||
impl<E: Component<Msg = FlowMsg>, P> FlowComponent2<E, P> {
|
||||
fn as_ref(&self) -> &E {
|
||||
&self.elem
|
||||
}
|
||||
@ -113,9 +119,9 @@ impl<E: Component<Msg = FlowMsg>, P> FlowComponent<E, P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, P> FlowStore for FlowComponent<E, P>
|
||||
impl<E, P> FlowStore for FlowComponent2<E, P>
|
||||
where
|
||||
E: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>,
|
||||
E: Component<Msg = FlowMsg> + MaybeTrace + Swipable,
|
||||
P: FlowStore,
|
||||
{
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -149,11 +155,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn map_swipable<T>(
|
||||
&mut self,
|
||||
i: usize,
|
||||
func: impl FnOnce(&mut dyn Swipable<FlowMsg>) -> T,
|
||||
) -> T {
|
||||
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T {
|
||||
if i == 0 {
|
||||
func(self.as_mut())
|
||||
} else {
|
||||
@ -161,16 +163,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn add<F: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>>(
|
||||
fn add<F: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
|
||||
self,
|
||||
elem: F,
|
||||
) -> Result<impl FlowStore, error::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(FlowComponent {
|
||||
Ok(FlowComponent2 {
|
||||
elem: self.elem,
|
||||
next: self.next.add(elem)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_swipe_config(&self, i: usize) -> SwipeConfig {
|
||||
if i == 0 {
|
||||
self.as_ref().get_swipe_config()
|
||||
} else {
|
||||
self.next.get_swipe_config(i - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_internal_page_count(&mut self, i: usize) -> usize {
|
||||
self.map_swipable(i, |swipable| swipable.get_internal_page_count())
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
use crate::{
|
||||
error,
|
||||
time::Instant,
|
||||
ui::{
|
||||
animation::Animation,
|
||||
component::{Component, Event, EventCtx, Swipe, SwipeDirection},
|
||||
flow::{base::Decision, FlowMsg, FlowState, FlowStore, SwipableResult},
|
||||
component::{
|
||||
base::AttachType, swipe_detect::SwipeSettings, Component, Event, EventCtx, SwipeDetect,
|
||||
SwipeDetectMsg, SwipeDirection,
|
||||
},
|
||||
event::SwipeEvent,
|
||||
flow::{base::Decision, FlowMsg, FlowState, FlowStore},
|
||||
geometry::Rect,
|
||||
shape::Renderer,
|
||||
util,
|
||||
util::animation_disabled,
|
||||
},
|
||||
};
|
||||
|
||||
@ -22,61 +24,54 @@ pub struct SwipeFlow<Q, S> {
|
||||
state: Q,
|
||||
/// FlowStore with all screens/components.
|
||||
store: S,
|
||||
/// `Transition::None` when state transition animation is in not progress.
|
||||
transition: Transition<Q>,
|
||||
/// Swipe detector.
|
||||
swipe: Swipe,
|
||||
}
|
||||
|
||||
enum Transition<Q> {
|
||||
/// SwipeFlow is performing transition between different states.
|
||||
External {
|
||||
/// State we are transitioning _from_.
|
||||
prev_state: Q,
|
||||
/// Animation progress.
|
||||
animation: Animation<f32>,
|
||||
/// Direction of the slide animation.
|
||||
direction: SwipeDirection,
|
||||
},
|
||||
/// Transition runs in child component, we forward events and wait.
|
||||
Internal,
|
||||
/// No transition.
|
||||
None,
|
||||
swipe: SwipeDetect,
|
||||
/// Swipe allowed
|
||||
allow_swipe: bool,
|
||||
/// Current internal state
|
||||
internal_state: u16,
|
||||
/// Internal pages count
|
||||
internal_pages: u16,
|
||||
/// If triggering swipe by event, make this decision instead of default
|
||||
/// after the swipe.
|
||||
decision_override: Option<Decision<Q>>,
|
||||
}
|
||||
|
||||
impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
|
||||
pub fn new(init: Q, store: S) -> Result<Self, error::Error> {
|
||||
Ok(Self {
|
||||
state: init,
|
||||
swipe: SwipeDetect::new(),
|
||||
store,
|
||||
transition: Transition::None,
|
||||
swipe: Swipe::new().down().up().left().right(),
|
||||
allow_swipe: true,
|
||||
internal_state: 0,
|
||||
internal_pages: 1,
|
||||
decision_override: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn goto(&mut self, ctx: &mut EventCtx, direction: SwipeDirection, state: Q) {
|
||||
if util::animation_disabled() {
|
||||
if state == self.state {
|
||||
assert!(self
|
||||
.store
|
||||
.map_swipable(state.index(), |s| s.swipe_finished()));
|
||||
}
|
||||
self.state = state;
|
||||
self.store.event(state.index(), ctx, Event::Attach);
|
||||
ctx.request_paint();
|
||||
return;
|
||||
self.swipe = SwipeDetect::new();
|
||||
self.allow_swipe = true;
|
||||
|
||||
self.store.event(
|
||||
state.index(),
|
||||
ctx,
|
||||
Event::Attach(AttachType::Swipe(direction)),
|
||||
);
|
||||
|
||||
self.internal_pages = self.store.get_internal_page_count(state.index()) as u16;
|
||||
|
||||
match direction {
|
||||
SwipeDirection::Up => {
|
||||
self.internal_state = 0;
|
||||
}
|
||||
if state == self.state {
|
||||
self.transition = Transition::Internal;
|
||||
return;
|
||||
SwipeDirection::Down => {
|
||||
self.internal_state = self.internal_pages.saturating_sub(1);
|
||||
}
|
||||
self.transition = Transition::External {
|
||||
prev_state: self.state,
|
||||
animation: Animation::new(0.0f32, 1.0f32, util::SLIDE_DURATION_MS, Instant::now()),
|
||||
direction,
|
||||
};
|
||||
self.state = state;
|
||||
ctx.request_anim_frame();
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ctx.request_paint();
|
||||
}
|
||||
|
||||
@ -84,69 +79,17 @@ impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
|
||||
self.store.render(state.index(), target)
|
||||
}
|
||||
|
||||
fn render_transition<'s>(
|
||||
&'s self,
|
||||
prev_state: &Q,
|
||||
animation: &Animation<f32>,
|
||||
direction: &SwipeDirection,
|
||||
target: &mut impl Renderer<'s>,
|
||||
) {
|
||||
util::render_slide(
|
||||
|target| self.render_state(*prev_state, target),
|
||||
|target| self.render_state(self.state, target),
|
||||
animation.value(Instant::now()),
|
||||
*direction,
|
||||
target,
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_transition(&mut self, ctx: &mut EventCtx, event: Event) -> Option<FlowMsg> {
|
||||
let i = self.state.index();
|
||||
let mut finished = false;
|
||||
let result = match &self.transition {
|
||||
Transition::External { animation, .. }
|
||||
if matches!(event, Event::Timer(EventCtx::ANIM_FRAME_TIMER)) =>
|
||||
{
|
||||
if animation.finished(Instant::now()) {
|
||||
finished = true;
|
||||
ctx.request_paint();
|
||||
self.store.event(i, ctx, Event::Attach)
|
||||
} else {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
None
|
||||
}
|
||||
}
|
||||
Transition::External { .. } => None, // ignore all events until animation finishes
|
||||
Transition::Internal => {
|
||||
let msg = self.store.event(i, ctx, event);
|
||||
if self.store.map_swipable(i, |s| s.swipe_finished()) {
|
||||
finished = true;
|
||||
};
|
||||
msg
|
||||
}
|
||||
Transition::None => unreachable!(),
|
||||
};
|
||||
if finished {
|
||||
self.transition = Transition::None;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn handle_swipe_child(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> Decision<Q> {
|
||||
let i = self.state.index();
|
||||
match self
|
||||
.store
|
||||
.map_swipable(i, |s| s.swipe_start(ctx, direction))
|
||||
{
|
||||
SwipableResult::Ignored => Decision::Nothing,
|
||||
SwipableResult::Animating => Decision::Goto(self.state, direction),
|
||||
SwipableResult::Return(x) => Decision::Return(x),
|
||||
}
|
||||
fn handle_swipe_child(
|
||||
&mut self,
|
||||
_ctx: &mut EventCtx,
|
||||
direction: SwipeDirection,
|
||||
) -> Decision<Q> {
|
||||
self.state.handle_swipe(direction)
|
||||
}
|
||||
|
||||
fn handle_event_child(&mut self, ctx: &mut EventCtx, event: Event) -> Decision<Q> {
|
||||
let msg = self.store.event(self.state.index(), ctx, event);
|
||||
|
||||
if let Some(msg) = msg {
|
||||
self.state.handle_event(msg)
|
||||
} else {
|
||||
@ -159,44 +102,146 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
|
||||
type Msg = FlowMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.swipe.place(bounds);
|
||||
self.store.place(bounds)
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if !matches!(self.transition, Transition::None) {
|
||||
return self.handle_transition(ctx, event);
|
||||
let mut decision: Decision<Q> = Decision::Nothing;
|
||||
|
||||
let mut attach = false;
|
||||
|
||||
let e = if self.allow_swipe {
|
||||
let mut config = self.store.get_swipe_config(self.state.index());
|
||||
|
||||
self.internal_pages = self.store.get_internal_page_count(self.state.index()) as u16;
|
||||
|
||||
// add additional swipe directions if there are more internal pages
|
||||
// todo can we get internal settings from config somehow?
|
||||
// might wanna different duration or something
|
||||
if config.vertical_pages && self.internal_state > 0 {
|
||||
config = config.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
}
|
||||
if config.horizontal_pages && self.internal_state > 0 {
|
||||
config = config.with_swipe(SwipeDirection::Right, SwipeSettings::default())
|
||||
}
|
||||
if config.vertical_pages && self.internal_state < self.internal_pages - 1 {
|
||||
config = config.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
}
|
||||
if config.horizontal_pages && self.internal_state < self.internal_pages - 1 {
|
||||
config = config.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
}
|
||||
|
||||
let mut decision = Decision::Nothing;
|
||||
if let Some(direction) = self.swipe.event(ctx, event) {
|
||||
decision = self
|
||||
.handle_swipe_child(ctx, direction)
|
||||
.or_else(|| self.state.handle_swipe(direction));
|
||||
match self.swipe.event(ctx, event, config.clone()) {
|
||||
Some(SwipeDetectMsg::Trigger(dir)) => {
|
||||
if let Some(override_decision) = self.decision_override.take() {
|
||||
decision = override_decision;
|
||||
} else {
|
||||
decision = self.handle_swipe_child(ctx, dir);
|
||||
}
|
||||
|
||||
let states_num = self.internal_pages;
|
||||
if states_num > 0 {
|
||||
if config.has_horizontal_pages() {
|
||||
let current_state = self.internal_state;
|
||||
if dir == SwipeDirection::Left && current_state < states_num - 1 {
|
||||
self.internal_state += 1;
|
||||
decision = Decision::Nothing;
|
||||
attach = true;
|
||||
} else if dir == SwipeDirection::Right && current_state > 0 {
|
||||
self.internal_state -= 1;
|
||||
decision = Decision::Nothing;
|
||||
attach = true;
|
||||
}
|
||||
}
|
||||
if config.has_vertical_pages() {
|
||||
let current_state = self.internal_state;
|
||||
if dir == SwipeDirection::Up && current_state < states_num - 1 {
|
||||
self.internal_state += 1;
|
||||
decision = Decision::Nothing;
|
||||
attach = true;
|
||||
} else if dir == SwipeDirection::Down && current_state > 0 {
|
||||
self.internal_state -= 1;
|
||||
decision = Decision::Nothing;
|
||||
attach = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Event::Swipe(SwipeEvent::End(dir)))
|
||||
}
|
||||
Some(SwipeDetectMsg::Move(dir, progress)) => {
|
||||
Some(Event::Swipe(SwipeEvent::Move(dir, progress)))
|
||||
}
|
||||
_ => Some(event),
|
||||
}
|
||||
} else {
|
||||
Some(event)
|
||||
};
|
||||
|
||||
if let Some(e) = e {
|
||||
match decision {
|
||||
Decision::Nothing => {
|
||||
decision = self.handle_event_child(ctx, e);
|
||||
|
||||
// when doing internal transition, pass attach event to the child after sending
|
||||
// swipe end.
|
||||
if attach {
|
||||
if let Event::Swipe(SwipeEvent::End(dir)) = e {
|
||||
self.store.event(
|
||||
self.state.index(),
|
||||
ctx,
|
||||
Event::Attach(AttachType::Swipe(dir)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.disable_swipe_requested() {
|
||||
self.swipe.reset();
|
||||
self.allow_swipe = false;
|
||||
}
|
||||
if ctx.enable_swipe_requested() {
|
||||
self.swipe.reset();
|
||||
self.allow_swipe = true;
|
||||
};
|
||||
|
||||
let config = self.store.get_swipe_config(self.state.index());
|
||||
|
||||
if let Decision::Goto(_, direction) = decision {
|
||||
if config.is_allowed(direction) {
|
||||
if !animation_disabled() {
|
||||
self.swipe.trigger(ctx, direction, config);
|
||||
self.decision_override = Some(decision);
|
||||
decision = Decision::Nothing;
|
||||
}
|
||||
self.allow_swipe = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
//ignore message, we are already transitioning
|
||||
self.store.event(self.state.index(), ctx, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
decision = decision.or_else(|| self.handle_event_child(ctx, event));
|
||||
|
||||
match decision {
|
||||
Decision::Nothing => None,
|
||||
Decision::Goto(next_state, direction) => {
|
||||
self.goto(ctx, direction, next_state);
|
||||
None
|
||||
}
|
||||
Decision::Return(msg) => Some(msg),
|
||||
Decision::Return(msg) => {
|
||||
self.swipe.reset();
|
||||
self.allow_swipe = true;
|
||||
Some(msg)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self) {}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
match &self.transition {
|
||||
Transition::None | Transition::Internal => self.render_state(self.state, target),
|
||||
Transition::External {
|
||||
prev_state,
|
||||
animation,
|
||||
direction,
|
||||
} => self.render_transition(prev_state, animation, direction, target),
|
||||
}
|
||||
self.render_state(self.state, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::ui::component::base::AttachType;
|
||||
#[cfg(feature = "new_rendering")]
|
||||
use crate::ui::{display::Color, shape::render_on_display};
|
||||
|
||||
@ -379,7 +380,7 @@ extern "C" fn ui_layout_attach_timer_fn(this: Obj, timer_fn: Obj) -> Obj {
|
||||
let block = || {
|
||||
let this: Gc<LayoutObj> = this.try_into()?;
|
||||
this.obj_set_timer_fn(timer_fn);
|
||||
let msg = this.obj_event(Event::Attach)?;
|
||||
let msg = this.obj_event(Event::Attach(AttachType::Initial))?;
|
||||
assert!(msg == Obj::const_none());
|
||||
Ok(Obj::const_none())
|
||||
};
|
||||
|
@ -6,9 +6,12 @@ use crate::{
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{
|
||||
swipe_detect::{SwipeConfig, SwipeSettings},
|
||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||
Component, Event, EventCtx, Paginate,
|
||||
Component, Event, EventCtx, Paginate, SwipeDirection,
|
||||
},
|
||||
event::SwipeEvent,
|
||||
flow::Swipable,
|
||||
geometry::Rect,
|
||||
shape::Renderer,
|
||||
},
|
||||
@ -56,12 +59,15 @@ impl AddressDetails {
|
||||
}
|
||||
let result = Self {
|
||||
details: Frame::left_aligned(details_title, para.into_paragraphs())
|
||||
.with_cancel_button(),
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.with_horizontal_pages(),
|
||||
xpub_view: Frame::left_aligned(
|
||||
" \n ".into(),
|
||||
Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, "").into_paragraphs(),
|
||||
)
|
||||
.with_cancel_button(),
|
||||
.with_cancel_button()
|
||||
.with_horizontal_pages(),
|
||||
xpubs: Vec::new(),
|
||||
xpub_page_count: Vec::new(),
|
||||
current_page: 0,
|
||||
@ -113,8 +119,7 @@ impl AddressDetails {
|
||||
|
||||
impl Paginate for AddressDetails {
|
||||
fn page_count(&mut self) -> usize {
|
||||
let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum();
|
||||
1usize.saturating_add(total_xpub_pages.into())
|
||||
self.get_internal_page_count()
|
||||
}
|
||||
|
||||
fn change_page(&mut self, to_page: usize) {
|
||||
@ -144,6 +149,22 @@ impl Component for AddressDetails {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
ctx.set_page_count(self.page_count());
|
||||
match event {
|
||||
Event::Swipe(SwipeEvent::End(SwipeDirection::Right)) => {
|
||||
let to_page = self.current_page.saturating_sub(1);
|
||||
self.change_page(to_page);
|
||||
}
|
||||
Event::Swipe(SwipeEvent::End(SwipeDirection::Left)) => {
|
||||
let to_page = self
|
||||
.current_page
|
||||
.saturating_add(1)
|
||||
.min(self.page_count() - 1);
|
||||
self.change_page(to_page);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let msg = match self.current_page {
|
||||
0 => self.details.event(ctx, event),
|
||||
_ => self.xpub_view.event(ctx, event),
|
||||
@ -177,6 +198,20 @@ impl Component for AddressDetails {
|
||||
}
|
||||
}
|
||||
|
||||
impl Swipable for AddressDetails {
|
||||
fn get_swipe_config(&self) -> SwipeConfig {
|
||||
match self.current_page {
|
||||
0 => self.details.get_swipe_config(),
|
||||
_ => self.xpub_view.get_swipe_config(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_internal_page_count(&self) -> usize {
|
||||
let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum();
|
||||
1usize.saturating_add(total_xpub_pages.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for AddressDetails {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -350,6 +350,7 @@ impl Component for Button {
|
||||
if let Some(duration) = self.long_press {
|
||||
self.long_timer = Some(ctx.request_timer(duration));
|
||||
}
|
||||
ctx.disable_swipe();
|
||||
return Some(ButtonMsg::Pressed);
|
||||
}
|
||||
}
|
||||
@ -360,6 +361,7 @@ impl Component for Button {
|
||||
State::Pressed if !touch_area.contains(pos) => {
|
||||
// Touch is leaving our area, transform to `Released` state.
|
||||
self.set(ctx, State::Released);
|
||||
ctx.enable_swipe();
|
||||
return Some(ButtonMsg::Released);
|
||||
}
|
||||
_ => {
|
||||
@ -375,11 +377,13 @@ impl Component for Button {
|
||||
State::Pressed if touch_area.contains(pos) => {
|
||||
// Touch finished in our area, we got clicked.
|
||||
self.set(ctx, State::Initial);
|
||||
ctx.enable_swipe();
|
||||
return Some(ButtonMsg::Clicked);
|
||||
}
|
||||
_ => {
|
||||
// Touch finished outside our area.
|
||||
self.set(ctx, State::Initial);
|
||||
ctx.enable_swipe();
|
||||
self.long_timer = None;
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ where
|
||||
_ if animation_disabled() => {
|
||||
return None;
|
||||
}
|
||||
Event::Attach if self.indeterminate => {
|
||||
Event::Attach(_) if self.indeterminate => {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
|
||||
|
@ -228,6 +228,3 @@ where
|
||||
t.child("controls", &self.controls);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl<U: Component> crate::ui::flow::Swipable<U::Msg> for IconDialog<U> {}
|
||||
|
@ -1,9 +1,13 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{text::TextStyle, Component, Event, EventCtx, Never},
|
||||
component::{text::TextStyle, Component, Event, EventCtx, Never, SwipeDirection},
|
||||
display::Color,
|
||||
event::SwipeEvent,
|
||||
geometry::{Alignment, Offset, Rect},
|
||||
lerp::Lerp,
|
||||
model_mercury::theme,
|
||||
shape,
|
||||
shape::{Renderer, Text},
|
||||
},
|
||||
};
|
||||
@ -21,6 +25,10 @@ pub struct Footer<'a> {
|
||||
text_description: Option<TString<'a>>,
|
||||
style_instruction: &'static TextStyle,
|
||||
style_description: &'static TextStyle,
|
||||
swipe_allow_up: bool,
|
||||
swipe_allow_down: bool,
|
||||
progress: i16,
|
||||
dir: SwipeDirection,
|
||||
}
|
||||
|
||||
impl<'a> Footer<'a> {
|
||||
@ -36,6 +44,10 @@ impl<'a> Footer<'a> {
|
||||
text_description: None,
|
||||
style_instruction: &theme::TEXT_SUB_GREY,
|
||||
style_description: &theme::TEXT_SUB_GREY_LIGHT,
|
||||
swipe_allow_down: false,
|
||||
swipe_allow_up: false,
|
||||
progress: 0,
|
||||
dir: SwipeDirection::Up,
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +85,19 @@ impl<'a> Footer<'a> {
|
||||
Footer::HEIGHT_SIMPLE
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_swipe_up(self) -> Self {
|
||||
Self {
|
||||
swipe_allow_up: true,
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn with_swipe_down(self) -> Self {
|
||||
Self {
|
||||
swipe_allow_down: true,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Component for Footer<'a> {
|
||||
@ -85,7 +110,29 @@ impl<'a> Component for Footer<'a> {
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
match event {
|
||||
Event::Attach(_) => {
|
||||
self.progress = 0;
|
||||
}
|
||||
Event::Swipe(SwipeEvent::Move(dir, progress)) => match dir {
|
||||
SwipeDirection::Up => {
|
||||
if self.swipe_allow_up {
|
||||
self.progress = progress;
|
||||
self.dir = dir;
|
||||
}
|
||||
}
|
||||
SwipeDirection::Down => {
|
||||
if self.swipe_allow_down {
|
||||
self.progress = progress;
|
||||
self.dir = dir;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@ -95,6 +142,26 @@ impl<'a> Component for Footer<'a> {
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let progress = self.progress as f32 / 1000.0;
|
||||
|
||||
let shift = pareen::constant(0.0).seq_ease_out(
|
||||
0.0,
|
||||
easer::functions::Cubic,
|
||||
1.0,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
|
||||
let offset = i16::lerp(0, 20, shift.eval(progress));
|
||||
|
||||
let mask = u8::lerp(0, 255, shift.eval(progress));
|
||||
|
||||
let offset = match self.dir {
|
||||
SwipeDirection::Up => Offset::y(-offset),
|
||||
SwipeDirection::Down => Offset::y(3 * offset),
|
||||
_ => Offset::zero(),
|
||||
};
|
||||
|
||||
target.with_origin(offset, &|target| {
|
||||
// show description only if there is space for it
|
||||
if self.area.height() == Footer::HEIGHT_DEFAULT {
|
||||
if let Some(description) = self.text_description {
|
||||
@ -132,6 +199,13 @@ impl<'a> Component for Footer<'a> {
|
||||
.with_align(Alignment::Center)
|
||||
.render(target);
|
||||
});
|
||||
|
||||
shape::Bar::new(self.area)
|
||||
.with_alpha(mask)
|
||||
.with_fg(Color::black())
|
||||
.with_bg(Color::black())
|
||||
.render(target);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_bounds")]
|
||||
|
@ -2,11 +2,20 @@ use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{
|
||||
base::ComponentExt, label::Label, text::TextStyle, Child, Component, Event, EventCtx,
|
||||
base::ComponentExt,
|
||||
label::Label,
|
||||
swipe_detect::{SwipeConfig, SwipeSettings},
|
||||
text::TextStyle,
|
||||
Child, Component, Event,
|
||||
Event::Swipe,
|
||||
EventCtx, SwipeDetect, SwipeDirection,
|
||||
},
|
||||
display::Icon,
|
||||
geometry::{Alignment, Insets, Rect},
|
||||
event::SwipeEvent,
|
||||
geometry::{Alignment, Insets, Point, Rect},
|
||||
lerp::Lerp,
|
||||
model_mercury::theme::TITLE_HEIGHT,
|
||||
shape,
|
||||
shape::Renderer,
|
||||
},
|
||||
};
|
||||
@ -18,13 +27,17 @@ const BUTTON_EXPAND_BORDER: i16 = 32;
|
||||
#[derive(Clone)]
|
||||
pub struct Frame<T> {
|
||||
border: Insets,
|
||||
bounds: Rect,
|
||||
title: Child<Label<'static>>,
|
||||
subtitle: Option<Child<Label<'static>>>,
|
||||
button: Option<Child<Button>>,
|
||||
button_msg: CancelInfoConfirmMsg,
|
||||
content: Child<T>,
|
||||
footer: Option<Footer<'static>>,
|
||||
overlapping_content: bool,
|
||||
swipe: SwipeConfig,
|
||||
internal_page_cnt: usize,
|
||||
progress: i16,
|
||||
dir: SwipeDirection,
|
||||
}
|
||||
|
||||
pub enum FrameMsg<T> {
|
||||
@ -41,13 +54,17 @@ where
|
||||
title: Child::new(
|
||||
Label::new(title, alignment, theme::label_title_main()).vertically_centered(),
|
||||
),
|
||||
bounds: Rect::zero(),
|
||||
subtitle: None,
|
||||
border: theme::borders(),
|
||||
button: None,
|
||||
button_msg: CancelInfoConfirmMsg::Cancelled,
|
||||
content: Child::new(content),
|
||||
footer: None,
|
||||
overlapping_content: false,
|
||||
swipe: SwipeConfig::new(),
|
||||
internal_page_cnt: 1,
|
||||
progress: 0,
|
||||
dir: SwipeDirection::Up,
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,6 +172,31 @@ where
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_swipe(self, dir: SwipeDirection, settings: SwipeSettings) -> Self {
|
||||
Self {
|
||||
footer: self.footer.map(|f| match dir {
|
||||
SwipeDirection::Up => f.with_swipe_up(),
|
||||
SwipeDirection::Down => f.with_swipe_down(),
|
||||
_ => f,
|
||||
}),
|
||||
swipe: self.swipe.with_swipe(dir, settings),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_horizontal_pages(self) -> Self {
|
||||
Self {
|
||||
swipe: self.swipe.with_horizontal_pages(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn with_vertical_pages(self) -> Self {
|
||||
Self {
|
||||
swipe: self.swipe.with_vertical_pages(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for Frame<T>
|
||||
@ -164,6 +206,8 @@ where
|
||||
type Msg = FrameMsg<T::Msg>;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.bounds = bounds;
|
||||
|
||||
let (mut header_area, mut content_area) = bounds.split_top(TITLE_HEIGHT);
|
||||
content_area = content_area.inset(Insets::top(theme::SPACING));
|
||||
header_area = header_area.inset(Insets::sides(theme::SPACING));
|
||||
@ -190,18 +234,37 @@ where
|
||||
footer.place(footer_area);
|
||||
content_area = remaining;
|
||||
}
|
||||
if self.overlapping_content {
|
||||
self.content.place(bounds);
|
||||
} else {
|
||||
|
||||
self.content.place(content_area);
|
||||
}
|
||||
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Event::Attach(_) = event {
|
||||
self.progress = 0;
|
||||
}
|
||||
|
||||
if let Swipe(SwipeEvent::Move(dir, progress)) = event {
|
||||
if self.swipe.is_allowed(dir) {
|
||||
match dir {
|
||||
SwipeDirection::Left | SwipeDirection::Right => {
|
||||
self.progress = progress;
|
||||
self.dir = dir;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.title.event(ctx, event);
|
||||
self.subtitle.event(ctx, event);
|
||||
self.footer.event(ctx, event);
|
||||
let msg = self.content.event(ctx, event).map(FrameMsg::Content);
|
||||
if let Some(count) = ctx.page_count() {
|
||||
self.internal_page_cnt = count;
|
||||
}
|
||||
|
||||
if msg.is_some() {
|
||||
return msg;
|
||||
}
|
||||
@ -224,6 +287,32 @@ where
|
||||
self.button.render(target);
|
||||
self.footer.render(target);
|
||||
self.content.render(target);
|
||||
|
||||
if self.progress > 0 {
|
||||
match self.dir {
|
||||
SwipeDirection::Left => {
|
||||
let shift = pareen::constant(0.0).seq_ease_out(
|
||||
0.0,
|
||||
easer::functions::Circ,
|
||||
1.0,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
|
||||
let p = Point::lerp(
|
||||
self.bounds.top_right(),
|
||||
self.bounds.top_left(),
|
||||
shift.eval(self.progress as f32 / SwipeDetect::PROGRESS_MAX as f32),
|
||||
);
|
||||
|
||||
shape::Bar::new(Rect::new(p, self.bounds.bottom_right()))
|
||||
.with_fg(theme::BLACK)
|
||||
.with_bg(theme::BLACK)
|
||||
.render(target);
|
||||
}
|
||||
SwipeDirection::Right => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_bounds")]
|
||||
@ -236,6 +325,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl<T> crate::ui::flow::Swipable for Frame<T> {
|
||||
fn get_swipe_config(&self) -> SwipeConfig {
|
||||
self.swipe
|
||||
}
|
||||
|
||||
fn get_internal_page_count(&self) -> usize {
|
||||
self.internal_page_cnt
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for Frame<T>
|
||||
where
|
||||
@ -256,22 +356,3 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl<T> crate::ui::flow::Swipable<FrameMsg<T::Msg>> for Frame<T>
|
||||
where
|
||||
T: Component + crate::ui::flow::Swipable<T::Msg>,
|
||||
{
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
direction: crate::ui::component::SwipeDirection,
|
||||
) -> crate::ui::flow::SwipableResult<FrameMsg<T::Msg>> {
|
||||
self.update_content(ctx, |ctx, inner| inner.swipe_start(ctx, direction))
|
||||
.map(|x| Some(FrameMsg::Content(x)))
|
||||
}
|
||||
|
||||
fn swipe_finished(&self) -> bool {
|
||||
self.inner().swipe_finished()
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,10 @@ impl HoldToConfirmAnim {
|
||||
const DURATION_MS: u32 = 2200;
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
if animation_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.timer
|
||||
.is_running_within(Duration::from_millis(Self::DURATION_MS))
|
||||
}
|
||||
@ -332,9 +336,6 @@ impl Component for HoldToConfirm {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl crate::ui::flow::Swipable<()> for HoldToConfirm {}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for HoldToConfirm {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -328,12 +328,12 @@ impl Component for Lockscreen {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if event == Event::Attach {
|
||||
if let Event::Attach(_) = event {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if (!animation_disabled()) {
|
||||
if !animation_disabled() {
|
||||
if !self.anim.timer.is_running() {
|
||||
self.anim.timer.start();
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ impl Component for PinKeyboard<'_> {
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
match event {
|
||||
// Set up timer to switch off warning prompt.
|
||||
Event::Attach if self.major_warning.is_some() => {
|
||||
Event::Attach(_) if self.major_warning.is_some() => {
|
||||
self.warning_timer = Some(ctx.request_timer(Duration::from_secs(2)));
|
||||
}
|
||||
// Hide warning, show major prompt.
|
||||
|
@ -36,6 +36,8 @@ mod set_brightness;
|
||||
mod share_words;
|
||||
mod simple_page;
|
||||
mod status_screen;
|
||||
mod swipe_content;
|
||||
#[cfg(feature = "translations")]
|
||||
mod swipe_up_screen;
|
||||
#[cfg(feature = "translations")]
|
||||
mod tap_to_confirm;
|
||||
@ -85,6 +87,8 @@ pub use set_brightness::SetBrightnessDialog;
|
||||
pub use share_words::ShareWords;
|
||||
pub use simple_page::SimplePage;
|
||||
pub use status_screen::StatusScreen;
|
||||
pub use swipe_content::SwipeContent;
|
||||
#[cfg(feature = "translations")]
|
||||
pub use swipe_up_screen::{SwipeUpScreen, SwipeUpScreenMsg};
|
||||
#[cfg(feature = "translations")]
|
||||
pub use tap_to_confirm::TapToConfirm;
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
Child, Component, Event, EventCtx, Pad, SwipeDirection,
|
||||
},
|
||||
display::Font,
|
||||
flow::{Swipable, SwipableResult},
|
||||
event::SwipeEvent,
|
||||
geometry::{Alignment, Grid, Insets, Offset, Rect},
|
||||
shape::{self, Renderer},
|
||||
},
|
||||
@ -89,6 +89,10 @@ where
|
||||
if let Some(NumberInputMsg::Changed(i)) = self.input.event(ctx, event) {
|
||||
self.update_text(ctx, i);
|
||||
}
|
||||
|
||||
if let Event::Swipe(SwipeEvent::End(SwipeDirection::Up)) = event {
|
||||
return Some(NumberInputDialogMsg(self.input.inner().value));
|
||||
}
|
||||
self.paragraphs.event(ctx, event);
|
||||
None
|
||||
}
|
||||
@ -111,26 +115,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Swipable<NumberInputDialogMsg> for NumberInputDialog<F>
|
||||
where
|
||||
F: Fn(u32) -> TString<'static>,
|
||||
{
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
_ctx: &mut EventCtx,
|
||||
direction: SwipeDirection,
|
||||
) -> SwipableResult<NumberInputDialogMsg> {
|
||||
match direction {
|
||||
SwipeDirection::Up => SwipableResult::Return(NumberInputDialogMsg(self.value())),
|
||||
_ => SwipableResult::Ignored,
|
||||
}
|
||||
}
|
||||
|
||||
fn swipe_finished(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<F> crate::trace::Trace for NumberInputDialog<F>
|
||||
where
|
||||
|
@ -68,9 +68,6 @@ impl Component for PromptScreen {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl crate::ui::flow::Swipable<()> for PromptScreen {}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for PromptScreen {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -1,12 +1,12 @@
|
||||
use super::theme;
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
time::{Duration, Instant},
|
||||
time::Duration,
|
||||
translations::TR,
|
||||
ui::{
|
||||
animation::Animation,
|
||||
component::{Component, Event, EventCtx, Never, SwipeDirection},
|
||||
flow::{Swipable, SwipableResult},
|
||||
event::SwipeEvent,
|
||||
geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
|
||||
model_mercury::component::Footer,
|
||||
shape,
|
||||
@ -25,14 +25,15 @@ const ANIMATION_DURATION_MS: Duration = Duration::from_millis(166);
|
||||
pub struct ShareWords<'a> {
|
||||
area: Rect,
|
||||
share_words: Vec<TString<'a>, MAX_WORDS>,
|
||||
page_index: usize,
|
||||
prev_index: usize,
|
||||
page_index: i16,
|
||||
next_index: i16,
|
||||
/// Area reserved for a shown word from mnemonic/share
|
||||
area_word: Rect,
|
||||
/// `Some` when transition animation is in progress
|
||||
animation: Option<Animation<f32>>,
|
||||
/// Footer component for instructions and word counting
|
||||
footer: Footer<'static>,
|
||||
progress: i16,
|
||||
}
|
||||
|
||||
impl<'a> ShareWords<'a> {
|
||||
@ -43,10 +44,11 @@ impl<'a> ShareWords<'a> {
|
||||
area: Rect::zero(),
|
||||
share_words,
|
||||
page_index: 0,
|
||||
prev_index: 0,
|
||||
next_index: 0,
|
||||
area_word: Rect::zero(),
|
||||
animation: None,
|
||||
footer: Footer::new(TR::instructions__swipe_up),
|
||||
progress: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,12 +57,15 @@ impl<'a> ShareWords<'a> {
|
||||
}
|
||||
|
||||
fn is_final_page(&self) -> bool {
|
||||
self.page_index == self.share_words.len() - 1
|
||||
self.page_index == self.share_words.len() as i16 - 1
|
||||
}
|
||||
|
||||
fn render_word<'s>(&self, word_index: usize, target: &mut impl Renderer<'s>) {
|
||||
fn render_word<'s>(&self, word_index: i16, target: &mut impl Renderer<'s>) {
|
||||
// the share word
|
||||
let word = self.share_words[word_index];
|
||||
if word_index >= self.share_words.len() as _ || word_index < 0 {
|
||||
return;
|
||||
}
|
||||
let word = self.share_words[word_index as usize];
|
||||
let word_baseline = target.viewport().clip.center()
|
||||
+ Offset::y(theme::TEXT_SUPER.text_font.visible_text_height("A") / 2);
|
||||
word.map(|w| {
|
||||
@ -93,16 +98,43 @@ impl<'a> Component for ShareWords<'a> {
|
||||
self.area
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
// ctx.set_page_count(self.share_words.len());
|
||||
if let Some(a) = &self.animation {
|
||||
if a.finished(Instant::now()) {
|
||||
self.animation = None;
|
||||
} else {
|
||||
ctx.request_anim_frame();
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
ctx.set_page_count(self.share_words.len());
|
||||
|
||||
match event {
|
||||
Event::Attach(_) => {
|
||||
self.progress = 0;
|
||||
}
|
||||
Event::Swipe(SwipeEvent::End(dir)) => match dir {
|
||||
SwipeDirection::Up if !self.is_final_page() => {
|
||||
self.progress = 0;
|
||||
self.page_index = (self.page_index + 1).min(self.share_words.len() as i16 - 1);
|
||||
ctx.request_paint();
|
||||
}
|
||||
SwipeDirection::Down if !self.is_first_page() => {
|
||||
self.progress = 0;
|
||||
self.page_index = self.page_index.saturating_sub(1);
|
||||
ctx.request_paint();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Event::Swipe(SwipeEvent::Move(dir, progress)) => {
|
||||
match dir {
|
||||
SwipeDirection::Up => {
|
||||
self.next_index = self.page_index + 1;
|
||||
self.progress = progress;
|
||||
}
|
||||
SwipeDirection::Down => {
|
||||
self.next_index = self.page_index - 1;
|
||||
self.progress = progress;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ctx.request_paint();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@ -132,13 +164,20 @@ impl<'a> Component for ShareWords<'a> {
|
||||
.with_fg(theme::GREY)
|
||||
.render(target);
|
||||
|
||||
if let Some(animation) = &self.animation {
|
||||
if self.progress > 0 {
|
||||
target.in_clip(self.area_word, &|target| {
|
||||
let progress = pareen::constant(0.0).seq_ease_out(
|
||||
0.0,
|
||||
easer::functions::Cubic,
|
||||
1.0,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
|
||||
util::render_slide(
|
||||
|target| self.render_word(self.prev_index, target),
|
||||
|target| self.render_word(self.page_index, target),
|
||||
animation.value(Instant::now()),
|
||||
if self.prev_index < self.page_index {
|
||||
|target| self.render_word(self.next_index, target),
|
||||
progress.eval(self.progress as f32 / 1000.0),
|
||||
if self.page_index < self.next_index {
|
||||
SwipeDirection::Up
|
||||
} else {
|
||||
SwipeDirection::Down
|
||||
@ -160,48 +199,11 @@ impl<'a> Component for ShareWords<'a> {
|
||||
fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {}
|
||||
}
|
||||
|
||||
impl<'a> Swipable<Never> for ShareWords<'a> {
|
||||
fn swipe_start(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
direction: SwipeDirection,
|
||||
) -> SwipableResult<Never> {
|
||||
match direction {
|
||||
SwipeDirection::Up if !self.is_final_page() => {
|
||||
self.prev_index = self.page_index;
|
||||
self.page_index = (self.page_index + 1).min(self.share_words.len() - 1);
|
||||
}
|
||||
SwipeDirection::Down if !self.is_first_page() => {
|
||||
self.prev_index = self.page_index;
|
||||
self.page_index = self.page_index.saturating_sub(1);
|
||||
}
|
||||
_ => return SwipableResult::Ignored,
|
||||
};
|
||||
if util::animation_disabled() {
|
||||
ctx.request_paint();
|
||||
return SwipableResult::Animating;
|
||||
}
|
||||
self.animation = Some(Animation::new(
|
||||
0.0f32,
|
||||
1.0f32,
|
||||
ANIMATION_DURATION_MS,
|
||||
Instant::now(),
|
||||
));
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
SwipableResult::Animating
|
||||
}
|
||||
|
||||
fn swipe_finished(&self) -> bool {
|
||||
self.animation.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<'a> crate::trace::Trace for ShareWords<'a> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ShareWords");
|
||||
let word = &self.share_words[self.page_index];
|
||||
let word = &self.share_words[self.page_index as usize];
|
||||
let content =
|
||||
word.map(|w| build_string!(50, inttostr!(self.page_index as u8 + 1), ". ", w, "\n"));
|
||||
t.string("screen_content", content.as_str().into());
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
time::{Duration, Stopwatch},
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Swipe, SwipeDirection, Timeout},
|
||||
component::{Component, Event, EventCtx, Timeout},
|
||||
constant::screen,
|
||||
display::{Color, Icon},
|
||||
geometry::{Alignment2D, Insets, Rect},
|
||||
@ -23,13 +23,17 @@ struct StatusAnimation {
|
||||
|
||||
impl StatusAnimation {
|
||||
pub fn is_active(&self) -> bool {
|
||||
if animation_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.timer
|
||||
.is_running_within(Duration::from_millis(TIMEOUT_MS))
|
||||
}
|
||||
|
||||
pub fn eval(&self) -> f32 {
|
||||
if animation_disabled() {
|
||||
return 1.0;
|
||||
return TIMEOUT_MS as f32 / 1000.0;
|
||||
}
|
||||
self.timer.elapsed().to_millis() as f32 / 1000.0
|
||||
}
|
||||
@ -96,7 +100,7 @@ pub struct StatusScreen {
|
||||
|
||||
#[derive(Clone)]
|
||||
enum DismissType {
|
||||
SwipeUp(Swipe),
|
||||
SwipeUp,
|
||||
Timeout(Timeout),
|
||||
}
|
||||
|
||||
@ -117,7 +121,7 @@ impl StatusScreen {
|
||||
theme::ICON_SIMPLE_CHECKMARK,
|
||||
theme::GREEN_LIME,
|
||||
theme::GREEN_LIGHT,
|
||||
DismissType::SwipeUp(Swipe::new().up()),
|
||||
DismissType::SwipeUp,
|
||||
)
|
||||
}
|
||||
|
||||
@ -135,7 +139,7 @@ impl StatusScreen {
|
||||
theme::ICON_SIMPLE_CHECKMARK,
|
||||
theme::GREY_EXTRA_LIGHT,
|
||||
theme::GREY_DARK,
|
||||
DismissType::SwipeUp(Swipe::new().up()),
|
||||
DismissType::SwipeUp,
|
||||
)
|
||||
}
|
||||
|
||||
@ -154,14 +158,11 @@ impl Component for StatusScreen {
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.area = bounds;
|
||||
if let DismissType::SwipeUp(swipe) = &mut self.dismiss_type {
|
||||
swipe.place(bounds);
|
||||
}
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Event::Attach = event {
|
||||
if let Event::Attach(_) = event {
|
||||
self.anim.start();
|
||||
ctx.request_paint();
|
||||
ctx.request_anim_frame();
|
||||
@ -173,19 +174,11 @@ impl Component for StatusScreen {
|
||||
}
|
||||
}
|
||||
|
||||
match self.dismiss_type {
|
||||
DismissType::SwipeUp(ref mut swipe) => {
|
||||
let swipe_dir = swipe.event(ctx, event);
|
||||
if let Some(SwipeDirection::Up) = swipe_dir {
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
DismissType::Timeout(ref mut timeout) => {
|
||||
if let DismissType::Timeout(ref mut timeout) = self.dismiss_type {
|
||||
if timeout.event(ctx, event).is_some() {
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
258
core/embed/rust/src/ui/model_mercury/component/swipe_content.rs
Normal file
258
core/embed/rust/src/ui/model_mercury/component/swipe_content.rs
Normal file
@ -0,0 +1,258 @@
|
||||
use crate::{
|
||||
time::{Duration, Stopwatch},
|
||||
ui::{
|
||||
component::{base::AttachType, Component, Event, EventCtx, SwipeDirection},
|
||||
display::Color,
|
||||
event::SwipeEvent,
|
||||
geometry::{Offset, Rect},
|
||||
lerp::Lerp,
|
||||
shape,
|
||||
shape::Renderer,
|
||||
util::animation_disabled,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct AttachAnimation {
|
||||
pub timer: Stopwatch,
|
||||
}
|
||||
|
||||
impl AttachAnimation {
|
||||
const DURATION_MS: u32 = 500;
|
||||
pub fn is_active(&self) -> bool {
|
||||
if animation_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.timer
|
||||
.is_running_within(Duration::from_millis(Self::DURATION_MS))
|
||||
}
|
||||
|
||||
pub fn eval(&self) -> f32 {
|
||||
if animation_disabled() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
self.timer.elapsed().to_millis() as f32 / 1000.0
|
||||
}
|
||||
|
||||
pub fn get_offset(&self, t: f32, attach_type: Option<AttachType>) -> Offset {
|
||||
let value = pareen::constant(0.0).seq_ease_in(
|
||||
0.0,
|
||||
easer::functions::Linear,
|
||||
Self::DURATION_MS as f32 / 1000.0,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
|
||||
match attach_type {
|
||||
Some(AttachType::Swipe(dir)) => match dir {
|
||||
SwipeDirection::Up => {
|
||||
Offset::lerp(Offset::new(0, 20), Offset::zero(), value.eval(t))
|
||||
}
|
||||
SwipeDirection::Down => {
|
||||
Offset::lerp(Offset::new(0, -20), Offset::zero(), value.eval(t))
|
||||
}
|
||||
_ => Offset::zero(),
|
||||
},
|
||||
_ => Offset::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_opacity(&self, t: f32, attach_type: Option<AttachType>) -> u8 {
|
||||
let value = pareen::constant(0.0).seq_ease_in_out(
|
||||
0.0,
|
||||
easer::functions::Cubic,
|
||||
0.2,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
match attach_type {
|
||||
Some(AttachType::Swipe(SwipeDirection::Up))
|
||||
| Some(AttachType::Swipe(SwipeDirection::Down)) => {}
|
||||
_ => {
|
||||
return 255;
|
||||
}
|
||||
}
|
||||
u8::lerp(0, 255, value.eval(t))
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.timer.start();
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.timer = Stopwatch::new_stopped();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SwipeContent<T> {
|
||||
inner: T,
|
||||
bounds: Rect,
|
||||
progress: i16,
|
||||
dir: SwipeDirection,
|
||||
normal: Option<AttachType>,
|
||||
attach_animation: AttachAnimation,
|
||||
attach_type: Option<AttachType>,
|
||||
}
|
||||
|
||||
impl<T: Component> SwipeContent<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
bounds: Rect::zero(),
|
||||
progress: 0,
|
||||
dir: SwipeDirection::Up,
|
||||
normal: Some(AttachType::Swipe(SwipeDirection::Down)),
|
||||
attach_animation: AttachAnimation::default(),
|
||||
attach_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_normal_attach(self, attach_type: Option<AttachType>) -> Self {
|
||||
Self {
|
||||
normal: attach_type,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Component for SwipeContent<T> {
|
||||
type Msg = T::Msg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.bounds = self.inner.place(bounds);
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Event::Attach(attach_type) = event {
|
||||
self.progress = 0;
|
||||
if let AttachType::Initial = attach_type {
|
||||
self.attach_type = self.normal;
|
||||
} else {
|
||||
self.attach_type = Some(attach_type);
|
||||
}
|
||||
self.attach_animation.reset();
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if !animation_disabled() {
|
||||
if !self.attach_animation.timer.is_running() {
|
||||
self.attach_animation.timer.start();
|
||||
}
|
||||
if self.attach_animation.is_active() {
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Event::Swipe(SwipeEvent::Move(dir, progress)) = event {
|
||||
match dir {
|
||||
SwipeDirection::Up | SwipeDirection::Down => {
|
||||
self.dir = dir;
|
||||
self.progress = progress;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ctx.request_paint();
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Touch(_) => {
|
||||
if self.attach_animation.is_active() {
|
||||
None
|
||||
} else {
|
||||
self.inner.event(ctx, event)
|
||||
}
|
||||
}
|
||||
_ => self.inner.event(ctx, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.inner.paint()
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let progress = self.progress as f32 / 1000.0;
|
||||
|
||||
let shift = pareen::constant(0.0).seq_ease_out(
|
||||
0.0,
|
||||
easer::functions::Cubic,
|
||||
1.0,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
|
||||
let offset = i16::lerp(0, 50, shift.eval(progress));
|
||||
|
||||
let mask = u8::lerp(0, 255, shift.eval(progress));
|
||||
|
||||
if self.progress > 0 {
|
||||
match self.dir {
|
||||
SwipeDirection::Up => {
|
||||
let offset = Offset::y(-offset);
|
||||
target.in_clip(self.bounds, &|target| {
|
||||
target.with_origin(offset, &|target| {
|
||||
self.inner.render(target);
|
||||
shape::Bar::new(self.bounds)
|
||||
.with_alpha(mask)
|
||||
.with_fg(Color::black())
|
||||
.with_bg(Color::black())
|
||||
.render(target);
|
||||
});
|
||||
});
|
||||
}
|
||||
SwipeDirection::Down => {
|
||||
let offset = Offset::y(offset);
|
||||
target.with_origin(offset, &|target| {
|
||||
self.inner.render(target);
|
||||
shape::Bar::new(self.bounds)
|
||||
.with_alpha(mask)
|
||||
.with_fg(Color::black())
|
||||
.with_bg(Color::black())
|
||||
.render(target);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
} else {
|
||||
let t = self.attach_animation.eval();
|
||||
let offset = self.attach_animation.get_offset(t, self.attach_type);
|
||||
let opacity = self.attach_animation.get_opacity(t, self.attach_type);
|
||||
|
||||
if offset.x != 0 || offset.y != 0 {
|
||||
target.in_clip(self.bounds, &|target| {
|
||||
target.with_origin(offset, &|target| {
|
||||
self.inner.render(target);
|
||||
shape::Bar::new(self.bounds)
|
||||
.with_alpha(255 - opacity)
|
||||
.with_fg(Color::black())
|
||||
.with_bg(Color::black())
|
||||
.render(target);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// some components draw outside their bounds during animations
|
||||
// let them do that unless we are animating here
|
||||
self.inner.render(target);
|
||||
shape::Bar::new(self.bounds)
|
||||
.with_alpha(255 - opacity)
|
||||
.with_fg(Color::black())
|
||||
.with_bg(Color::black())
|
||||
.render(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for SwipeContent<T>
|
||||
where
|
||||
T: crate::trace::Trace,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("SwipeContent");
|
||||
t.child("content", &self.inner);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
use crate::ui::{
|
||||
component::{Component, Event, EventCtx, Swipe, SwipeDirection},
|
||||
component::{Component, Event, EventCtx, SwipeDetect, SwipeDetectMsg},
|
||||
event::SwipeEvent,
|
||||
flow::Swipable,
|
||||
geometry::Rect,
|
||||
shape::Renderer,
|
||||
};
|
||||
@ -7,7 +9,7 @@ use crate::ui::{
|
||||
/// Wrapper component adding "swipe up" handling to `content`.
|
||||
pub struct SwipeUpScreen<T> {
|
||||
content: T,
|
||||
swipe: Swipe,
|
||||
swipe: SwipeDetect,
|
||||
}
|
||||
|
||||
pub enum SwipeUpScreenMsg<T> {
|
||||
@ -19,33 +21,40 @@ impl<T> SwipeUpScreen<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
pub fn new(content: T) -> Self {
|
||||
pub fn new(content: T) -> Self
|
||||
where
|
||||
T: Swipable,
|
||||
{
|
||||
Self {
|
||||
content,
|
||||
swipe: Swipe::new().up(),
|
||||
swipe: SwipeDetect::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for SwipeUpScreen<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
impl<T: Swipable + Component> Component for SwipeUpScreen<T> {
|
||||
type Msg = SwipeUpScreenMsg<T::Msg>;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.swipe.place(bounds);
|
||||
self.content.place(bounds);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Some(SwipeDirection::Up) = self.swipe.event(ctx, event) {
|
||||
let e = match self
|
||||
.swipe
|
||||
.event(ctx, event, self.content.get_swipe_config())
|
||||
{
|
||||
Some(SwipeDetectMsg::Trigger(_)) => {
|
||||
return Some(SwipeUpScreenMsg::Swiped);
|
||||
}
|
||||
self.content
|
||||
.event(ctx, event)
|
||||
.map(SwipeUpScreenMsg::Content)
|
||||
Some(SwipeDetectMsg::Move(dir, progress)) => {
|
||||
Event::Swipe(SwipeEvent::Move(dir, progress))
|
||||
}
|
||||
_ => event,
|
||||
};
|
||||
|
||||
self.content.event(ctx, e).map(SwipeUpScreenMsg::Content)
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
|
@ -25,6 +25,10 @@ impl TapToConfirmAmin {
|
||||
const DURATION_MS: u32 = 600;
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
if animation_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.timer
|
||||
.is_running_within(Duration::from_millis(Self::DURATION_MS))
|
||||
}
|
||||
@ -161,24 +165,16 @@ impl Component for TapToConfirm {
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let btn_msg = self.button.event(ctx, event);
|
||||
match btn_msg {
|
||||
Some(ButtonMsg::Pressed) => {
|
||||
if let Some(ButtonMsg::Clicked) = btn_msg {
|
||||
if animation_disabled() {
|
||||
return Some(());
|
||||
}
|
||||
self.anim.start();
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
Some(ButtonMsg::Released) => {
|
||||
self.anim.reset();
|
||||
ctx.request_anim_frame();
|
||||
ctx.request_paint();
|
||||
}
|
||||
Some(ButtonMsg::Clicked) => {
|
||||
if animation_disabled() {
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if !animation_disabled() {
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if self.anim.is_active() {
|
||||
ctx.request_anim_frame();
|
||||
@ -186,8 +182,10 @@ impl Component for TapToConfirm {
|
||||
}
|
||||
}
|
||||
if self.anim.is_finished() {
|
||||
ctx.enable_swipe();
|
||||
return Some(());
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@ -245,9 +243,6 @@ impl Component for TapToConfirm {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl crate::ui::flow::Swipable<()> for TapToConfirm {}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for TapToConfirm {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -3,12 +3,16 @@ use heapless::Vec;
|
||||
use super::theme;
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
time::{Duration, Stopwatch},
|
||||
ui::{
|
||||
component::{base::Component, Event, EventCtx},
|
||||
display::Icon,
|
||||
geometry::Rect,
|
||||
constant::screen,
|
||||
display::{Color, Icon},
|
||||
geometry::{Offset, Rect},
|
||||
lerp::Lerp,
|
||||
model_mercury::component::button::{Button, ButtonMsg, IconText},
|
||||
shape::{Bar, Renderer},
|
||||
util::animation_disabled,
|
||||
},
|
||||
};
|
||||
|
||||
@ -33,6 +37,84 @@ const MENU_SEP_HEIGHT: i16 = 2;
|
||||
type VerticalMenuButtons = Vec<Button, N_ITEMS>;
|
||||
type AreasForSeparators = Vec<Rect, N_SEPS>;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct AttachAnimation {
|
||||
pub timer: Stopwatch,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl AttachAnimation {
|
||||
const DURATION_MS: u32 = 350;
|
||||
fn is_active(&self) -> bool {
|
||||
if animation_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.timer
|
||||
.is_running_within(Duration::from_millis(Self::DURATION_MS))
|
||||
}
|
||||
|
||||
fn eval(&self) -> f32 {
|
||||
if animation_disabled() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
self.timer.elapsed().to_millis() as f32 / 1000.0
|
||||
}
|
||||
|
||||
fn get_offset(&self, t: f32) -> Offset {
|
||||
let value = pareen::constant(0.0).seq_ease_in(
|
||||
0.0,
|
||||
easer::functions::Cubic,
|
||||
Self::DURATION_MS as f32 / 1000.0,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
|
||||
Offset::lerp(Offset::new(-40, 0), Offset::zero(), value.eval(t))
|
||||
}
|
||||
|
||||
fn get_mask_width(&self, t: f32) -> i16 {
|
||||
let value = pareen::constant(0.0).seq_ease_in(
|
||||
0.0,
|
||||
easer::functions::Circ,
|
||||
0.15,
|
||||
pareen::constant(1.0),
|
||||
);
|
||||
//todo screen here is incorrect
|
||||
i16::lerp(screen().width(), 0, value.eval(t))
|
||||
}
|
||||
|
||||
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) {
|
||||
if let Event::Attach(_) = event {
|
||||
self.reset();
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VerticalMenu {
|
||||
area: Rect,
|
||||
@ -40,6 +122,8 @@ pub struct VerticalMenu {
|
||||
buttons: VerticalMenuButtons,
|
||||
/// areas for visual separators between buttons
|
||||
areas_sep: AreasForSeparators,
|
||||
|
||||
attach_animation: AttachAnimation,
|
||||
}
|
||||
|
||||
impl VerticalMenu {
|
||||
@ -48,6 +132,7 @@ impl VerticalMenu {
|
||||
area: Rect::zero(),
|
||||
buttons,
|
||||
areas_sep: AreasForSeparators::new(),
|
||||
attach_animation: AttachAnimation::default(),
|
||||
}
|
||||
}
|
||||
pub fn select_word(words: [TString<'static>; 3]) -> Self {
|
||||
@ -107,11 +192,15 @@ impl Component for VerticalMenu {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.attach_animation.lazy_start(ctx, event);
|
||||
|
||||
if !self.attach_animation.is_active() {
|
||||
for (i, button) in self.buttons.iter_mut().enumerate() {
|
||||
if let Some(ButtonMsg::Clicked) = button.event(ctx, event) {
|
||||
return Some(VerticalMenuChoiceMsg::Selected(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@ -120,6 +209,12 @@ impl Component for VerticalMenu {
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let t = self.attach_animation.eval();
|
||||
|
||||
let offset = self.attach_animation.get_offset(t);
|
||||
let mask_width = self.attach_animation.get_mask_width(t);
|
||||
|
||||
target.with_origin(offset, &|target| {
|
||||
// render buttons separated by thin bars
|
||||
for button in &self.buttons {
|
||||
button.render(target);
|
||||
@ -130,6 +225,15 @@ impl Component for VerticalMenu {
|
||||
.with_fg(theme::GREY_EXTRA_DARK)
|
||||
.render(target);
|
||||
}
|
||||
|
||||
// todo screen here is incorrect
|
||||
let r = Rect::from_size(Offset::new(mask_width, screen().height()));
|
||||
|
||||
Bar::new(r)
|
||||
.with_fg(Color::black())
|
||||
.with_bg(Color::black())
|
||||
.render(target);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_bounds")]
|
||||
@ -149,6 +253,3 @@ impl crate::trace::Trace for VerticalMenu {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
impl crate::ui::flow::Swipable<VerticalMenuChoiceMsg> for VerticalMenu {}
|
||||
|
@ -4,10 +4,8 @@ use crate::{
|
||||
strutil::TString,
|
||||
translations::TR,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::Paragraph, Component, ComponentExt, Paginate, SwipeDirection,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
component::{text::paragraphs::Paragraph, ComponentExt, SwipeDirection},
|
||||
flow::{base::Decision, FlowMsg, FlowState, FlowStore},
|
||||
},
|
||||
};
|
||||
|
||||
@ -107,8 +105,14 @@ impl FlowState for ConfirmActionSimple {
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
|
||||
ui::{
|
||||
component::text::paragraphs::{ParagraphSource, ParagraphVecShort, VecExt},
|
||||
component::{
|
||||
swipe_detect::SwipeSettings,
|
||||
text::paragraphs::{ParagraphSource, ParagraphVecShort, VecExt},
|
||||
Component, Paginate,
|
||||
},
|
||||
flow::{flow_store, SwipeFlow, SwipePage},
|
||||
layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
@ -154,16 +158,22 @@ fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Err
|
||||
paragraphs.into_paragraphs()
|
||||
};
|
||||
|
||||
let mut content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
|
||||
let mut content_intro =
|
||||
Frame::left_aligned(title, SwipeContent::new(SwipePage::vertical(paragraphs)))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None);
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.with_vertical_pages();
|
||||
|
||||
if let Some(subtitle) = subtitle {
|
||||
content_intro = content_intro.with_subtitle(subtitle);
|
||||
}
|
||||
|
||||
let content_intro =
|
||||
content_intro.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
|
||||
let content_intro = content_intro.map(move |msg| match msg {
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let content_menu = if let Some(verb_cancel) = verb_cancel {
|
||||
Frame::left_aligned(
|
||||
@ -177,6 +187,7 @@ fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Err
|
||||
)
|
||||
}
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(move |msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(_)) => Some(FlowMsg::Choice(0)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
@ -199,9 +210,11 @@ fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Err
|
||||
)
|
||||
};
|
||||
|
||||
let mut content_confirm = Frame::left_aligned(title, prompt)
|
||||
let mut content_confirm = Frame::left_aligned(title, SwipeContent::new(prompt))
|
||||
.with_footer(prompt_action, None)
|
||||
.with_menu_button();
|
||||
.with_menu_button()
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default());
|
||||
|
||||
if let Some(subtitle) = subtitle {
|
||||
content_confirm = content_confirm.with_subtitle(subtitle);
|
||||
@ -228,9 +241,12 @@ pub fn new_confirm_action_simple<T: Component + Paginate + Clone + MaybeTrace +
|
||||
verb: Option<TString<'static>>,
|
||||
verb_cancel: Option<TString<'static>>,
|
||||
) -> Result<Obj, error::Error> {
|
||||
let mut frame = Frame::left_aligned(title, SwipePage::vertical(content))
|
||||
let mut frame = Frame::left_aligned(title, SwipeContent::new(SwipePage::vertical(content)))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), verb);
|
||||
.with_footer(TR::instructions__swipe_up.into(), verb)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::immediate())
|
||||
.with_vertical_pages();
|
||||
if let Some(subtitle) = subtitle {
|
||||
frame = frame.with_subtitle(subtitle)
|
||||
}
|
||||
@ -249,6 +265,7 @@ pub fn new_confirm_action_simple<T: Component + Paginate + Clone + MaybeTrace +
|
||||
)
|
||||
}
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(move |msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
ui::{
|
||||
button_request::ButtonRequest,
|
||||
component::{ButtonRequestExt, ComponentExt, SwipeDirection},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
|
||||
},
|
||||
};
|
||||
|
||||
@ -76,7 +76,10 @@ impl FlowState for ConfirmOutput {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -117,6 +120,7 @@ impl ConfirmOutput {
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_text_mono(text_mono)
|
||||
.with_swipe_down()
|
||||
.into_layout()?
|
||||
.one_button_request(ButtonRequest::from_num(br_code, br_type));
|
||||
|
||||
@ -128,6 +132,7 @@ impl ConfirmOutput {
|
||||
.danger(theme::ICON_CANCEL, "Cancel sign".into()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
@ -135,15 +140,17 @@ impl ConfirmOutput {
|
||||
|
||||
// AccountInfo
|
||||
let ad = AddressDetails::new(TR::send__send_from.into(), account, account_path)?;
|
||||
let content_account = SwipePage::horizontal(ad).map(|_| Some(FlowMsg::Cancelled));
|
||||
let content_account = ad.map(|_| Some(FlowMsg::Cancelled));
|
||||
|
||||
// CancelTap
|
||||
let content_cancel_tap = Frame::left_aligned(
|
||||
TR::send__cancel_sign.into(),
|
||||
PromptScreen::new_tap_to_cancel(),
|
||||
SwipeContent::new(PromptScreen::new_tap_to_cancel()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
|
@ -6,16 +6,18 @@ use crate::{
|
||||
ui::{
|
||||
button_request::ButtonRequestCode,
|
||||
component::{
|
||||
swipe_detect::SwipeSettings,
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
ButtonRequestExt, ComponentExt, SwipeDirection,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
|
||||
layout::obj::LayoutObj,
|
||||
model_mercury::component::{PromptScreen, SwipeContent},
|
||||
},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::{Frame, FrameMsg, HoldToConfirm, VerticalMenu, VerticalMenuChoiceMsg},
|
||||
component::{Frame, FrameMsg, VerticalMenu, VerticalMenuChoiceMsg},
|
||||
theme,
|
||||
};
|
||||
|
||||
@ -32,15 +34,18 @@ impl FlowState for ConfirmResetCreate {
|
||||
(ConfirmResetCreate::Intro, SwipeDirection::Left) => {
|
||||
Decision::Goto(ConfirmResetCreate::Menu, direction)
|
||||
}
|
||||
(ConfirmResetCreate::Menu, SwipeDirection::Right) => {
|
||||
Decision::Goto(ConfirmResetCreate::Intro, direction)
|
||||
}
|
||||
(ConfirmResetCreate::Intro, SwipeDirection::Up) => {
|
||||
Decision::Goto(ConfirmResetCreate::Confirm, direction)
|
||||
}
|
||||
(ConfirmResetCreate::Menu, SwipeDirection::Right) => {
|
||||
Decision::Goto(ConfirmResetCreate::Intro, direction)
|
||||
}
|
||||
(ConfirmResetCreate::Confirm, SwipeDirection::Down) => {
|
||||
Decision::Goto(ConfirmResetCreate::Intro, direction)
|
||||
}
|
||||
(ConfirmResetCreate::Confirm, SwipeDirection::Left) => {
|
||||
Decision::Goto(ConfirmResetCreate::Menu, direction)
|
||||
}
|
||||
_ => Decision::Nothing,
|
||||
}
|
||||
}
|
||||
@ -57,6 +62,9 @@ impl FlowState for ConfirmResetCreate {
|
||||
(ConfirmResetCreate::Confirm, FlowMsg::Confirmed) => {
|
||||
Decision::Return(FlowMsg::Confirmed)
|
||||
}
|
||||
(ConfirmResetCreate::Confirm, FlowMsg::Info) => {
|
||||
Decision::Goto(ConfirmResetCreate::Menu, SwipeDirection::Left)
|
||||
}
|
||||
_ => Decision::Nothing,
|
||||
}
|
||||
}
|
||||
@ -81,9 +89,11 @@ impl ConfirmResetCreate {
|
||||
Paragraph::new(&theme::TEXT_SUB_GREY_LIGHT, TR::reset__tos_link),
|
||||
];
|
||||
let paragraphs = Paragraphs::new(par_array);
|
||||
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
|
||||
let content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
|
||||
.one_button_request(ButtonRequestCode::ResetDevice.with_type("setup_device"));
|
||||
|
||||
@ -92,21 +102,25 @@ impl ConfirmResetCreate {
|
||||
VerticalMenu::empty().danger(theme::ICON_CANCEL, "Cancel".into()), // TODO: use TR
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
});
|
||||
|
||||
let content_confirm =
|
||||
Frame::left_aligned(TR::reset__title_create_wallet.into(), HoldToConfirm::new())
|
||||
let content_confirm = Frame::left_aligned(
|
||||
TR::reset__title_create_wallet.into(),
|
||||
SwipeContent::new(PromptScreen::new_hold_to_confirm()),
|
||||
)
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__hold_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
_ => Some(FlowMsg::Cancelled),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
||||
})
|
||||
.one_button_request(
|
||||
ButtonRequestCode::ResetDevice.with_type("confirm_setup_device"),
|
||||
);
|
||||
.one_button_request(ButtonRequestCode::ResetDevice.with_type("confirm_setup_device"));
|
||||
|
||||
let store = flow_store()
|
||||
.add(content_intro)?
|
||||
|
@ -7,12 +7,12 @@ use crate::{
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
ButtonRequestExt, ComponentExt, SwipeDirection,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
|
||||
},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg},
|
||||
component::{Frame, FrameMsg, VerticalMenu, VerticalMenuChoiceMsg},
|
||||
theme,
|
||||
};
|
||||
|
||||
@ -54,7 +54,11 @@ impl FlowState for ConfirmResetRecover {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings,
|
||||
layout::obj::LayoutObj,
|
||||
model_mercury::component::{PromptScreen, SwipeContent},
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -77,10 +81,12 @@ impl ConfirmResetRecover {
|
||||
let paragraphs = Paragraphs::new(par_array);
|
||||
let content_intro = Frame::left_aligned(
|
||||
TR::recovery__title_recover.into(),
|
||||
SwipePage::vertical(paragraphs),
|
||||
SwipeContent::new(paragraphs),
|
||||
)
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
|
||||
.one_button_request(ButtonRequestCode::ProtectCall.with_type("recover_device"));
|
||||
|
||||
@ -92,6 +98,7 @@ impl ConfirmResetRecover {
|
||||
),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
@ -99,9 +106,10 @@ impl ConfirmResetRecover {
|
||||
|
||||
let content_confirm = Frame::left_aligned(
|
||||
TR::reset__title_create_wallet.into(),
|
||||
PromptScreen::new_hold_to_confirm(),
|
||||
SwipeContent::new(PromptScreen::new_hold_to_confirm()),
|
||||
)
|
||||
.with_footer(TR::instructions__hold_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
_ => Some(FlowMsg::Cancelled),
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
ComponentExt, SwipeDirection,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
flow::{base::Decision, FlowMsg, FlowState, FlowStore},
|
||||
},
|
||||
};
|
||||
|
||||
@ -31,13 +31,21 @@ impl FlowState for SetNewPin {
|
||||
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
|
||||
match (self, direction) {
|
||||
(SetNewPin::Intro, SwipeDirection::Left) => Decision::Goto(SetNewPin::Menu, direction),
|
||||
(SetNewPin::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
|
||||
|
||||
(SetNewPin::Menu, SwipeDirection::Right) => Decision::Goto(SetNewPin::Intro, direction),
|
||||
(SetNewPin::CancelPinIntro, SwipeDirection::Up) => {
|
||||
Decision::Goto(SetNewPin::CancelPinConfirm, direction)
|
||||
}
|
||||
(SetNewPin::CancelPinIntro, SwipeDirection::Right) => {
|
||||
Decision::Goto(SetNewPin::Intro, direction)
|
||||
}
|
||||
(SetNewPin::CancelPinConfirm, SwipeDirection::Down) => {
|
||||
Decision::Goto(SetNewPin::CancelPinIntro, direction)
|
||||
}
|
||||
(SetNewPin::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
|
||||
(SetNewPin::CancelPinConfirm, SwipeDirection::Right) => {
|
||||
Decision::Goto(SetNewPin::Intro, direction)
|
||||
}
|
||||
_ => Decision::Nothing,
|
||||
}
|
||||
}
|
||||
@ -54,7 +62,7 @@ impl FlowState for SetNewPin {
|
||||
Decision::Goto(SetNewPin::Intro, SwipeDirection::Right)
|
||||
}
|
||||
(SetNewPin::CancelPinIntro, FlowMsg::Cancelled) => {
|
||||
Decision::Goto(SetNewPin::Menu, SwipeDirection::Right)
|
||||
Decision::Goto(SetNewPin::Intro, SwipeDirection::Right)
|
||||
}
|
||||
(SetNewPin::CancelPinConfirm, FlowMsg::Cancelled) => {
|
||||
Decision::Goto(SetNewPin::CancelPinIntro, SwipeDirection::Right)
|
||||
@ -69,7 +77,12 @@ impl FlowState for SetNewPin {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings,
|
||||
flow::{flow_store, SwipeFlow},
|
||||
layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -86,9 +99,11 @@ impl SetNewPin {
|
||||
let par_array: [Paragraph<'static>; 1] =
|
||||
[Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description)];
|
||||
let paragraphs = Paragraphs::new(par_array);
|
||||
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
|
||||
let content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| {
|
||||
matches!(msg, FrameMsg::Button(CancelInfoConfirmMsg::Info)).then_some(FlowMsg::Info)
|
||||
});
|
||||
@ -98,6 +113,7 @@ impl SetNewPin {
|
||||
VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::pin__cancel_setup.into()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
@ -111,13 +127,15 @@ impl SetNewPin {
|
||||
let paragraphs_cancel_intro = Paragraphs::new(par_array_cancel_intro);
|
||||
let content_cancel_intro = Frame::left_aligned(
|
||||
TR::pin__cancel_setup.into(),
|
||||
SwipePage::vertical(paragraphs_cancel_intro),
|
||||
SwipeContent::new(paragraphs_cancel_intro),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(
|
||||
TR::instructions__swipe_up.into(),
|
||||
Some(TR::pin__cancel_description.into()),
|
||||
)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
_ => None,
|
||||
@ -125,9 +143,12 @@ impl SetNewPin {
|
||||
|
||||
let content_cancel_confirm = Frame::left_aligned(
|
||||
TR::pin__cancel_setup.into(),
|
||||
PromptScreen::new_tap_to_cancel(),
|
||||
SwipeContent::new(PromptScreen::new_tap_to_cancel()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
|
@ -78,7 +78,10 @@ impl FlowState for ConfirmSummary {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -98,7 +101,8 @@ impl ConfirmSummary {
|
||||
// Summary
|
||||
let mut summary = ShowInfoParams::new(title)
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None);
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe_up();
|
||||
for pair in IterBuf::new().try_iterate(items)? {
|
||||
let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
|
||||
summary = unwrap!(summary.add(label, value));
|
||||
@ -112,10 +116,12 @@ impl ConfirmSummary {
|
||||
// Hold to confirm
|
||||
let content_hold = Frame::left_aligned(
|
||||
TR::send__sign_transaction.into(),
|
||||
PromptScreen::new_hold_to_confirm(),
|
||||
SwipeContent::new(PromptScreen::new_hold_to_confirm()),
|
||||
)
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__hold_to_sign.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
||||
@ -130,6 +136,7 @@ impl ConfirmSummary {
|
||||
.danger(theme::ICON_CANCEL, "Cancel sign".into()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
@ -158,6 +165,7 @@ impl ConfirmSummary {
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
|
@ -9,10 +9,7 @@ use crate::{
|
||||
text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
|
||||
ButtonRequestExt, ComponentExt, Qr, SwipeDirection,
|
||||
},
|
||||
flow::{
|
||||
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow,
|
||||
SwipePage,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
|
||||
layout::util::ConfirmBlob,
|
||||
},
|
||||
};
|
||||
@ -49,6 +46,7 @@ impl FlowState for GetAddress {
|
||||
(GetAddress::Tap, SwipeDirection::Down) => {
|
||||
Decision::Goto(GetAddress::Address, direction)
|
||||
}
|
||||
(GetAddress::Tap, SwipeDirection::Left) => Decision::Goto(GetAddress::Menu, direction),
|
||||
(GetAddress::Menu, SwipeDirection::Right) => {
|
||||
Decision::Goto(GetAddress::Address, direction)
|
||||
}
|
||||
@ -56,7 +54,7 @@ impl FlowState for GetAddress {
|
||||
Decision::Goto(GetAddress::Menu, direction)
|
||||
}
|
||||
(GetAddress::AccountInfo, SwipeDirection::Right) => {
|
||||
Decision::Goto(GetAddress::Menu, direction)
|
||||
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
|
||||
}
|
||||
(GetAddress::Cancel, SwipeDirection::Up) => {
|
||||
Decision::Goto(GetAddress::CancelTap, direction)
|
||||
@ -67,6 +65,9 @@ impl FlowState for GetAddress {
|
||||
(GetAddress::CancelTap, SwipeDirection::Down) => {
|
||||
Decision::Goto(GetAddress::Cancel, direction)
|
||||
}
|
||||
(GetAddress::CancelTap, SwipeDirection::Right) => {
|
||||
Decision::Goto(GetAddress::Menu, direction)
|
||||
}
|
||||
_ => Decision::Nothing,
|
||||
}
|
||||
}
|
||||
@ -81,6 +82,10 @@ impl FlowState for GetAddress {
|
||||
Decision::Goto(GetAddress::Confirmed, SwipeDirection::Up)
|
||||
}
|
||||
|
||||
(GetAddress::Tap, FlowMsg::Info) => {
|
||||
Decision::Goto(GetAddress::Menu, SwipeDirection::Left)
|
||||
}
|
||||
|
||||
(GetAddress::Confirmed, _) => Decision::Return(FlowMsg::Confirmed),
|
||||
|
||||
(GetAddress::Menu, FlowMsg::Choice(0)) => {
|
||||
@ -124,7 +129,10 @@ impl FlowState for GetAddress {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings, flow::SwipePage, layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -167,30 +175,34 @@ impl GetAddress {
|
||||
data_font: data_style,
|
||||
}
|
||||
.into_paragraphs();
|
||||
let content_address = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
|
||||
let content_address =
|
||||
Frame::left_aligned(title, SwipeContent::new(SwipePage::vertical(paragraphs)))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.with_vertical_pages()
|
||||
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
|
||||
.one_button_request(ButtonRequest::from_num(br_code, br_type))
|
||||
// Count tap-to-confirm screen towards page count
|
||||
.with_pages(|address_pages| address_pages + 1);
|
||||
|
||||
// Tap
|
||||
let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm())
|
||||
let content_tap =
|
||||
Frame::left_aligned(title, SwipeContent::new(PromptScreen::new_tap_to_confirm()))
|
||||
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
_ => None,
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
||||
});
|
||||
|
||||
let content_confirmed = IgnoreSwipe::new(
|
||||
Frame::left_aligned(
|
||||
let content_confirmed = Frame::left_aligned(
|
||||
TR::address__confirmed.into(),
|
||||
StatusScreen::new_success_timeout(),
|
||||
)
|
||||
.with_footer(TR::instructions__continue_in_app.into(), None),
|
||||
)
|
||||
.with_footer(TR::instructions__continue_in_app.into(), None)
|
||||
.map(|_| Some(FlowMsg::Confirmed));
|
||||
|
||||
// Menu
|
||||
@ -205,6 +217,7 @@ impl GetAddress {
|
||||
.danger(theme::ICON_CANCEL, TR::address__cancel_receive.into()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
@ -213,11 +226,9 @@ impl GetAddress {
|
||||
// QrCode
|
||||
let content_qr = Frame::left_aligned(
|
||||
title,
|
||||
IgnoreSwipe::new(
|
||||
address_qr
|
||||
.map(|s| Qr::new(s, case_sensitive))?
|
||||
.with_border(QR_BORDER),
|
||||
),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled));
|
||||
@ -228,18 +239,20 @@ impl GetAddress {
|
||||
let [xtitle, text]: [TString; 2] = util::iter_into_array(i)?;
|
||||
ad.add_xpub(xtitle, text)?;
|
||||
}
|
||||
let content_account = SwipePage::horizontal(ad).map(|_| Some(FlowMsg::Cancelled));
|
||||
let content_account = ad.map(|_| Some(FlowMsg::Cancelled));
|
||||
|
||||
// Cancel
|
||||
let content_cancel_info = Frame::left_aligned(
|
||||
TR::address__cancel_receive.into(),
|
||||
SwipePage::vertical(Paragraphs::new(Paragraph::new(
|
||||
SwipeContent::new(Paragraphs::new(Paragraph::new(
|
||||
&theme::TEXT_MAIN_GREY_LIGHT,
|
||||
TR::address__cancel_contact_support,
|
||||
))),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled));
|
||||
|
||||
// CancelTap
|
||||
@ -249,6 +262,8 @@ impl GetAddress {
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
ComponentExt, SwipeDirection,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
flow::{base::Decision, FlowMsg, FlowState, FlowStore},
|
||||
},
|
||||
};
|
||||
|
||||
@ -32,13 +32,24 @@ impl FlowState for PromptBackup {
|
||||
(PromptBackup::Intro, SwipeDirection::Left) => {
|
||||
Decision::Goto(PromptBackup::Menu, direction)
|
||||
}
|
||||
(PromptBackup::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
|
||||
|
||||
(PromptBackup::Menu, SwipeDirection::Right) => {
|
||||
Decision::Goto(PromptBackup::Intro, direction)
|
||||
}
|
||||
|
||||
(PromptBackup::SkipBackupIntro, SwipeDirection::Up) => {
|
||||
Decision::Goto(PromptBackup::SkipBackupConfirm, direction)
|
||||
}
|
||||
(PromptBackup::SkipBackupIntro, SwipeDirection::Right) => {
|
||||
Decision::Goto(PromptBackup::Intro, direction)
|
||||
}
|
||||
(PromptBackup::SkipBackupConfirm, SwipeDirection::Down) => {
|
||||
Decision::Goto(PromptBackup::SkipBackupIntro, direction)
|
||||
}
|
||||
(PromptBackup::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
|
||||
(PromptBackup::SkipBackupConfirm, SwipeDirection::Right) => {
|
||||
Decision::Goto(PromptBackup::Intro, direction)
|
||||
}
|
||||
_ => Decision::Nothing,
|
||||
}
|
||||
}
|
||||
@ -70,7 +81,12 @@ impl FlowState for PromptBackup {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings,
|
||||
flow::{flow_store, SwipeFlow},
|
||||
layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -88,9 +104,11 @@ impl PromptBackup {
|
||||
TString::from_str("Your wallet backup contains words in a specific order."),
|
||||
)];
|
||||
let paragraphs = Paragraphs::new(par_array);
|
||||
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
|
||||
let content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| {
|
||||
matches!(msg, FrameMsg::Button(CancelInfoConfirmMsg::Info)).then_some(FlowMsg::Info)
|
||||
});
|
||||
@ -100,6 +118,7 @@ impl PromptBackup {
|
||||
VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::backup__title_skip.into()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
@ -116,13 +135,15 @@ impl PromptBackup {
|
||||
let paragraphs_skip_intro = Paragraphs::new(par_array_skip_intro);
|
||||
let content_skip_intro = Frame::left_aligned(
|
||||
TR::backup__title_skip.into(),
|
||||
SwipePage::vertical(paragraphs_skip_intro),
|
||||
SwipeContent::new(paragraphs_skip_intro),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(
|
||||
TR::instructions__swipe_up.into(),
|
||||
Some(TR::words__continue_anyway.into()),
|
||||
)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
_ => None,
|
||||
@ -130,9 +151,12 @@ impl PromptBackup {
|
||||
|
||||
let content_skip_confirm = Frame::left_aligned(
|
||||
TR::backup__title_skip.into(),
|
||||
PromptScreen::new_tap_to_cancel(),
|
||||
SwipeContent::new(PromptScreen::new_tap_to_cancel()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
ButtonRequestExt, ComponentExt, SwipeDirection,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
|
||||
},
|
||||
};
|
||||
|
||||
@ -67,7 +67,10 @@ impl FlowState for RequestNumber {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -102,9 +105,12 @@ impl RequestNumber {
|
||||
|
||||
let number_input_dialog =
|
||||
NumberInputDialog::new(min_count, max_count, count, description_cb)?;
|
||||
let content_number_input = Frame::left_aligned(title, number_input_dialog)
|
||||
let content_number_input =
|
||||
Frame::left_aligned(title, SwipeContent::new(number_input_dialog))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Info),
|
||||
FrameMsg::Content(NumberInputDialogMsg(n)) => Some(FlowMsg::Choice(n as usize)),
|
||||
@ -118,6 +124,7 @@ impl RequestNumber {
|
||||
.danger(theme::ICON_CANCEL, TR::backup__title_skip.into()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
@ -130,9 +137,10 @@ impl RequestNumber {
|
||||
));
|
||||
let content_info = Frame::left_aligned(
|
||||
TR::backup__title_skip.into(),
|
||||
SwipePage::vertical(paragraphs_info),
|
||||
SwipeContent::new(paragraphs_info),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||
_ => None,
|
||||
|
@ -6,11 +6,13 @@ use crate::{
|
||||
ui::{
|
||||
button_request::ButtonRequestCode,
|
||||
component::{
|
||||
swipe_detect::SwipeSettings,
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
ButtonRequestExt, ComponentExt, SwipeDirection,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
|
||||
layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
use heapless::Vec;
|
||||
@ -84,35 +86,41 @@ impl ShowShareWords {
|
||||
|
||||
let content_instruction = Frame::left_aligned(
|
||||
title,
|
||||
SwipePage::vertical(Paragraphs::new(Paragraph::new(
|
||||
SwipeContent::new(Paragraphs::new(Paragraph::new(
|
||||
&theme::TEXT_MAIN_GREY_LIGHT,
|
||||
text_info,
|
||||
))),
|
||||
)
|
||||
.with_subtitle(TR::words__instructions.into())
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed))
|
||||
.one_button_request(ButtonRequestCode::ResetDevice.with_type("share_words"))
|
||||
.with_pages(move |_| nwords + 2);
|
||||
|
||||
let content_words = Frame::left_aligned(title, ShareWords::new(share_words_vec))
|
||||
.with_subtitle(subtitle)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.with_vertical_pages()
|
||||
.map(|_| None);
|
||||
|
||||
let content_confirm =
|
||||
Frame::left_aligned(text_confirm, PromptScreen::new_hold_to_confirm())
|
||||
.with_footer(TR::instructions__hold_to_confirm.into(), None)
|
||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||
.map(|_| Some(FlowMsg::Confirmed));
|
||||
|
||||
let content_check_backup_intro = Frame::left_aligned(
|
||||
TR::reset__check_backup_title.into(),
|
||||
SwipePage::vertical(Paragraphs::new(Paragraph::new(
|
||||
SwipeContent::new(Paragraphs::new(Paragraph::new(
|
||||
&theme::TEXT_MAIN_GREY_LIGHT,
|
||||
TR::reset__check_backup_instructions,
|
||||
))),
|
||||
)
|
||||
.with_subtitle(TR::words__instructions.into())
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.map(|_| Some(FlowMsg::Confirmed));
|
||||
|
||||
let store = flow_store()
|
||||
|
@ -10,11 +10,13 @@ use crate::{
|
||||
ui::{
|
||||
component::{
|
||||
base::ComponentExt,
|
||||
swipe_detect::SwipeSettings,
|
||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt},
|
||||
Component,
|
||||
Component, SwipeDirection,
|
||||
},
|
||||
flow::{FlowMsg, Swipable, SwipePage},
|
||||
layout::util::ConfirmBlob,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
use heapless::Vec;
|
||||
@ -30,6 +32,7 @@ pub struct ConfirmBlobParams {
|
||||
menu_button: bool,
|
||||
chunkify: bool,
|
||||
text_mono: bool,
|
||||
swipe_down: bool,
|
||||
}
|
||||
|
||||
impl ConfirmBlobParams {
|
||||
@ -49,6 +52,7 @@ impl ConfirmBlobParams {
|
||||
menu_button: false,
|
||||
chunkify: false,
|
||||
text_mono: true,
|
||||
swipe_down: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +71,11 @@ impl ConfirmBlobParams {
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_swipe_down(mut self) -> Self {
|
||||
self.swipe_down = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_footer(
|
||||
mut self,
|
||||
instruction: TString<'static>,
|
||||
@ -89,7 +98,7 @@ impl ConfirmBlobParams {
|
||||
|
||||
pub fn into_layout(
|
||||
self,
|
||||
) -> Result<impl Component<Msg = FlowMsg> + Swipable<FlowMsg> + MaybeTrace, Error> {
|
||||
) -> Result<impl Component<Msg = FlowMsg> + Swipable + MaybeTrace, Error> {
|
||||
let paragraphs = ConfirmBlob {
|
||||
description: self.description.unwrap_or("".into()),
|
||||
extra: self.extra.unwrap_or("".into()),
|
||||
@ -107,7 +116,7 @@ impl ConfirmBlobParams {
|
||||
}
|
||||
.into_paragraphs();
|
||||
|
||||
let page = SwipePage::vertical(paragraphs);
|
||||
let page = SwipeContent::new(SwipePage::vertical(paragraphs));
|
||||
let mut frame = Frame::left_aligned(self.title, page);
|
||||
if let Some(subtitle) = self.subtitle {
|
||||
frame = frame.with_subtitle(subtitle);
|
||||
@ -117,7 +126,16 @@ impl ConfirmBlobParams {
|
||||
}
|
||||
if let Some(instruction) = self.footer_instruction {
|
||||
frame = frame.with_footer(instruction, self.footer_description);
|
||||
frame = frame.with_swipe(SwipeDirection::Left, SwipeSettings::default());
|
||||
}
|
||||
|
||||
if self.swipe_down {
|
||||
frame = frame.with_swipe(SwipeDirection::Down, SwipeSettings::default());
|
||||
}
|
||||
|
||||
frame = frame.with_swipe(SwipeDirection::Up, SwipeSettings::default());
|
||||
frame = frame.with_vertical_pages();
|
||||
|
||||
Ok(frame.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)))
|
||||
}
|
||||
}
|
||||
@ -130,6 +148,7 @@ pub struct ShowInfoParams {
|
||||
footer_instruction: Option<TString<'static>>,
|
||||
footer_description: Option<TString<'static>>,
|
||||
chunkify: bool,
|
||||
swipe_up: bool,
|
||||
items: Vec<(TString<'static>, TString<'static>), 4>,
|
||||
}
|
||||
|
||||
@ -143,6 +162,7 @@ impl ShowInfoParams {
|
||||
footer_instruction: None,
|
||||
footer_description: None,
|
||||
chunkify: false,
|
||||
swipe_up: false,
|
||||
items: Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -185,9 +205,14 @@ impl ShowInfoParams {
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_swipe_up(mut self) -> Self {
|
||||
self.swipe_up = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_layout(
|
||||
self,
|
||||
) -> Result<impl Component<Msg = FlowMsg> + Swipable<FlowMsg> + MaybeTrace, Error> {
|
||||
) -> Result<impl Component<Msg = FlowMsg> + Swipable + MaybeTrace, Error> {
|
||||
let mut paragraphs = ParagraphVecShort::new();
|
||||
let mut first: bool = true;
|
||||
for item in self.items {
|
||||
@ -212,19 +237,30 @@ impl ShowInfoParams {
|
||||
|
||||
let mut frame = Frame::left_aligned(
|
||||
self.title,
|
||||
SwipePage::vertical(paragraphs.into_paragraphs()),
|
||||
SwipeContent::new(SwipePage::vertical(paragraphs.into_paragraphs())),
|
||||
);
|
||||
if let Some(subtitle) = self.subtitle {
|
||||
frame = frame.with_subtitle(subtitle);
|
||||
}
|
||||
if self.cancel_button {
|
||||
frame = frame.with_cancel_button();
|
||||
frame = frame
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate());
|
||||
} else if self.menu_button {
|
||||
frame = frame.with_menu_button();
|
||||
frame = frame
|
||||
.with_menu_button()
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default());
|
||||
}
|
||||
if let Some(instruction) = self.footer_instruction {
|
||||
frame = frame.with_footer(instruction, self.footer_description);
|
||||
}
|
||||
|
||||
if self.swipe_up {
|
||||
frame = frame.with_swipe(SwipeDirection::Up, SwipeSettings::default());
|
||||
}
|
||||
|
||||
frame = frame.with_vertical_pages();
|
||||
|
||||
Ok(frame.map(move |msg| {
|
||||
matches!(msg, FrameMsg::Button(_)).then_some(if self.cancel_button {
|
||||
FlowMsg::Cancelled
|
||||
|
@ -8,10 +8,7 @@ use crate::{
|
||||
text::paragraphs::{Paragraph, ParagraphSource},
|
||||
ComponentExt, SwipeDirection,
|
||||
},
|
||||
flow::{
|
||||
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow,
|
||||
SwipePage,
|
||||
},
|
||||
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
|
||||
},
|
||||
};
|
||||
|
||||
@ -63,7 +60,10 @@ impl FlowState for WarningHiPrio {
|
||||
|
||||
use crate::{
|
||||
micropython::{map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
ui::{
|
||||
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
|
||||
model_mercury::component::SwipeContent,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
@ -89,10 +89,12 @@ impl WarningHiPrio {
|
||||
.with_top_padding(Self::EXTRA_PADDING),
|
||||
]
|
||||
.into_paragraphs();
|
||||
let content_message = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
|
||||
let content_message = Frame::left_aligned(title, SwipeContent::new(paragraphs))
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), Some(cancel))
|
||||
.with_danger()
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
|
||||
// .one_button_request(ButtonRequestCode::Warning, br_type);
|
||||
|
||||
@ -104,16 +106,16 @@ impl WarningHiPrio {
|
||||
.danger(theme::ICON_CHEVRON_RIGHT, confirm),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
.map(|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
});
|
||||
|
||||
// Cancelled
|
||||
let content_cancelled = IgnoreSwipe::new(
|
||||
let content_cancelled =
|
||||
Frame::left_aligned(done_title, StatusScreen::new_neutral_timeout())
|
||||
.with_footer(TR::instructions__continue_in_app.into(), None),
|
||||
)
|
||||
.with_footer(TR::instructions__continue_in_app.into(), None)
|
||||
.map(|_| Some(FlowMsg::Cancelled));
|
||||
|
||||
let store = flow_store()
|
||||
|
@ -12,11 +12,12 @@ use crate::{
|
||||
ui::{
|
||||
backlight::BACKLIGHT_LEVELS_OBJ,
|
||||
component::{
|
||||
base::ComponentExt,
|
||||
base::{AttachType, ComponentExt},
|
||||
connect::Connect,
|
||||
image::BlendedImage,
|
||||
jpeg::Jpeg,
|
||||
paginated::{PageMsg, Paginate},
|
||||
swipe_detect::SwipeSettings,
|
||||
text::{
|
||||
op::OpTextLayout,
|
||||
paragraphs::{
|
||||
@ -25,15 +26,16 @@ use crate::{
|
||||
},
|
||||
TextStyle,
|
||||
},
|
||||
Border, Component, Empty, FormattedText, Label, Never, Timeout,
|
||||
Border, Component, Empty, FormattedText, Label, Never, SwipeDirection, Timeout,
|
||||
},
|
||||
flow::Swipable,
|
||||
geometry,
|
||||
layout::{
|
||||
obj::{ComponentMsgObj, LayoutObj},
|
||||
result::{CANCELLED, CONFIRMED, INFO},
|
||||
util::{upy_disable_animation, ConfirmBlob, PropsList},
|
||||
},
|
||||
model_mercury::component::check_homescreen_format,
|
||||
model_mercury::component::{check_homescreen_format, SwipeContent},
|
||||
},
|
||||
};
|
||||
|
||||
@ -209,10 +211,7 @@ impl ComponentMsgObj for PromptScreen {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ComponentMsgObj for SwipeUpScreen<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
impl<T: Component + Swipable> ComponentMsgObj for SwipeUpScreen<T> {
|
||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||
match msg {
|
||||
SwipeUpScreenMsg::Content(_) => Err(Error::TypeError),
|
||||
@ -736,7 +735,8 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(TR::modify_amount__title.into(), paragraphs)
|
||||
.with_cancel_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None),
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
@ -778,7 +778,8 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(title, paragraphs)
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None),
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
@ -860,20 +861,21 @@ extern "C" fn new_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map)
|
||||
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||
let allow_cancel: bool = kwargs.get(Qstr::MP_QSTR_allow_cancel)?.try_into()?;
|
||||
|
||||
let content = SwipeUpScreen::new(Paragraphs::new([Paragraph::new(
|
||||
&theme::TEXT_MAIN_GREY_LIGHT,
|
||||
description,
|
||||
)]));
|
||||
let content = Paragraphs::new([Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description)]);
|
||||
let frame = if allow_cancel {
|
||||
Frame::left_aligned(title, content)
|
||||
Frame::left_aligned(title, SwipeContent::new(content))
|
||||
.with_cancel_button()
|
||||
.with_danger()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
} else {
|
||||
Frame::left_aligned(title, content)
|
||||
Frame::left_aligned(title, SwipeContent::new(content))
|
||||
.with_danger()
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
};
|
||||
|
||||
let frame = SwipeUpScreen::new(frame);
|
||||
let obj = LayoutObj::new(frame)?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
@ -918,15 +920,16 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?;
|
||||
let action: Option<TString> = kwargs.get(Qstr::MP_QSTR_button)?.try_into_option()?;
|
||||
|
||||
let content = SwipeUpScreen::new(Paragraphs::new([
|
||||
let content = Paragraphs::new([
|
||||
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description),
|
||||
Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value),
|
||||
]));
|
||||
let obj = LayoutObj::new(
|
||||
Frame::left_aligned(title, content)
|
||||
]);
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(title, SwipeContent::new(content))
|
||||
.with_warning_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), action),
|
||||
)?;
|
||||
.with_footer(TR::instructions__swipe_up.into(), action)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -936,10 +939,11 @@ extern "C" fn new_show_success(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let content = StatusScreen::new_success();
|
||||
let obj = LayoutObj::new(
|
||||
Frame::left_aligned(title, content)
|
||||
.with_footer(TR::instructions__swipe_up.into(), None),
|
||||
)?;
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(title, SwipeContent::new(content).with_normal_attach(None))
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -949,14 +953,12 @@ extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||
let content = SwipeUpScreen::new(Paragraphs::new([Paragraph::new(
|
||||
&theme::TEXT_MAIN_GREY_LIGHT,
|
||||
description,
|
||||
)]));
|
||||
let obj = LayoutObj::new(
|
||||
Frame::left_aligned(title, content)
|
||||
.with_footer(TR::instructions__swipe_up.into(), None),
|
||||
)?;
|
||||
let content = Paragraphs::new([Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description)]);
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(title, SwipeContent::new(content))
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -1070,7 +1072,8 @@ extern "C" fn new_confirm_with_info(n_args: usize, args: *const Obj, kwargs: *mu
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(title, paragraphs.into_paragraphs())
|
||||
.with_menu_button()
|
||||
.with_footer(TR::instructions__swipe_up.into(), Some(button)),
|
||||
.with_footer(TR::instructions__swipe_up.into(), Some(button))
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
@ -1227,8 +1230,7 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
paragraphs.add(Paragraph::new(style, text));
|
||||
}
|
||||
|
||||
let checklist_content = SwipeUpScreen::new(
|
||||
Checklist::from_paragraphs(
|
||||
let checklist_content = Checklist::from_paragraphs(
|
||||
theme::ICON_CHEVRON_RIGHT,
|
||||
theme::ICON_BULLET_CHECKMARK,
|
||||
active,
|
||||
@ -1237,12 +1239,17 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
.with_spacing(theme::CHECKLIST_SPACING),
|
||||
)
|
||||
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
|
||||
.with_icon_done_color(theme::GREEN),
|
||||
);
|
||||
let obj = LayoutObj::new(
|
||||
Frame::left_aligned(title, checklist_content)
|
||||
.with_footer(TR::instructions__swipe_up.into(), None),
|
||||
)?;
|
||||
.with_icon_done_color(theme::GREEN);
|
||||
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(
|
||||
title,
|
||||
SwipeContent::new(checklist_content)
|
||||
.with_normal_attach(Some(AttachType::Swipe(SwipeDirection::Up))),
|
||||
)
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -1264,12 +1271,12 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
|
||||
_ => TR::recovery__title.into(),
|
||||
};
|
||||
|
||||
let content = SwipeUpScreen::new(paragraphs);
|
||||
let obj = LayoutObj::new(
|
||||
Frame::left_aligned(notification, content)
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(notification, SwipeContent::new(paragraphs))
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_subtitle(TR::words__instructions.into()),
|
||||
)?;
|
||||
.with_subtitle(TR::words__instructions.into())
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
|
@ -63,7 +63,7 @@ impl Component for CoinJoinProgress {
|
||||
// Determinate ones are receiving Event::Progress events.
|
||||
if self.indeterminate {
|
||||
match event {
|
||||
Event::Attach => {
|
||||
Event::Attach(_) => {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
|
||||
|
@ -93,7 +93,7 @@ where
|
||||
_ if animation_disabled() => {
|
||||
return None;
|
||||
}
|
||||
Event::Attach if self.indeterminate => {
|
||||
Event::Attach(_) if self.indeterminate => {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
|
||||
|
@ -199,7 +199,7 @@ impl Component for PinKeyboard<'_> {
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
match event {
|
||||
// Set up timer to switch off warning prompt.
|
||||
Event::Attach if self.major_warning.is_some() => {
|
||||
Event::Attach(_) if self.major_warning.is_some() => {
|
||||
self.warning_timer = Some(ctx.request_timer(Duration::from_secs(2)));
|
||||
}
|
||||
// Hide warning, show major prompt.
|
||||
|
@ -81,6 +81,7 @@ def _require_confirm_change_pin(msg: ChangePin) -> Awaitable[None]:
|
||||
TR.pin__title_settings,
|
||||
description=TR.pin__change,
|
||||
verb=TR.buttons__change,
|
||||
prompt_screen=False,
|
||||
)
|
||||
|
||||
if not msg.remove and not has_pin: # setting new pin
|
||||
|
@ -15500,9 +15500,9 @@
|
||||
"T3T1_cs_test_autolock.py::test_dryrun_enter_word_slowly": "101892fa0a24f33279d07fda4477178a7b45fc04c01acbbf901833d4d024158f",
|
||||
"T3T1_cs_test_autolock.py::test_dryrun_locks_at_number_of_words": "55cdac02cdd20ce5249fabd5e63ad68386a796634fa21e2ef7cb1701e98c0020",
|
||||
"T3T1_cs_test_autolock.py::test_dryrun_locks_at_word_entry": "be2ba7ad30c636e7542ede4c2b39f6c65da16eed559557d559fa2a9afdb98de6",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "9baac2b705a9f38453c784d97f7db405f78c3ddad7d32e75acd6c9cfe418b69a",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "a289533058d32023964eecfaeaec3f0c5c489aa3ddacddb44c7944126a9e2c92",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "30add0f52ceec0d4aab7c8f4fbfa848b70748dc6b449fd35bb800ed65bfa6918",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "cf939a63908ea803ff46919cdf957e24fc1eaf9a830fb16033b910a1ad0b4c82",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "79241bbf8d46ac4497e668e7705152553c9c34c59658ed3b38fb52d70b613e24",
|
||||
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "d03f87e6020b3c9e47a5464a8a68752683b96746612274d91301c7ab4ffbca3e",
|
||||
"T3T1_cs_test_lock.py::test_hold_to_lock": "8e99b5897e849374c94b8bf9a581971bffa050bdf39a684967be642f66fca612",
|
||||
"T3T1_cs_test_passphrase_mercury.py::test_cycle_through_last_character": "bf5322dcec0e6560aec763f04e4c2f6afd3e056968644414f3acfe4203745c66",
|
||||
"T3T1_cs_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "9c272fbb29ebcf1d330827e863bdba92ef47b506843ef9108a43304c93dd9e86",
|
||||
@ -15549,9 +15549,9 @@
|
||||
"T3T1_de_test_autolock.py::test_dryrun_enter_word_slowly": "2f123bac78e5f12dfea18414032142e3c6efd601aa60b84a5a9e0e10a46ff85c",
|
||||
"T3T1_de_test_autolock.py::test_dryrun_locks_at_number_of_words": "5c221cc9751183eaa02e2347571819469c316bb1cde0d29a5a252cf0c7a5819e",
|
||||
"T3T1_de_test_autolock.py::test_dryrun_locks_at_word_entry": "5744408e0dc4ea581e209bf26abd86b29f34f89e39f8ceb125811670feee27f3",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "f0d1307d9bb9e842726e8616d4ababa60df4dcfd8e48f9777583c5fdc1d529be",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "1bf1275b1b0ae36c3df53e0df122c08ef0c1b11ad27dfda08a0e7ea4f9e13e5b",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "2d820b034627bebf99a0a054ed7ec3b81ad06f2b14803f67566e1776da943990",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "a7fcfa798f05f90df7ecf9a4e7b0f16c3c41999b995cfddd3249a890c21a50b1",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "f976c56e7f7e4302d27df9eb54f30cfce19d6dd553e5dbf42608ee579605fed0",
|
||||
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "ce7fc86f644dcbaae77fbd45f6e12004c3175fef69d5cb80b7eace5b38f0195f",
|
||||
"T3T1_de_test_lock.py::test_hold_to_lock": "dfecbd90b394f3b35f0cc2a0248c35b50427a67ed647499e5652ac51d6cc786c",
|
||||
"T3T1_de_test_passphrase_mercury.py::test_cycle_through_last_character": "5532543c77b2c9f64c16cc9f31371decda359063067dac21143c1b318174750d",
|
||||
"T3T1_de_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "58bf45df88e43fdc94381e7d98320311daac04740851aacc8e9930d7fcf95245",
|
||||
@ -15598,9 +15598,9 @@
|
||||
"T3T1_en_test_autolock.py::test_dryrun_enter_word_slowly": "f6862c4fdc00878f9c14339896139094701c34a89f4f9c32d4e03767540de60e",
|
||||
"T3T1_en_test_autolock.py::test_dryrun_locks_at_number_of_words": "d2423bdeb2923a2c06b59bf114a6219e4b918d7d3c061750985531b3f6ae8329",
|
||||
"T3T1_en_test_autolock.py::test_dryrun_locks_at_word_entry": "5f36a5d9d88476467ba31078701529dfdcdea2344944a8952e69dbfec238598f",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "62e797bcac12d651ab64b33db19dae577a31fd002945e3f495899b3a7004e415",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "ea977ed91b155f102f66fbaf519b9c764a3839bd47f64461d41a8d1677faeb1f",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "4c47a36836400172683dd2d47b38cbdde11bbbb702d4cac72a8b0778f24e2c2c",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "1cc207d1d95529d71febcf656c29e54272aa67d4853291e7bda746b0e01234c5",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "12a6032073d94c4f1be1176f087faea05d4393e2fce5d889375b7c84891de3d3",
|
||||
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "6b80f5fe9beede5b34269bd8747d86e93c11de489758f35eac92d8a6ce09a786",
|
||||
"T3T1_en_test_lock.py::test_hold_to_lock": "1665b2e4984bcc7a2f307a89789dc2d6c8db9c04a5906729f769e0e8b76da04c",
|
||||
"T3T1_en_test_passphrase_mercury.py::test_cycle_through_last_character": "e0faf9f3c0c83f6762dfa00323d7a69ef4f1f71ef99160b88541e9068f04e55a",
|
||||
"T3T1_en_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "51c45f969237398ccc41494bca87694b061578042c71508c61994ea47907e033",
|
||||
@ -16794,12 +16794,12 @@
|
||||
"T3T1_cs_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "a004391eed80bfe3a85e716311e04bcaea4223178ce2c24dd3a478c1103b3ae0",
|
||||
"T3T1_cs_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "627937945fa769ac6f8a05d1538f0fadceac61a1347ed748a7c9280e7a223b66",
|
||||
"T3T1_cs_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "901d35e416e91c264ebedd47854e9eed3601cfd851cfae5ca57940c1b5c3abc0",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "df132e2ca8201b12311cd4b525e108ace38049a4b95675579eaf2c4cfc0c379f",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced_Ext-10ea47d6": "bc727f137c9bfa6926f9da4645217b6003ef1b6c686bb165b1417afc15697214",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic_Extend-5dbe8b0f": "03717e33ab843a8706c5cd373d2b979351e0408d578109f4a1cc1ddd1bfe916a",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "d64a0585937fd4c01c4771b8fff082dea3b864da5588f88637f01210bd07e3c6",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced_Extend-8b11c1dc": "5459bb41b69480ccf1850e3cd3edf8cce8b85457403a929f98cab17aa6d432a5",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic_Extendabl-cc19e908": "17db466823df48752dc7f66b86d46fc4c9242d4af92668aa282985f64e520130",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "d57198c00b1fbc8d8aff6e9ceb7a28b33e13b0a497fbde18bb5338a706509fbf",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced_Ext-10ea47d6": "fffde13cd2189fd285cfba584b6257471be1703f5f3bea57d340fc80f5c4124e",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic_Extend-5dbe8b0f": "57630bbab5d3566a14403ca11cc93e21e9742ac0de27b6c9700744682a587cb1",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "5504101b5bccffc7c99f0b7306c8e4ee0d8f030c210b6b233993e0e961321fec",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced_Extend-8b11c1dc": "10069d5e7c8aed9894fd038ecf70c16f0196f6c6f0c0bf3e305760efe3deab32",
|
||||
"T3T1_cs_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic_Extendabl-cc19e908": "2643a0a963c0178087d7432eef8a1217979e7d064082dbc3dd96cd91805954de",
|
||||
"T3T1_cs_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "c61d87e076ae2251d0595371f8ab11f441d040475187da9739d6b5d469544915",
|
||||
"T3T1_cs_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "b81c0adf31a42588f04333364365ae4a2a17c41bba10f739839598768698f644",
|
||||
"T3T1_cs_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "17d88765a91d0123005233bbc9f943d67a021a7eb424f161aa80af98dd64d462",
|
||||
@ -17030,7 +17030,7 @@
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_change_failed": "1c81b081fb05d47982bdd595e3f71d4a911530e08dadfd5fbfe118421d0a66e7",
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_change_invalid_current": "e508b79f30baea0c05b6989aafcda142c1eb4751af38b3a0bfea05e30bb8d56d",
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_change_pin": "7e6a3b80d28d7d3496ab9e94c392b3a1a66a618fbd12d2552a327f8d017d5586",
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_pin_menu_cancel_setup": "d57405f5be43c7d0771c6153bb27e6abeca32cd50b0f048a4010d5aa15e3182a",
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_pin_menu_cancel_setup": "5435aba8f069b05c13fd1570cc39fe108195256267747d4cc1143b6789394a72",
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_remove_pin": "730b9ea97861bb3a2d375ab44ebeb81418949966246946ca906c9135e0cdbdf0",
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_set_failed": "69928e6b1ff42739a3dbdc07f6ef6ac0aae3b6983dac3018b03b325b96462f87",
|
||||
"T3T1_cs_test_msg_changepin_t2.py::test_set_pin": "ebf8a6aac47b252d84ad970de97f83ad0f8e00fe5baef5d1cf46362900b5adba",
|
||||
@ -17043,7 +17043,7 @@
|
||||
"T3T1_cs_test_msg_sd_protect.py::test_enable_disable": "d761f9a9ba40ca640572cbb95daaefdd8a6fb828f91547b41ae9e9cc4b071bdc",
|
||||
"T3T1_cs_test_msg_sd_protect.py::test_refresh": "3ba6fe4f822ad730666aa7df1b129c49432fec58819d843368308aafbbdb4f7b",
|
||||
"T3T1_cs_test_msg_sd_protect.py::test_wipe": "404eeec69de878aaad93ad8bf14c5614daa704ff84220ef288e4f5975fef2ea3",
|
||||
"T3T1_cs_test_msg_wipedevice.py::test_autolock_not_retained": "35d5299b8abb4b410bbb37e808a588b3f57191739ce433a23c9c671b0230a84d",
|
||||
"T3T1_cs_test_msg_wipedevice.py::test_autolock_not_retained": "1ba139c8e4297f637d67bb5338794fe012e2b4b907eadb06323e5b72e39509cc",
|
||||
"T3T1_cs_test_msg_wipedevice.py::test_wipe_device": "2f03f2f391cb5aa5869185bd3ec61c6950ab1840df7d080374da4cbbc0fcd5a3",
|
||||
"T3T1_cs_test_passphrase_slip39_advanced.py::test_128bit_passphrase": "a13cb96104505549e4ca94f033982cfdd70638c9b99aee3aba62bcdb77c2cd8b",
|
||||
"T3T1_cs_test_passphrase_slip39_advanced.py::test_256bit_passphrase": "a13cb96104505549e4ca94f033982cfdd70638c9b99aee3aba62bcdb77c2cd8b",
|
||||
@ -18179,12 +18179,12 @@
|
||||
"T3T1_de_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "f0fc86ffb3f74456a9c6282789bb4f71a319fc125da5fd5616636e311fd23eae",
|
||||
"T3T1_de_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "a04bf47e9b8ad1f7bc6545f6a2049fb0bb4211f66d340a501367eef323a173ab",
|
||||
"T3T1_de_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "093169b644e8cc008ea7caead9d2b1c59c18805359488924c501f90e6f5cd5b6",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "ed212433613715e7aaa5a80bb0b2111dc8988487820cc83d239059b7e8cd7584",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced_Ext-10ea47d6": "430a25654df0daf86c5ca74296ec129640aacfd5694810b1ca98cc97a8ed3bcc",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic_Extend-5dbe8b0f": "eeb3d9c92d56d99254a6bf9045da0547b4461a8b7b311eae5b12fc434be19d61",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "363a46acab4a0ac5cf5fa8733848a5ffb135740e9c99a4d15a06beba2301ae6e",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced_Extend-8b11c1dc": "2ad7b98efd5be80e34d1f37323b2206813e294e162ac3e8b79f69cf022868821",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic_Extendabl-cc19e908": "fe3e24d59b677a462d31cc44549d57101f439ad380114dfc027e9ff71d8863c1",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "a56344d6a6c46392d9565ce735add8e44e8f88b7eef39a2377f89a4463e3121f",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced_Ext-10ea47d6": "275d962f906bcbef3246543bcf4621debc70f0d3cd79e9a0e3d543bec4361545",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic_Extend-5dbe8b0f": "b5f858d874b814ce5a35fc1cf50cd0aafc521962d9d91c57c342813d7146f926",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "05883449c72ccfbc18fafcf0ceab1706bcad730508d5bda3d52966d8cbef6c6d",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced_Extend-8b11c1dc": "ce55afaceb64b9996cbb9235f3f9ec8e61fca5a1fb3c147f32c3796931ffa395",
|
||||
"T3T1_de_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic_Extendabl-cc19e908": "56baa91ccc304e729befbd8e086eecfaf88560babddcc8920f8f9096688dc398",
|
||||
"T3T1_de_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "fba566a05fc63d6d2308dfad2a31bb12b35fbc77a7a8d89f252c4626234f8a32",
|
||||
"T3T1_de_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "d8bd2de90bec49d4175cab2f5104193f754f672c99efc83a0692c7d13157a7e7",
|
||||
"T3T1_de_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "8ae6882dd2413c176b79798b2ed43bec9467e0635817ea8b848f04d91a7af7e4",
|
||||
@ -18415,7 +18415,7 @@
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_change_failed": "4cd2cca8836ebc77b09313f05f905d2b9f11c8684b2191242fbef92692433337",
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_change_invalid_current": "306ce6895ef75875f16e4f35b3c5f05a004bdd67967faad504402d772b2f0cef",
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_change_pin": "bb540789300781e12ec7b711f142ccfcf2f79d6c5e7ae98aaaba54cabf722e5d",
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_pin_menu_cancel_setup": "d708ac7f0bd9a5a640c68b6c0540a2f5c7414a93dbd6bcc9979bed6faac11a36",
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_pin_menu_cancel_setup": "8213ade8e7cfd4e496e6f28dc5a4c98b924a09be32930deb694eb1467bc5d490",
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_remove_pin": "367e749689b96e5b0e3fa39ff8f5378abb4fac06cce6f78abdf393a5f6587d73",
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_set_failed": "894b365102246efd03f8fe53dcfbaa1808d6f77b846b91ef62b5da2d66b4d60a",
|
||||
"T3T1_de_test_msg_changepin_t2.py::test_set_pin": "16c1f995ca4a929e2b4ca18324039906b831f9f9e8c969b9db008351d03f92cd",
|
||||
@ -18428,7 +18428,7 @@
|
||||
"T3T1_de_test_msg_sd_protect.py::test_enable_disable": "fe8a1f97e89304079cc295b418d5eac75ee441ccab46e0207db549863849b477",
|
||||
"T3T1_de_test_msg_sd_protect.py::test_refresh": "c643c038a11b3a301b20316fa0d2837c69841f5e074d9bdd0de0841d1f7b18d0",
|
||||
"T3T1_de_test_msg_sd_protect.py::test_wipe": "619f5d025dfd71d0e15ba018c7ecfd844e6f43be7896cf612a003398993462f0",
|
||||
"T3T1_de_test_msg_wipedevice.py::test_autolock_not_retained": "49e224d28671db584b0ed3524d8efb4f414bc1f03231b838172598f27886f755",
|
||||
"T3T1_de_test_msg_wipedevice.py::test_autolock_not_retained": "96fd8ddab0dbb82d6d0c116aea86f7f2eeb63372a084cdadb8c898de4d1461ec",
|
||||
"T3T1_de_test_msg_wipedevice.py::test_wipe_device": "92363bb8c8f23b9f81f1ce004de2e911fc517b17f63c7866047e257e414342c1",
|
||||
"T3T1_de_test_passphrase_slip39_advanced.py::test_128bit_passphrase": "0807342605b4bbcb2a62d17f1bb8468f89ad7242d8d5523151b7f0f91c68663c",
|
||||
"T3T1_de_test_passphrase_slip39_advanced.py::test_256bit_passphrase": "0807342605b4bbcb2a62d17f1bb8468f89ad7242d8d5523151b7f0f91c68663c",
|
||||
@ -19564,12 +19564,12 @@
|
||||
"T3T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "974c222954a10307ffcdcf4f2cfc458443d45f6158293ea9dfb91e613d5eaa63",
|
||||
"T3T1_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "0dcdbc898a66346a2781b20eed15ea9d2abe4ba6d968160d808f36bda4cc17a0",
|
||||
"T3T1_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "a805d3037b5eb7a4a87d264aa6d2a97097509f4b5d0a4d4c030321b741ad3ef3",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "e669290e2feef97631bfab5fb0d04401c5e5f00acbb2d259393c874934d1b65d",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced_Ext-10ea47d6": "7e7c5cd831de5a6bb145f68bcb8b7f75e944242ba076caa1d9dc57411d5b5c56",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic_Extend-5dbe8b0f": "d00dcc98c2ae1cfa0d36c9c6067839b88d32c29728883140d5b47fba84e21c11",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "cbc02f1465fe548c6a8d4bfbcdcbce11f84aab8331ef61780978be4f70fbf139",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced_Extend-8b11c1dc": "744cd1e9a722d50a1c841940f707292ad235df72ab404d72711f3eafef556df9",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic_Extendabl-cc19e908": "a77e280cc35809d034b7809a5556f642238b8c54b012bd1fc00e2b7525294366",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "7bed2cc8a8c0e35148b48d2323e4fcded2b2cc35cfdf3063f89410d6b4029e58",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced_Ext-10ea47d6": "66054413e53b696cdd65ac3274794bfb8943125af6b08db88f8c19a8c9533fcd",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic_Extend-5dbe8b0f": "68bfe53e51bccd3efdc1c4c6e1e14ce46756648307f336fdc3b9aa062b13601a",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "a03996ea71e63ced4ba94d24bea15a7ee63a3798dd1a145a4f7b36accc838f4a",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced_Extend-8b11c1dc": "3fec77c8c814c4dfeaeb35a8207cd6e001bdd84c3d103bf3f7b6d3c33532c260",
|
||||
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic_Extendabl-cc19e908": "94b9b9477ed2c637f2356e3e27da01ebadeafa5b4d90d35d9ea3a63f91043ecc",
|
||||
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "0373a0f99ec1445924dc60f071ca11f626176d9b7e40ba4e0b0afad195ad31e0",
|
||||
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "24e59539ea9627589fb6f660d90a5c61e1a7f91c50540cf43c360c5162dc85b6",
|
||||
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "9bfefe78931ad0662da0d3a811b90986ac615b306fbab341db6a93606e703b4d",
|
||||
@ -19800,7 +19800,7 @@
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_change_failed": "f3c671c723ce01ca2ccea0bfa4d07b442634d0fd7d5bbc649966309d8998034d",
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_change_invalid_current": "ea829f2c9af3d95e87fe72448b50fcc07fb2a72235811706f7394cf96f3fd2a9",
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_change_pin": "5293c5f59d99d5d741afafa202e4ca20f1d89c50e9d6b51adc2cf12f7ab151e3",
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_pin_menu_cancel_setup": "dc7a0e78034630a77c8e1a204f6df95a40df66b34dd1f764e89151174826b3ee",
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_pin_menu_cancel_setup": "c0501e1a2c84d9311ec207d3e819880fa8fee09de9b242e0fef1bc923b9bd1d6",
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_remove_pin": "f4fb7e28e42a490ac1d970eb91be9ee07faa2dd4b3b0c0d8ec70e2fb31922eb9",
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_set_failed": "0ebc831cb8454d21ff85175b67599c51bdc3af2018e2555ab786460d7c758bb8",
|
||||
"T3T1_en_test_msg_changepin_t2.py::test_set_pin": "be7f33fa7cbe3b8601c974a1c313f8efd13a0507e6f7e4667899da8263d5a391",
|
||||
@ -19813,7 +19813,7 @@
|
||||
"T3T1_en_test_msg_sd_protect.py::test_enable_disable": "aca3981d378d8ed504c5a4475941af91b57eb78d919a1c9c452fd437af696f78",
|
||||
"T3T1_en_test_msg_sd_protect.py::test_refresh": "b0a88717e57ee4b5af18527d282a8ee4f0f68563f70f4c0fd7165b145018f5d4",
|
||||
"T3T1_en_test_msg_sd_protect.py::test_wipe": "7482be64b3596739a22d970843085e1433b510e735fca6769e95382e5f4d0a65",
|
||||
"T3T1_en_test_msg_wipedevice.py::test_autolock_not_retained": "e7370d47a0d56e88fd4c59b3519a35ca7303b8915ce4e3ed16cdd7a03914592f",
|
||||
"T3T1_en_test_msg_wipedevice.py::test_autolock_not_retained": "2428552c5d27675cbfe2c19f01c317fbd7891b785badb59b21e739198f4e64a0",
|
||||
"T3T1_en_test_msg_wipedevice.py::test_wipe_device": "7c70b9ef9a09cec7bbdb4ee343b6d54bfe8e013dea28ab40894a72f8625ccea6",
|
||||
"T3T1_en_test_passphrase_slip39_advanced.py::test_128bit_passphrase": "2e8ce270ebe538a0576f6faceda02cc1b7e558be2a945b9798c17f7c60f33e72",
|
||||
"T3T1_en_test_passphrase_slip39_advanced.py::test_256bit_passphrase": "2e8ce270ebe538a0576f6faceda02cc1b7e558be2a945b9798c17f7c60f33e72",
|
||||
@ -22670,7 +22670,7 @@
|
||||
"T3T1_en_test_safety_checks.py::test_safety_checks_level_after_reboot[SafetyCheckLevel.PromptTempora-b3d21f4a": "22a4b35e9ffca23d723285ccbdde8786637fb5bdc15b45d1be4677c9b0f940f6",
|
||||
"T3T1_en_test_safety_checks.py::test_safety_checks_level_after_reboot[SafetyCheckLevel.Strict-Safety-f1ff9c26": "43ece0c802dffed4f0982daf8802de8df2a29ca5ab16c5390475f697d1ac61fb",
|
||||
"T3T1_en_test_shamir_persistence.py::test_abort": "699a1450e491a9059926afded4c4bac2fc20bc125adc6508e446457052015014",
|
||||
"T3T1_en_test_shamir_persistence.py::test_recovery_multiple_resets": "eb7f05a015e78939669902d95f47e8312d4312a364168343e51969ed03e6dec1",
|
||||
"T3T1_en_test_shamir_persistence.py::test_recovery_multiple_resets": "6da13841e94b968ebf1871dd18e20b73ff464f07eb21ebf6d510675505b22945",
|
||||
"T3T1_en_test_shamir_persistence.py::test_recovery_on_old_wallet": "a4c00fba813a30af023ce02cf7f554f736852f15f68241d88e1a1085cbc23b03",
|
||||
"T3T1_en_test_shamir_persistence.py::test_recovery_single_reset": "7c1d0aa16d3a4c19815262ef18bf53796d3cefa5d71f19775ddf95acc153055a",
|
||||
"T3T1_en_test_wipe_code.py::test_wipe_code_activate_core": "74483206af4a9ffa769eb4ee55f44ad4a9ab9429ca76169f8e01ee3d496826b4"
|
||||
|
Loading…
Reference in New Issue
Block a user