1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 05:28:40 +00:00

feat(core/ui): T3T1 confirm_action

Adds SwipeFlow for ConfirmAction or ConfirmActionSimple. Without
animation as of now.

[no changelog]
This commit is contained in:
obrusvit 2024-05-09 23:54:33 +02:00 committed by Martin Milata
parent d8f20616be
commit 1ee36baea8
5 changed files with 218 additions and 49 deletions

View File

@ -358,6 +358,7 @@ static void _librust_qstrs(void) {
MP_QSTR_progress__x_seconds_left_template;
MP_QSTR_progress_event;
MP_QSTR_prompt;
MP_QSTR_prompt_screen;
MP_QSTR_qr_title;
MP_QSTR_reboot_to_bootloader__just_a_moment;
MP_QSTR_reboot_to_bootloader__restart;

View File

@ -0,0 +1,210 @@
use crate::{
error,
strutil::TString,
translations::TR,
ui::{
component::{text::paragraphs::Paragraph, ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
},
};
use super::super::{
component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg},
theme,
};
// TODO: merge with code from https://github.com/trezor/trezor-firmware/pull/3805
// when ready
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmAction {
Intro,
Menu,
Confirm,
}
/// ConfirmAction flow without a separate "Tap to confirm" or "Hold to confirm"
/// screen. Swiping up directly from the intro screen confirms action.
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmActionSimple {
Intro,
Menu,
}
impl FlowState for ConfirmAction {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmAction::Intro, SwipeDirection::Left) => {
Decision::Goto(ConfirmAction::Menu, direction)
}
(ConfirmAction::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmAction::Intro, direction)
}
(ConfirmAction::Intro, SwipeDirection::Up) => {
Decision::Goto(ConfirmAction::Confirm, direction)
}
(ConfirmAction::Confirm, SwipeDirection::Down) => {
Decision::Goto(ConfirmAction::Intro, direction)
}
(ConfirmAction::Confirm, SwipeDirection::Left) => {
Decision::Goto(ConfirmAction::Menu, direction)
}
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(ConfirmAction::Intro, FlowMsg::Info) => {
Decision::Goto(ConfirmAction::Menu, SwipeDirection::Left)
}
(ConfirmAction::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmAction::Intro, SwipeDirection::Right)
}
(ConfirmAction::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
(ConfirmAction::Confirm, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed),
(ConfirmAction::Confirm, FlowMsg::Info) => {
Decision::Goto(ConfirmAction::Menu, SwipeDirection::Left)
}
_ => Decision::Nothing,
}
}
}
impl FlowState for ConfirmActionSimple {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmActionSimple::Intro, SwipeDirection::Left) => {
Decision::Goto(ConfirmActionSimple::Menu, direction)
}
(ConfirmActionSimple::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmActionSimple::Intro, direction)
}
(ConfirmActionSimple::Intro, SwipeDirection::Up) => {
Decision::Return(FlowMsg::Confirmed)
}
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(ConfirmActionSimple::Intro, FlowMsg::Info) => {
Decision::Goto(ConfirmActionSimple::Menu, SwipeDirection::Left)
}
(ConfirmActionSimple::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmActionSimple::Intro, SwipeDirection::Right)
}
(ConfirmActionSimple::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
ui::{
component::text::paragraphs::{ParagraphSource, ParagraphVecShort, VecExt},
layout::obj::LayoutObj,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, new_confirm_action_obj) }
}
fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let action: Option<TString> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
let description: Option<TString> = kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
// let verb: Option<TString> = kwargs
// .get(Qstr::MP_QSTR_verb)
// .unwrap_or_else(|_| Obj::const_none())
// .try_into_option()?;
let verb_cancel: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let reverse: bool = kwargs.get_or(Qstr::MP_QSTR_reverse, false)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
// let hold_danger: bool = kwargs.get_or(Qstr::MP_QSTR_hold_danger, false)?;
let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, false)?;
let paragraphs = {
let action = action.unwrap_or("".into());
let description = description.unwrap_or("".into());
let mut paragraphs = ParagraphVecShort::new();
if !reverse {
paragraphs
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, action))
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description));
} else {
paragraphs
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description))
.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, action));
}
paragraphs.into_paragraphs()
};
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
let content_menu = if let Some(verb_cancel) = verb_cancel {
Frame::left_aligned(
"".into(),
VerticalMenu::empty().danger(theme::ICON_CANCEL, verb_cancel.into()),
)
} else {
Frame::left_aligned(
"".into(),
VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()),
)
}
.with_cancel_button()
.map(move |msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(_)) => Some(FlowMsg::Choice(0)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
if !prompt_screen {
let store = flow_store().add(content_intro)?.add(content_menu)?;
let res = SwipeFlow::new(ConfirmActionSimple::Intro, store)?;
return Ok(LayoutObj::new(res)?.into());
} else {
let (prompt, prompt_action) = if hold {
(
PromptScreen::new_hold_to_confirm(),
TR::instructions__hold_to_confirm.into(),
)
} else {
(
PromptScreen::new_tap_to_confirm(),
TR::instructions__tap_to_confirm.into(),
)
};
let content_confirm = Frame::left_aligned(title, prompt)
.with_footer(prompt_action, None)
.with_menu_button();
// .with_overlapping_content();
// if let Some(subtitle) = subtitle {
// content_confirm = content_confirm.with_subtitle(subtitle);
// }
let content_confirm = content_confirm.map(move |msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
FrameMsg::Button(_) => Some(FlowMsg::Info),
});
let store = flow_store()
.add(content_intro)?
.add(content_menu)?
.add(content_confirm)?;
let res = SwipeFlow::new(ConfirmAction::Intro, store)?;
return Ok(LayoutObj::new(res)?.into());
};
}

