diff --git a/core/embed/rust/src/ui/api/firmware_upy.rs b/core/embed/rust/src/ui/api/firmware_upy.rs index 334244afc1..3993b0efc2 100644 --- a/core/embed/rust/src/ui/api/firmware_upy.rs +++ b/core/embed/rust/src/ui/api/firmware_upy.rs @@ -13,7 +13,8 @@ use crate::{ layout::{ base::LAYOUT_STATE, obj::{LayoutObj, ATTACH_TYPE_OBJ}, - result::{CANCELLED, CONFIRMED, INFO}, util::upy_disable_animation, + result::{CANCELLED, CONFIRMED, INFO}, + util::upy_disable_animation, }, ui_features::ModelUI, ui_features_fw::UIFeaturesFirmware, @@ -24,6 +25,51 @@ use crate::{ // UIFeaturesFirmware` // NOTE: `disable_animation` not a part of trait UiFeaturesFirmware +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 = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?; + let description: Option = + kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; + let subtitle: Option = kwargs + .get(Qstr::MP_QSTR_subtitle) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let verb: Option = kwargs + .get(Qstr::MP_QSTR_verb) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let verb_cancel: Option = kwargs + .get(Qstr::MP_QSTR_verb_cancel) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + 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 reverse: bool = kwargs.get_or(Qstr::MP_QSTR_reverse, false)?; + let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, false)?; + let prompt_title: Option = kwargs + .get(Qstr::MP_QSTR_prompt_title) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + + let layout = ModelUI::confirm_action( + title, + action, + description, + subtitle, + verb, + verb_cancel, + hold, + hold_danger, + reverse, + prompt_screen, + prompt_title, + )?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_request_bip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; @@ -200,6 +246,23 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// """Disable animations, debug builds only.""" Qstr::MP_QSTR_disable_animation => obj_fn_1!(upy_disable_animation).as_obj(), + /// 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, + /// prompt_title: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Confirm action.""" + Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(), + /// def request_bip39( /// *, /// prompt: str, diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 2703df9e8e..46cf1e269c 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -324,40 +324,6 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -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 = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let subtitle: Option = kwargs - .get(Qstr::MP_QSTR_subtitle) - .unwrap_or(Obj::const_none()) - .try_into_option()?; - let verb_cancel: Option = 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 prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, false)?; - let prompt_title: TString = kwargs.get_or(Qstr::MP_QSTR_prompt_title, title)?; - - let flow = flow::confirm_action::new_confirm_action( - title, - action, - description, - subtitle, - verb_cancel, - reverse, - hold, - prompt_screen, - prompt_title, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} extern "C" fn new_confirm_address(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()?; @@ -1861,5 +1827,5 @@ pub static mp_module_trezorui2: Module = obj_module! { /// cancel_text: str | None = None, /// ) -> LayoutObj[UiResult]: /// """Total summary and hold to confirm.""" - Qstr::MP_QSTR_flow_confirm_summary => obj_fn_kw!(0, flow::new_confirm_summary).as_obj(), + Qstr::MP_QSTR_flow_confirm_summary => obj_fn_kw!(0, new_confirm_summary).as_obj(), }; diff --git a/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs b/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs index 9fed76ed92..be190a38ea 100644 --- a/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs +++ b/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs @@ -18,7 +18,8 @@ use crate::{ use super::{ component::{ Bip39Input, Frame, MnemonicKeyboard, PinKeyboard, Slip39Input, SwipeContent, SwipeUpScreen, - }, flow, theme, ModelMercuryFeatures + }, + flow, theme, ModelMercuryFeatures, }; impl UIFeaturesFirmware for ModelMercuryFeatures { diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index ff6651bf7c..22ed86459d 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -270,42 +270,6 @@ fn content_in_button_page( Ok(obj.into()) } -extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let action: Option = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let verb: TString<'static> = - kwargs.get_or(Qstr::MP_QSTR_verb, TR::buttons__confirm.into())?; - let verb_cancel: Option> = 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 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_BOLD, action)) - .add(Paragraph::new(&theme::TEXT_NORMAL, description)); - } else { - paragraphs - .add(Paragraph::new(&theme::TEXT_NORMAL, description)) - .add(Paragraph::new(&theme::TEXT_BOLD, action)); - } - paragraphs.into_paragraphs() - }; - - content_in_button_page(title, paragraphs, verb, verb_cancel, hold) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_confirm_blob(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()?; diff --git a/core/embed/rust/src/ui/model_tr/ui_features_fw.rs b/core/embed/rust/src/ui/model_tr/ui_features_fw.rs index 1c8946ad70..ff329af7b1 100644 --- a/core/embed/rust/src/ui/model_tr/ui_features_fw.rs +++ b/core/embed/rust/src/ui/model_tr/ui_features_fw.rs @@ -6,8 +6,8 @@ use crate::{ strutil::TString, ui::{ component::{ - text::paragraphs::{Paragraph, Paragraphs}, - ComponentExt, Timeout, + text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, + Component, ComponentExt, Paginate, Timeout, }, layout::obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, ui_features_fw::UIFeaturesFirmware, @@ -15,7 +15,10 @@ use crate::{ }; use super::{ - component::{Frame, PassphraseEntry, PinEntry, WordlistEntry, WordlistType}, + component::{ + ButtonDetails, ButtonPage, Frame, PassphraseEntry, PinEntry, ScrollableFrame, + WordlistEntry, WordlistType, + }, theme, ModelTRFeatures, }; @@ -135,3 +138,40 @@ impl UIFeaturesFirmware for ModelTRFeatures { Ok(obj) } } + +/// Function to create and call a `ButtonPage` dialog based on paginable content +/// (e.g. `Paragraphs` or `FormattedText`). +/// Has optional title (supply empty `TString` for that) and hold-to-confirm +/// functionality. +fn content_in_button_page( + title: TString<'static>, + content: T, + verb: TString<'static>, + verb_cancel: Option>, + hold: bool, +) -> Result { + // Left button - icon, text or nothing. + let cancel_btn = verb_cancel.map(ButtonDetails::from_text_possible_icon); + + // Right button - text or nothing. + // Optional HoldToConfirm + let mut confirm_btn = if !verb.is_empty() { + Some(ButtonDetails::text(verb)) + } else { + None + }; + if hold { + confirm_btn = confirm_btn.map(|btn| btn.with_default_duration()); + } + + let content = ButtonPage::new(content, theme::BG) + .with_cancel_btn(cancel_btn) + .with_confirm_btn(confirm_btn); + + let mut frame = ScrollableFrame::new(content); + if !title.is_empty() { + frame = frame.with_title(title); + } + + Ok(RootComponent::new(frame)) +} diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index c8dae062e5..bce5b3439e 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -317,54 +317,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 = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let verb: Option = kwargs - .get(Qstr::MP_QSTR_verb) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let verb_cancel: Option = 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(theme::label_title(), 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()?; @@ -1574,23 +1526,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// from trezorui_api import * /// - /// 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, - /// prompt_title: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm action.""" - Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(), - /// def confirm_emphasized( /// *, /// title: str, diff --git a/core/embed/rust/src/ui/model_tt/ui_features_fw.rs b/core/embed/rust/src/ui/model_tt/ui_features_fw.rs index c4bc5dad30..3bbc3f5ef3 100644 --- a/core/embed/rust/src/ui/model_tt/ui_features_fw.rs +++ b/core/embed/rust/src/ui/model_tt/ui_features_fw.rs @@ -5,7 +5,11 @@ use crate::{ strutil::TString, translations::TR, ui::{ - component::{image::BlendedImage, ComponentExt, Empty, Timeout}, + component::{ + image::BlendedImage, + text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt}, + ComponentExt, Empty, Timeout, + }, layout::obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, ui_features_fw::UIFeaturesFirmware, }, @@ -13,8 +17,8 @@ use crate::{ use super::{ component::{ - Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, IconDialog, - MnemonicKeyboard, PassphraseKeyboard, PinKeyboard, Slip39Input, + Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet, CancelConfirmMsg, Frame, + IconDialog, MnemonicKeyboard, PassphraseKeyboard, PinKeyboard, Slip39Input, }, theme, ModelTTFeatures, }; diff --git a/core/embed/rust/src/ui/ui_features_fw.rs b/core/embed/rust/src/ui/ui_features_fw.rs index 4a08fc7042..7679851cd2 100644 --- a/core/embed/rust/src/ui/ui_features_fw.rs +++ b/core/embed/rust/src/ui/ui_features_fw.rs @@ -2,6 +2,7 @@ use crate::{error::Error, io::BinaryData, micropython::gc::Gc, strutil::TString} use super::layout::obj::{LayoutMaybeTrace, LayoutObj}; + pub trait UIFeaturesFirmware { fn confirm_action( title: TString<'static>, diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index a1f1647392..840673ec93 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -78,6 +78,24 @@ def disable_animation(disable: bool) -> None: """Disable animations, debug builds only.""" +# rust/src/ui/api/firmware_upy.rs +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, + prompt_title: str | None = None, +) -> LayoutObj[UiResult]: + """Confirm action.""" + + # rust/src/ui/api/firmware_upy.rs def request_bip39( *, diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index 32723b8ae6..1738eb1747 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -44,7 +44,7 @@ def confirm_action( description = description.format(description_param) return raise_if_not_confirmed( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=title, action=action, description=description, diff --git a/core/src/trezor/ui/layouts/mercury/fido.py b/core/src/trezor/ui/layouts/mercury/fido.py index 6ab7aa784a..93ed556ce7 100644 --- a/core/src/trezor/ui/layouts/mercury/fido.py +++ b/core/src/trezor/ui/layouts/mercury/fido.py @@ -44,7 +44,7 @@ async def confirm_fido_reset() -> bool: from trezor import TR confirm = ui.Layout( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=TR.fido__title_reset, action=TR.fido__erase_credentials, description=TR.words__really_wanna, diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index dbcc022b3a..ddccc17f77 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -70,7 +70,7 @@ def confirm_action( description = description.format(description_param) return raise_if_not_confirmed( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=title, action=action, description=description, @@ -146,7 +146,7 @@ async def prompt_backup() -> bool: return True result = await interact( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=TR.backup__title_skip, action=None, description=TR.backup__want_to_skip, diff --git a/core/src/trezor/ui/layouts/tr/fido.py b/core/src/trezor/ui/layouts/tr/fido.py index 4aaaad634d..b2c85ac540 100644 --- a/core/src/trezor/ui/layouts/tr/fido.py +++ b/core/src/trezor/ui/layouts/tr/fido.py @@ -34,7 +34,7 @@ async def confirm_fido( async def confirm_fido_reset() -> bool: from trezor import TR - confirm = trezorui2.confirm_action( + confirm = trezorui_api.confirm_action( title=TR.fido__title_reset, description=TR.fido__wanna_erase_credentials, action=None, diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index ff447deef0..5cbc8f30ee 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -42,7 +42,7 @@ def confirm_action( description = description.format(description_param) return raise_if_not_confirmed( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=title, action=action, description=description, @@ -108,7 +108,7 @@ async def show_wallet_created_success() -> None: # TODO cleanup @ redesign async def prompt_backup() -> bool: result = await interact( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=TR.words__title_success, action=TR.backup__new_wallet_successfully_created, description=TR.backup__it_should_be_backed_up, @@ -123,7 +123,7 @@ async def prompt_backup() -> bool: return True result = await interact( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=TR.words__warning, action=TR.backup__want_to_skip, description=TR.backup__can_back_up_anytime, diff --git a/core/src/trezor/ui/layouts/tt/fido.py b/core/src/trezor/ui/layouts/tt/fido.py index 76d071ab69..ef435a026c 100644 --- a/core/src/trezor/ui/layouts/tt/fido.py +++ b/core/src/trezor/ui/layouts/tt/fido.py @@ -53,7 +53,7 @@ async def confirm_fido_reset() -> bool: from trezor import TR confirm = ui.Layout( - trezorui2.confirm_action( + trezorui_api.confirm_action( title=TR.fido__title_reset, action=TR.fido__erase_credentials, description=TR.words__really_wanna,