From 84ca196f35bc3125aa4833c86c05ff8e1bd8e545 Mon Sep 17 00:00:00 2001 From: obrusvit Date: Tue, 5 Nov 2024 09:41:43 +0100 Subject: [PATCH] refactor(core): move show_success/warning/error - return type is Gc - new_show_modal removed from model_t layout.rs --- core/embed/rust/librust_qstr.h | 1 - core/embed/rust/src/ui/api/firmware_upy.rs | 87 +++++++++++ .../embed/rust/src/ui/model_mercury/layout.rs | 113 -------------- .../src/ui/model_mercury/ui_features_fw.rs | 95 +++++++++++- core/embed/rust/src/ui/model_tr/layout.rs | 41 ----- .../rust/src/ui/model_tr/ui_features_fw.rs | 54 +++++++ core/embed/rust/src/ui/model_tt/layout.rs | 146 ------------------ .../rust/src/ui/model_tt/ui_features_fw.rs | 85 +++++++++- core/embed/rust/src/ui/ui_features_fw.rs | 33 +++- core/mocks/generated/trezorui2.pyi | 86 ----------- core/mocks/generated/trezorui_api.pyi | 38 +++++ .../src/trezor/ui/layouts/mercury/__init__.py | 11 +- .../src/trezor/ui/layouts/mercury/recovery.py | 2 +- core/src/trezor/ui/layouts/mercury/reset.py | 4 +- core/src/trezor/ui/layouts/tr/__init__.py | 5 +- core/src/trezor/ui/layouts/tt/__init__.py | 10 +- core/src/trezor/ui/layouts/tt/recovery.py | 2 +- core/src/trezor/ui/layouts/tt/reset.py | 2 +- 18 files changed, 403 insertions(+), 412 deletions(-) diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 3f2ca5aa90..74e82a4444 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -741,7 +741,6 @@ static void _librust_qstrs(void) { MP_QSTR_verb_info; MP_QSTR_verify; MP_QSTR_version; - MP_QSTR_warning; MP_QSTR_wipe__info; MP_QSTR_wipe__title; MP_QSTR_wipe__want_to_wipe; diff --git a/core/embed/rust/src/ui/api/firmware_upy.rs b/core/embed/rust/src/ui/api/firmware_upy.rs index c3dc4d9eef..8b38a56457 100644 --- a/core/embed/rust/src/ui/api/firmware_upy.rs +++ b/core/embed/rust/src/ui/api/firmware_upy.rs @@ -299,6 +299,20 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_show_error(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 button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; + let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; + let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?; + let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; + + let layout = ModelUI::show_error(title, button, description, allow_cancel, time_ms)?; + Ok(layout.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_show_group_share_success( n_args: usize, args: *const Obj, @@ -418,6 +432,20 @@ extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs: unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_show_success(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 button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; + let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, TString::empty())?; + let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, false)?; + let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; + + let layout = ModelUI::show_success(title, button, description, allow_cancel, time_ms)?; + Ok(layout.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_show_wait_text(message: Obj) -> Obj { let block = || { let message: TString<'static> = message.try_into()?; @@ -429,6 +457,30 @@ extern "C" fn new_show_wait_text(message: Obj) -> Obj { unsafe { util::try_or_raise(block) } } +extern "C" fn new_show_warning(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 button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; + let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; + let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; + let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?; + let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; + let danger: bool = kwargs.get_or(Qstr::MP_QSTR_danger, false)?; + + let layout = ModelUI::show_warning( + title, + button, + value, + description, + allow_cancel, + time_ms, + danger, + )?; + Ok(layout.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = |_args: &[Obj], _kwargs: &Map| { let layout = ModelUI::tutorial()?; @@ -700,6 +752,17 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// mark next to them. Limited to 3 items.""" Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), + /// def show_error( + /// *, + /// title: str, + /// button: str, + /// description: str = "", + /// allow_cancel: bool = True, + /// time_ms: int = 0, + /// ) -> LayoutObj[UiResult]: + /// """Error modal. No buttons shown when `button` is empty string.""" + Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(), + /// def show_group_share_success( /// *, /// lines: Iterable[str], @@ -771,10 +834,34 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// """Shows SLIP39 state after info button is pressed on `confirm_recovery`.""" Qstr::MP_QSTR_show_remaining_shares => obj_fn_kw!(0, new_show_remaining_shares).as_obj(), + /// def show_success( + /// *, + /// title: str, + /// button: str, + /// description: str = "", + /// allow_cancel: bool = True, + /// time_ms: int = 0, + /// ) -> LayoutObj[UiResult]: + /// """Success modal. No buttons shown when `button` is empty string.""" + Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(), + /// def show_wait_text(message: str, /) -> LayoutObj[None]: /// """Show single-line text in the middle of the screen.""" Qstr::MP_QSTR_show_wait_text => obj_fn_1!(new_show_wait_text).as_obj(), + /// def show_warning( + /// *, + /// title: str, + /// button: str, + /// value: str = "", + /// description: str = "", + /// allow_cancel: bool = True, + /// time_ms: int = 0, + /// danger: bool = False, # unused on TT + /// ) -> LayoutObj[UiResult]: + /// """Warning modal. TT: No buttons shown when `button` is empty string. TR: middle button and centered text.""" + Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(), + /// def tutorial() -> LayoutObj[UiResult]: /// """Show user how to interact with the device.""" Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, new_tutorial).as_obj(), diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index b9d8ccf09c..261b2433c8 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -658,32 +658,6 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_show_error(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 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 = Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description)); - let frame = if allow_cancel { - Frame::left_aligned(title, SwipeContent::new(content)) - .with_cancel_button() - .with_danger() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_swipe(Direction::Up, SwipeSettings::default()) - } else { - Frame::left_aligned(title, SwipeContent::new(content)) - .with_danger() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_swipe(Direction::Up, SwipeSettings::default()) - }; - - let frame = SwipeUpScreen::new(frame); - Ok(LayoutObj::new(frame)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_show_share_words(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()?; @@ -716,58 +690,6 @@ extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_show_warning(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 description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; - let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; - let action: Option = kwargs.get(Qstr::MP_QSTR_button)?.try_into_option()?; - let danger: bool = kwargs.get_or(Qstr::MP_QSTR_danger, false)?; - - let content = ParagraphVecShort::from_iter([ - Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description), - Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value), - ]) - .into_paragraphs(); - - let frame = Frame::left_aligned(title, SwipeContent::new(content)) - .with_footer(TR::instructions__swipe_up.into(), action) - .with_swipe(Direction::Up, SwipeSettings::default()); - - let frame_with_icon = if danger { - frame.with_danger_icon() - } else { - frame.with_warning_low_icon() - }; - - Ok(LayoutObj::new(SwipeUpScreen::new(frame_with_icon))?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_success(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 description: Option = kwargs - .get(Qstr::MP_QSTR_description)? - .try_into_option()? - .and_then(|desc: TString| if desc.is_empty() { None } else { Some(desc) }); - - let content = StatusScreen::new_success(title); - let obj = LayoutObj::new(SwipeUpScreen::new( - Frame::left_aligned( - TR::words__title_success.into(), - SwipeContent::new(content).with_no_attach_anim(), - ) - .with_footer(TR::instructions__swipe_up.into(), description) - .with_result_icon(ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT) - .with_swipe(Direction::Up, SwipeSettings::default()), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; @@ -1051,41 +973,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """ Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), - /// def show_error( - /// *, - /// title: str, - /// button: str = "CONTINUE", - /// description: str = "", - /// allow_cancel: bool = False, - /// time_ms: int = 0, - /// ) -> LayoutObj[UiResult]: - /// """Error modal. No buttons shown when `button` is empty string.""" - Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(), - - /// def show_warning( - /// *, - /// title: str, - /// button: str = "CONTINUE", - /// value: str = "", - /// description: str = "", - /// allow_cancel: bool = False, - /// time_ms: int = 0, - /// danger: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Warning modal. No buttons shown when `button` is empty string.""" - Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(), - - /// def show_success( - /// *, - /// title: str, - /// button: str = "CONTINUE", - /// description: str = "", - /// allow_cancel: bool = False, - /// time_ms: int = 0, - /// ) -> LayoutObj[UiResult]: - /// """Success screen. Description is used in the footer.""" - Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(), - /// def show_simple( /// *, /// title: str | None, 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 aee8c571ff..30248460c1 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 @@ -8,10 +8,13 @@ use crate::{ translations::TR, ui::{ component::{ - connect::Connect, swipe_detect::SwipeSettings, text::paragraphs::{ + connect::Connect, + swipe_detect::SwipeSettings, + text::paragraphs::{ Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, Paragraphs, VecExt, - }, CachedJpeg, ComponentExt, Empty, Never, Timeout + }, + CachedJpeg, ComponentExt, Empty, Never, Timeout, }, geometry::{self, Direction}, layout::{ @@ -25,8 +28,8 @@ use crate::{ use super::{ component::{ check_homescreen_format, Bip39Input, CoinJoinProgress, Frame, Homescreen, Lockscreen, - MnemonicKeyboard, PinKeyboard, Progress, SelectWordCount, Slip39Input, SwipeContent, - SwipeUpScreen, VerticalMenu, + MnemonicKeyboard, PinKeyboard, Progress, SelectWordCount, Slip39Input, StatusScreen, + SwipeContent, SwipeUpScreen, VerticalMenu, }, flow::{self, new_confirm_action_simple, ConfirmActionMenu, ConfirmActionStrings}, theme, ModelMercuryFeatures, @@ -356,6 +359,31 @@ impl UIFeaturesFirmware for ModelMercuryFeatures { Ok(layout) } + fn show_error( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error> { + let content = Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description)); + let frame = if allow_cancel { + Frame::left_aligned(title, SwipeContent::new(content)) + .with_cancel_button() + .with_danger() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()) + } else { + Frame::left_aligned(title, SwipeContent::new(content)) + .with_danger() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()) + }; + + let obj = LayoutObj::new(SwipeUpScreen::new(frame))?; + Ok(obj) + } + fn show_group_share_success( lines: [TString<'static>; 4], ) -> Result { @@ -475,11 +503,70 @@ impl UIFeaturesFirmware for ModelMercuryFeatures { )) } + fn show_success( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error> { + // description used in the Footer + let description = if description.is_empty() { + None + } else { + Some(description) + }; + let content = StatusScreen::new_success(title); + let layout = LayoutObj::new(SwipeUpScreen::new( + Frame::left_aligned( + TR::words__title_success.into(), + SwipeContent::new(content).with_no_attach_anim(), + ) + .with_footer(TR::instructions__swipe_up.into(), description) + .with_result_icon(theme::ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT) + .with_swipe(Direction::Up, SwipeSettings::default()), + ))?; + Ok(layout) + } + fn show_wait_text(text: TString<'static>) -> Result { let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG)); Ok(layout) } + fn show_warning( + title: TString<'static>, + button: TString<'static>, + value: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + danger: bool, + ) -> Result, Error> { + let action = if button.is_empty() { + None + } else { + Some(button) + }; + let content = ParagraphVecShort::from_iter([ + Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description), + Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value), + ]) + .into_paragraphs(); + + let frame = Frame::left_aligned(title, SwipeContent::new(content)) + .with_footer(TR::instructions__swipe_up.into(), action) + .with_swipe(Direction::Up, SwipeSettings::default()); + + let frame_with_icon = if danger { + frame.with_danger_icon() + } else { + frame.with_warning_low_icon() + }; + + Ok(LayoutObj::new(SwipeUpScreen::new(frame_with_icon))?) + } + fn tutorial() -> Result { let flow = flow::show_tutorial::new_show_tutorial()?; Ok(flow) diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index b20240ac39..1d8afe985c 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -842,38 +842,6 @@ extern "C" fn new_confirm_fido(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_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let warning: TString = kwargs.get(Qstr::MP_QSTR_warning)?.try_into()?; - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - - let get_page = move |page_index| { - assert!(page_index == 0); - - let btn_layout = ButtonLayout::none_armed_none(button); - let btn_actions = ButtonActions::none_confirm_none(); - let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); - ops = ops.alignment(geometry::Alignment::Center); - if !warning.is_empty() { - ops = ops.text_bold_upper(warning); - if !description.is_empty() { - ops = ops.newline(); - } - } - if !description.is_empty() { - ops = ops.text_normal(description); - } - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted) - }; - let pages = FlowPages::new(get_page, 1); - let obj = LayoutObj::new(Flow::new(pages))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_show_passphrase() -> Obj { let block = move || { let text: TString = TR::passphrase__please_enter.into(); @@ -1164,15 +1132,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Show multiple texts, each on its own page.""" Qstr::MP_QSTR_multiple_pages_texts => obj_fn_kw!(0, new_multiple_pages_texts).as_obj(), - /// def show_warning( - /// *, - /// button: str, - /// warning: str, - /// description: str, - /// ) -> LayoutObj[UiResult]: - /// """Warning modal with middle button and centered text.""" - Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(), - /// def show_passphrase() -> LayoutObj[UiResult]: /// """Show passphrase on host dialog.""" Qstr::MP_QSTR_show_passphrase => obj_fn_0!(new_show_passphrase).as_obj(), 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 be578d65a4..8f1cb951c9 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 @@ -19,6 +19,7 @@ use crate::{ }, Component, ComponentExt, Empty, FormattedText, Label, LineBreaking, Paginate, Timeout, }, + geometry, layout::{ obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, util::RecoveryType, @@ -375,6 +376,16 @@ impl UIFeaturesFirmware for ModelTRFeatures { Ok(layout) } + fn show_error( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error> { + Err::, Error>(Error::ValueError(c"show error not supported")) + } + fn show_group_share_success( lines: [TString<'static>; 4], ) -> Result { @@ -499,11 +510,54 @@ impl UIFeaturesFirmware for ModelTRFeatures { )) } + fn show_success( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error> { + Err::, Error>(Error::ValueError(c"show success not supported")) + } + fn show_wait_text(text: TString<'static>) -> Result { let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG)); Ok(layout) } + fn show_warning( + title: TString<'static>, + button: TString<'static>, + value: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + danger: bool, + ) -> Result, Error> { + let get_page = move |page_index| { + assert!(page_index == 0); + + let btn_layout = ButtonLayout::none_armed_none(button); + let btn_actions = ButtonActions::none_confirm_none(); + let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); + ops = ops.alignment(geometry::Alignment::Center); + if !value.is_empty() { + ops = ops.text_bold_upper(value); + if !description.is_empty() { + ops = ops.newline(); + } + } + if !description.is_empty() { + ops = ops.text_normal(description); + } + let formatted = FormattedText::new(ops).vertically_centered(); + Page::new(btn_layout, btn_actions, formatted) + }; + let pages = FlowPages::new(get_page, 1); + let obj = LayoutObj::new(Flow::new(pages))?; + Ok(obj) + } + fn tutorial() -> Result { const PAGE_COUNT: usize = 7; diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 421e0251a7..d8f084f359 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -702,89 +702,6 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -fn new_show_modal( - kwargs: &Map, - icon: BlendedImage, - button_style: ButtonStyleSheet, -) -> Result { - let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; - let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; - let button: TString = kwargs.get_or(Qstr::MP_QSTR_button, TR::buttons__continue.into())?; - let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?; - let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; - - let no_buttons = button.is_empty(); - let obj = if no_buttons && time_ms == 0 { - // No buttons and no timer, used when we only want to draw the dialog once and - // then throw away the layout object. - LayoutObj::new( - IconDialog::new(icon, title, Empty) - .with_value(value) - .with_description(description), - )? - .into() - } else if no_buttons && time_ms > 0 { - // Timeout, no buttons. - LayoutObj::new( - IconDialog::new( - icon, - title, - Timeout::new(time_ms).map(|_| Some(CancelConfirmMsg::Confirmed)), - ) - .with_value(value) - .with_description(description), - )? - .into() - } else if allow_cancel { - // Two buttons. - LayoutObj::new( - IconDialog::new( - icon, - title, - Button::cancel_confirm( - Button::with_icon(theme::ICON_CANCEL), - Button::with_text(button).styled(button_style), - false, - ), - ) - .with_value(value) - .with_description(description), - )? - .into() - } else { - // Single button. - LayoutObj::new( - IconDialog::new( - icon, - title, - theme::button_bar(Button::with_text(button).styled(button_style).map(|msg| { - (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) - })), - ) - .with_value(value) - .with_description(description), - )? - .into() - }; - - Ok(obj) -} - -extern "C" fn new_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let icon = BlendedImage::new( - theme::IMAGE_BG_CIRCLE, - theme::IMAGE_FG_ERROR, - theme::ERROR_COLOR, - theme::FG, - theme::BG, - ); - new_show_modal(kwargs, icon, theme::button_default()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_confirm_fido(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()?; @@ -816,34 +733,6 @@ extern "C" fn new_confirm_fido(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_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let icon = BlendedImage::new( - theme::IMAGE_BG_OCTAGON, - theme::IMAGE_FG_WARN, - theme::WARN_COLOR, - theme::FG, - theme::BG, - ); - new_show_modal(kwargs, icon, theme::button_reset()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_success(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let icon = BlendedImage::new( - theme::IMAGE_BG_CIRCLE, - theme::IMAGE_FG_SUCCESS, - theme::SUCCESS_COLOR, - theme::FG, - theme::BG, - ); - new_show_modal(kwargs, icon, theme::button_confirm()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: Option = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?; @@ -1144,41 +1033,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """ Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), - /// def show_error( - /// *, - /// title: str, - /// button: str = "CONTINUE", - /// description: str = "", - /// allow_cancel: bool = False, - /// time_ms: int = 0, - /// ) -> LayoutObj[UiResult]: - /// """Error modal. No buttons shown when `button` is empty string.""" - Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(), - - /// def show_warning( - /// *, - /// title: str, - /// button: str = "CONTINUE", - /// value: str = "", - /// description: str = "", - /// allow_cancel: bool = False, - /// time_ms: int = 0, - /// danger: bool = False, # unused on TT - /// ) -> LayoutObj[UiResult]: - /// """Warning modal. No buttons shown when `button` is empty string.""" - Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(), - - /// def show_success( - /// *, - /// title: str, - /// button: str = "CONTINUE", - /// description: str = "", - /// allow_cancel: bool = False, - /// time_ms: int = 0, - /// ) -> LayoutObj[UiResult]: - /// """Success modal. No buttons shown when `button` is empty string.""" - Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(), - /// def show_simple( /// *, /// title: str | None, 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 916919967c..50ee840977 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 @@ -406,6 +406,32 @@ impl UIFeaturesFirmware for ModelTTFeatures { Ok(layout) } + fn show_error( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error> { + let icon = BlendedImage::new( + theme::IMAGE_BG_CIRCLE, + theme::IMAGE_FG_ERROR, + theme::ERROR_COLOR, + theme::FG, + theme::BG, + ); + new_show_modal( + title, + TString::empty(), + description, + button, + allow_cancel, + time_ms, + icon, + theme::button_default(), + ) + } + fn show_group_share_success( lines: [TString<'static>; 4], ) -> Result { @@ -447,7 +473,7 @@ impl UIFeaturesFirmware for ModelTTFeatures { theme::FG, theme::BG, ); - let obj = new_show_modal( + new_show_modal( title, TString::empty(), description, @@ -456,8 +482,7 @@ impl UIFeaturesFirmware for ModelTTFeatures { time_ms, icon, theme::button_info(), - )?; - Ok(obj) + ) } fn show_lockscreen( @@ -562,11 +587,65 @@ impl UIFeaturesFirmware for ModelTTFeatures { Ok(layout) } + fn show_success( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error> { + let icon = BlendedImage::new( + theme::IMAGE_BG_CIRCLE, + theme::IMAGE_FG_SUCCESS, + theme::SUCCESS_COLOR, + theme::FG, + theme::BG, + ); + new_show_modal( + title, + TString::empty(), + description, + button, + allow_cancel, + time_ms, + icon, + theme::button_confirm(), + ) + } + fn show_wait_text(text: TString<'static>) -> Result { let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG)); Ok(layout) } + fn show_warning( + title: TString<'static>, + button: TString<'static>, + value: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + danger: bool, + ) -> Result, Error> { + let icon = BlendedImage::new( + theme::IMAGE_BG_OCTAGON, + theme::IMAGE_FG_WARN, + theme::WARN_COLOR, + theme::FG, + theme::BG, + ); + new_show_modal( + title, + value, + description, + button, + allow_cancel, + 0, + icon, + theme::button_reset(), + ) + } + fn tutorial() -> Result { Err::, Error>(Error::ValueError( c"tutorial not supported", diff --git a/core/embed/rust/src/ui/ui_features_fw.rs b/core/embed/rust/src/ui/ui_features_fw.rs index 895dd9f582..3c317b49ab 100644 --- a/core/embed/rust/src/ui/ui_features_fw.rs +++ b/core/embed/rust/src/ui/ui_features_fw.rs @@ -1,4 +1,9 @@ -use crate::{error::Error, io::BinaryData, micropython::{gc::Gc, obj::Obj}, strutil::TString}; +use crate::{ + error::Error, + io::BinaryData, + micropython::{gc::Gc, obj::Obj}, + strutil::TString, +}; use super::layout::{ obj::{LayoutMaybeTrace, LayoutObj}, @@ -103,6 +108,14 @@ pub trait UIFeaturesFirmware { items: [TString<'static>; 3], ) -> Result; + fn show_error( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + fn show_group_share_success( lines: [TString<'static>; 4], ) -> Result; @@ -146,7 +159,25 @@ pub trait UIFeaturesFirmware { pages_iterable: Obj, // TODO: replace Obj ) -> Result; + fn show_success( + title: TString<'static>, + button: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + fn show_wait_text(text: TString<'static>) -> Result; + fn show_warning( + title: TString<'static>, + button: TString<'static>, + value: TString<'static>, + description: TString<'static>, + allow_cancel: bool, + time_ms: u32, + danger: bool, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + fn tutorial() -> Result; } diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 720ed3b904..5cdd8cb406 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -121,44 +121,6 @@ def confirm_fido( """ -# rust/src/ui/model_mercury/layout.rs -def show_error( - *, - title: str, - button: str = "CONTINUE", - description: str = "", - allow_cancel: bool = False, - time_ms: int = 0, -) -> LayoutObj[UiResult]: - """Error modal. No buttons shown when `button` is empty string.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_warning( - *, - title: str, - button: str = "CONTINUE", - value: str = "", - description: str = "", - allow_cancel: bool = False, - time_ms: int = 0, - danger: bool = False, -) -> LayoutObj[UiResult]: - """Warning modal. No buttons shown when `button` is empty string.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_success( - *, - title: str, - button: str = "CONTINUE", - description: str = "", - allow_cancel: bool = False, - time_ms: int = 0, -) -> LayoutObj[UiResult]: - """Success screen. Description is used in the footer.""" - - # rust/src/ui/model_mercury/layout.rs def show_simple( *, @@ -450,16 +412,6 @@ def multiple_pages_texts( """Show multiple texts, each on its own page.""" -# rust/src/ui/model_tr/layout.rs -def show_warning( - *, - button: str, - warning: str, - description: str, -) -> LayoutObj[UiResult]: - """Warning modal with middle button and centered text.""" - - # rust/src/ui/model_tr/layout.rs def show_passphrase() -> LayoutObj[UiResult]: """Show passphrase on host dialog.""" @@ -635,44 +587,6 @@ def confirm_fido( """ -# rust/src/ui/model_tt/layout.rs -def show_error( - *, - title: str, - button: str = "CONTINUE", - description: str = "", - allow_cancel: bool = False, - time_ms: int = 0, -) -> LayoutObj[UiResult]: - """Error modal. No buttons shown when `button` is empty string.""" - - -# rust/src/ui/model_tt/layout.rs -def show_warning( - *, - title: str, - button: str = "CONTINUE", - value: str = "", - description: str = "", - allow_cancel: bool = False, - time_ms: int = 0, - danger: bool = False, # unused on TT -) -> LayoutObj[UiResult]: - """Warning modal. No buttons shown when `button` is empty string.""" - - -# rust/src/ui/model_tt/layout.rs -def show_success( - *, - title: str, - button: str = "CONTINUE", - description: str = "", - allow_cancel: bool = False, - time_ms: int = 0, -) -> LayoutObj[UiResult]: - """Success modal. No buttons shown when `button` is empty string.""" - - # rust/src/ui/model_tt/layout.rs def show_simple( *, diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index 38af866829..49c1135790 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -244,6 +244,18 @@ def show_checklist( mark next to them. Limited to 3 items.""" +# rust/src/ui/api/firmware_upy.rs +def show_error( + *, + title: str, + button: str, + description: str = "", + allow_cancel: bool = True, + time_ms: int = 0, +) -> LayoutObj[UiResult]: + """Error modal. No buttons shown when `button` is empty string.""" + + # rust/src/ui/api/firmware_upy.rs def show_group_share_success( *, @@ -323,11 +335,37 @@ def show_remaining_shares( """Shows SLIP39 state after info button is pressed on `confirm_recovery`.""" +# rust/src/ui/api/firmware_upy.rs +def show_success( + *, + title: str, + button: str, + description: str = "", + allow_cancel: bool = True, + time_ms: int = 0, +) -> LayoutObj[UiResult]: + """Success modal. No buttons shown when `button` is empty string.""" + + # rust/src/ui/api/firmware_upy.rs def show_wait_text(message: str, /) -> LayoutObj[None]: """Show single-line text in the middle of the screen.""" +# rust/src/ui/api/firmware_upy.rs +def show_warning( + *, + title: str, + button: str, + value: str = "", + description: str = "", + allow_cancel: bool = True, + time_ms: int = 0, + danger: bool = False, # unused on TT +) -> LayoutObj[UiResult]: + """Warning modal. TT: No buttons shown when `button` is empty string. TR: middle button and centered text.""" + + # rust/src/ui/api/firmware_upy.rs def tutorial() -> LayoutObj[UiResult]: """Show user how to interact with the device.""" diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index e5e07311f4..00ea0f8ee5 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -97,7 +97,7 @@ def confirm_reset_device(recovery: bool = False) -> Awaitable[None]: async def show_wallet_created_success() -> None: await interact( - trezorui2.show_success(title=TR.backup__new_wallet_created, description=""), + trezorui_api.show_success(title=TR.backup__new_wallet_created, button=""), "backup_device", ButtonRequestType.ResetDevice, ) @@ -286,7 +286,7 @@ async def show_error_and_raise( ) -> NoReturn: button = button or TR.buttons__try_again # def_arg await interact( - trezorui2.show_error( + trezorui_api.show_error( title=subheader or "", description=content, button=button, @@ -308,7 +308,7 @@ def show_warning( ) -> Awaitable[None]: button = button or TR.buttons__continue # def_arg return raise_if_not_confirmed( - trezorui2.show_warning( + trezorui_api.show_warning( title=TR.words__important, value=content, button=subheader or TR.words__continue_anyway_question, @@ -326,8 +326,9 @@ def show_success( button: str | None = None, ) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.show_success( + trezorui_api.show_success( title=content, + button="", description=subheader if subheader else "", ), br_name, @@ -1073,7 +1074,7 @@ def error_popup( if subtitle: title += f"\n{subtitle}" - return trezorui2.show_error( + return trezorui_api.show_error( title=title, description=description.format(description_param), button=button, diff --git a/core/src/trezor/ui/layouts/mercury/recovery.py b/core/src/trezor/ui/layouts/mercury/recovery.py index 2f009c8932..775b6b3c68 100644 --- a/core/src/trezor/ui/layouts/mercury/recovery.py +++ b/core/src/trezor/ui/layouts/mercury/recovery.py @@ -135,7 +135,7 @@ async def show_recovery_warning( ) -> None: button = button or TR.buttons__try_again # def_arg await raise_if_not_confirmed( - trezorui2.show_warning( + trezorui_api.show_warning( title=content or TR.words__warning, value=subheader or "", button=button, diff --git a/core/src/trezor/ui/layouts/mercury/reset.py b/core/src/trezor/ui/layouts/mercury/reset.py index 47cfbd36c4..1c95b04030 100644 --- a/core/src/trezor/ui/layouts/mercury/reset.py +++ b/core/src/trezor/ui/layouts/mercury/reset.py @@ -301,7 +301,7 @@ async def show_intro_backup(single_share: bool, num_of_words: int | None) -> Non def show_warning_backup() -> Awaitable[ui.UiResult]: return interact( - trezorui2.show_warning( + trezorui_api.show_warning( title=TR.words__important, value=TR.reset__never_make_digital_copy, button="", @@ -328,7 +328,7 @@ def show_reset_warning( br_code: ButtonRequestType = ButtonRequestType.Warning, ) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.show_warning( + trezorui_api.show_warning( title=subheader or "", description=content, value="", diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index c5cb2f1767..bc4802fabd 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -393,9 +393,10 @@ def show_warning( content = content + "\n" return interact( - trezorui2.show_warning( # type: ignore [Argument missing for parameter "title"] + trezorui_api.show_warning( # type: ignore [Argument missing for parameter "title"] + title="", button=button, - warning=content, # type: ignore [No parameter named "warning"] + value=content, description=subheader or "", ), br_name, diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index 84fb0451be..78842b520e 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -136,7 +136,7 @@ def confirm_path_warning(path: str, path_type: str | None = None) -> Awaitable[N else f"{TR.words__unknown} {path_type.lower()}." ) return raise_if_not_confirmed( - trezorui2.show_warning( + trezorui_api.show_warning( title=title, value=path, description=TR.words__continue_anyway_question, @@ -323,7 +323,7 @@ async def show_error_and_raise( ) -> NoReturn: button = button or TR.buttons__try_again # def_arg await interact( - trezorui2.show_error( + trezorui_api.show_error( title=subheader or "", description=content, button=button, @@ -347,7 +347,7 @@ def show_warning( ) -> Awaitable[None]: button = button or TR.buttons__continue # def_arg return raise_if_not_confirmed( - trezorui2.show_warning( + trezorui_api.show_warning( title=content, description=subheader or "", button=button, @@ -365,7 +365,7 @@ def show_success( ) -> Awaitable[None]: button = button or TR.buttons__continue # def_arg return raise_if_not_confirmed( - trezorui2.show_success( + trezorui_api.show_success( title=content, description=subheader or "", button=button, @@ -1121,7 +1121,7 @@ def error_popup( if subtitle: title += f"\n{subtitle}" - layout = trezorui2.show_error( + layout = trezorui_api.show_error( title=title, description=description.format(description_param), button=button, diff --git a/core/src/trezor/ui/layouts/tt/recovery.py b/core/src/trezor/ui/layouts/tt/recovery.py index e3b0f0e58e..6a9410599d 100644 --- a/core/src/trezor/ui/layouts/tt/recovery.py +++ b/core/src/trezor/ui/layouts/tt/recovery.py @@ -185,7 +185,7 @@ def show_recovery_warning( button = button or TR.buttons__try_again # def_arg return interact( - trezorui2.show_warning( + trezorui_api.show_warning( title=content, description=subheader or "", button=button, diff --git a/core/src/trezor/ui/layouts/tt/reset.py b/core/src/trezor/ui/layouts/tt/reset.py index 9ee1c66140..231aad0750 100644 --- a/core/src/trezor/ui/layouts/tt/reset.py +++ b/core/src/trezor/ui/layouts/tt/reset.py @@ -355,7 +355,7 @@ def show_reset_warning( ) -> Awaitable[trezorui_api.UiResult]: button = button or TR.buttons__try_again # def_arg return interact( - trezorui2.show_warning( + trezorui_api.show_warning( title=subheader or "", description=content, button=button,