View File

@ -1,3 +1,4 @@
pub mod confirm_action;
pub mod confirm_reset_create;
pub mod confirm_reset_recover;
pub mod confirm_set_new_pin;
@ -6,6 +7,7 @@ pub mod prompt_backup;
pub mod show_share_words;
pub mod warning_hi_prio;
pub use confirm_action::new_confirm_action;
pub use confirm_reset_create::ConfirmResetCreate;
pub use confirm_reset_recover::ConfirmResetRecover;
pub use confirm_set_new_pin::SetNewPin;

View File

@ -331,54 +331,6 @@ impl ComponentMsgObj for super::component::bl_confirm::Confirm<'_> {
}
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let action: Option<TString> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
let description: Option<TString> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let verb: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb_cancel: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let reverse: bool = kwargs.get_or(Qstr::MP_QSTR_reverse, false)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let hold_danger: bool = kwargs.get_or(Qstr::MP_QSTR_hold_danger, false)?;
let paragraphs = {
let action = action.unwrap_or("".into());
let description = description.unwrap_or("".into());
let mut paragraphs = ParagraphVecShort::new();
if !reverse {
paragraphs
.add(Paragraph::new(&theme::TEXT_DEMIBOLD, action))
.add(Paragraph::new(&theme::TEXT_NORMAL, description));
} else {
paragraphs
.add(Paragraph::new(&theme::TEXT_NORMAL, description))
.add(Paragraph::new(&theme::TEXT_DEMIBOLD, action));
}
paragraphs.into_paragraphs()
};
let mut page = if hold {
ButtonPage::new(paragraphs, theme::BG).with_hold()?
} else {
ButtonPage::new(paragraphs, theme::BG).with_cancel_confirm(verb_cancel, verb)
};
if hold && hold_danger {
page = page.with_confirm_style(theme::button_danger())
}
let obj = LayoutObj::new(Frame::left_aligned(title, page))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -1610,14 +1562,16 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str,
/// action: str | None,
/// description: str | None,
/// subtitle: str | None = None,
/// verb: str | None = None,
/// verb_cancel: str | None = None,
/// hold: bool = False,
/// hold_danger: bool = False,
/// reverse: bool = False,
/// prompt_screen: bool = False,
/// ) -> LayoutObj[UiResult]:
/// """Confirm action."""
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, flow::confirm_action::new_confirm_action).as_obj(),
/// def confirm_emphasized(
/// *,

View File

@ -76,11 +76,13 @@ def confirm_action(
title: str,
action: str | None,
description: str | None,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
hold: bool = False,
hold_danger: bool = False,
reverse: bool = False,
prompt_screen: bool = False,
) -> LayoutObj[UiResult]:
"""Confirm action."""