From dd9ac038d0b7c61ba9b5d2adb794616b42159728 Mon Sep 17 00:00:00 2001 From: obrusvit Date: Tue, 22 Oct 2024 22:24:40 +0200 Subject: [PATCH] refactor(core): port functions to FirmwareUI - keyboards - standalone functions - disable_animations - check_homescreen_format - confirm_action - selectors - confirm_firmware_update - show_homescreen/lockscreen - confirm_homescreen - confirm_homescreen of mercury changed according to Figma - set_brightness - show_wait_text - show_progress - request_number - show_checklist - show_mismatch - confirm_reset - tutorial - confirm_coinjoin - confirm_modify_output/fee - show_group_share_success - show_remaining_shares - show_success/warning/error/danger - show_simple - continue_recovery - confirm_recovery of model_t and model_r merged with flow_continue_recovery of mercury into a continue_recovery_homepage trait function, parameters renamed to be more descriptive - show_share_words moved and refactored - model_t version was moved from using plain Paragraph to a dedicated component `ShareWords` so that it's consistent with other models. This allowed to move formatting to Rust and allowed the trait function to have `words` parameter of type `Vec` - model_r ShareWords::render slightly refactored to be consistent with the new model_t version - mercury uses a unique version. The reason is that mercury SwipeFlow contains also the initial screen with instructions and prompt screen at the end. - confirm_with_info - show_info_with_cancel - confirm_blob - confirm_value - confirm_properties - confirm_more - confirm_address - prompt_backub - confirm_emphasized - show_address_details - confirm_summary - TR removed: - confirm_output_address and confirm_output_amount replaced with confirm_blob. UI diff is minimal. - confirm_joint_total, replaced a TR-specific function with a generic `confirm_properties` with no UI changes - confirm_multiple_pages_texts - the function is TR specific and is used only in confirm_set_new_pin, it would be better to use something different - remaining mercury flows with no counterpart - confirm_blob_intro - flow_confirm_set_new_pin - flow_confirm_output - flow_get_address [no changelog] --- core/embed/rust/librust_qstr.h | 28 +- core/embed/rust/src/ui/api/firmware_upy.rs | 1556 ++++++++++++++- .../src/ui/model_mercury/flow/confirm_fido.rs | 225 +-- .../model_mercury/flow/confirm_homescreen.rs | 108 + ...overy.rs => continue_recovery_homepage.rs} | 10 +- .../src/ui/model_mercury/flow/get_address.rs | 3 +- .../rust/src/ui/model_mercury/flow/mod.rs | 6 +- .../ui/model_mercury/flow/request_number.rs | 5 +- .../ui/model_mercury/flow/show_share_words.rs | 8 +- .../embed/rust/src/ui/model_mercury/layout.rs | 1734 ---------------- .../src/ui/model_mercury/ui_features_fw.rs | 1117 ++++++++++- .../src/ui/model_tr/component/share_words.rs | 39 +- core/embed/rust/src/ui/model_tr/layout.rs | 1735 +--------------- core/embed/rust/src/ui/model_tr/mod.rs | 2 + .../rust/src/ui/model_tr/ui_features_fw.rs | 1315 +++++++++++- .../rust/src/ui/model_tt/component/mod.rs | 4 + .../ui/model_tt/component/set_brightness.rs | 3 +- .../src/ui/model_tt/component/share_words.rs | 114 ++ core/embed/rust/src/ui/model_tt/layout.rs | 1768 +---------------- core/embed/rust/src/ui/model_tt/mod.rs | 2 + .../rust/src/ui/model_tt/ui_features_fw.rs | 1257 +++++++++++- core/embed/rust/src/ui/ui_features_fw.rs | 378 +++- core/mocks/generated/trezorui2.pyi | 1441 -------------- core/mocks/generated/trezorui_api.pyi | 587 ++++++ core/src/apps/management/apply_settings.py | 4 +- .../management/recovery_device/__init__.py | 2 +- .../apps/management/reset_device/__init__.py | 2 +- core/src/trezor/ui/__init__.py | 4 +- core/src/trezor/ui/layouts/homescreen.py | 7 +- .../src/trezor/ui/layouts/mercury/__init__.py | 97 +- core/src/trezor/ui/layouts/mercury/fido.py | 5 +- .../src/trezor/ui/layouts/mercury/recovery.py | 24 +- core/src/trezor/ui/layouts/mercury/reset.py | 42 +- core/src/trezor/ui/layouts/progress.py | 6 +- core/src/trezor/ui/layouts/tr/__init__.py | 121 +- core/src/trezor/ui/layouts/tr/fido.py | 8 +- core/src/trezor/ui/layouts/tr/recovery.py | 16 +- core/src/trezor/ui/layouts/tr/reset.py | 12 +- core/src/trezor/ui/layouts/tt/__init__.py | 102 +- core/src/trezor/ui/layouts/tt/fido.py | 5 +- core/src/trezor/ui/layouts/tt/recovery.py | 45 +- core/src/trezor/ui/layouts/tt/reset.py | 46 +- 42 files changed, 6838 insertions(+), 7155 deletions(-) create mode 100644 core/embed/rust/src/ui/model_mercury/flow/confirm_homescreen.rs rename core/embed/rust/src/ui/model_mercury/flow/{continue_recovery.rs => continue_recovery_homepage.rs} (98%) create mode 100644 core/embed/rust/src/ui/model_tt/component/share_words.rs diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index a5cf74e9b7..6a83449f47 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -63,7 +63,6 @@ static void _librust_qstrs(void) { MP_QSTR_address_details__derivation_path_colon; MP_QSTR_address_details__title_receive_address; MP_QSTR_address_details__title_receiving_to; - MP_QSTR_address_label; MP_QSTR_address_qr; MP_QSTR_address_title; MP_QSTR_allow_cancel; @@ -71,7 +70,6 @@ static void _librust_qstrs(void) { MP_QSTR_amount_change; MP_QSTR_amount_label; MP_QSTR_amount_new; - MP_QSTR_amount_title; MP_QSTR_app_name; MP_QSTR_area_bytesize; MP_QSTR_attach_timer_fn; @@ -187,7 +185,6 @@ static void _librust_qstrs(void) { MP_QSTR_coinjoin_authorized; MP_QSTR_confirm_action; MP_QSTR_confirm_address; - MP_QSTR_confirm_backup; MP_QSTR_confirm_blob; MP_QSTR_confirm_blob_intro; MP_QSTR_confirm_coinjoin; @@ -195,14 +192,10 @@ static void _librust_qstrs(void) { MP_QSTR_confirm_fido; MP_QSTR_confirm_firmware_update; MP_QSTR_confirm_homescreen; - MP_QSTR_confirm_joint_total; MP_QSTR_confirm_modify_fee; MP_QSTR_confirm_modify_output; MP_QSTR_confirm_more; - MP_QSTR_confirm_output_address; - MP_QSTR_confirm_output_amount; MP_QSTR_confirm_properties; - MP_QSTR_confirm_recovery; MP_QSTR_confirm_reset_device; MP_QSTR_confirm_summary; MP_QSTR_confirm_total__fee_rate; @@ -212,6 +205,7 @@ static void _librust_qstrs(void) { MP_QSTR_confirm_total__title_sending_from; MP_QSTR_confirm_value; MP_QSTR_confirm_with_info; + MP_QSTR_continue_recovery_homepage; MP_QSTR_count; MP_QSTR_current; MP_QSTR_danger; @@ -246,16 +240,9 @@ static void _librust_qstrs(void) { MP_QSTR_fingerprint; MP_QSTR_firmware_update__title; MP_QSTR_firmware_update__title_fingerprint; - MP_QSTR_first_screen; MP_QSTR_flow_confirm_output; - MP_QSTR_flow_confirm_reset; MP_QSTR_flow_confirm_set_new_pin; - MP_QSTR_flow_continue_recovery; MP_QSTR_flow_get_address; - MP_QSTR_flow_prompt_backup; - MP_QSTR_flow_request_number; - MP_QSTR_flow_request_passphrase; - MP_QSTR_flow_show_share_words; MP_QSTR_get_language; MP_QSTR_get_transition_out; MP_QSTR_haptic_feedback__disable; @@ -292,6 +279,7 @@ static void _librust_qstrs(void) { MP_QSTR_inputs__return; MP_QSTR_inputs__show; MP_QSTR_inputs__space; + MP_QSTR_instructions; MP_QSTR_instructions__continue_holding; MP_QSTR_instructions__continue_in_app; MP_QSTR_instructions__enter_next_share; @@ -347,6 +335,7 @@ static void _librust_qstrs(void) { MP_QSTR_modify_fee__no_change; MP_QSTR_modify_fee__title; MP_QSTR_modify_fee__transaction_fee; + MP_QSTR_more_info_callback; MP_QSTR_multiple_pages_texts; MP_QSTR_notification; MP_QSTR_notification_level; @@ -422,6 +411,7 @@ static void _librust_qstrs(void) { MP_QSTR_progress__x_seconds_left_template; MP_QSTR_progress_event; MP_QSTR_prompt; + MP_QSTR_prompt_backup; MP_QSTR_prompt_screen; MP_QSTR_prompt_title; MP_QSTR_qr_title; @@ -481,6 +471,7 @@ static void _librust_qstrs(void) { MP_QSTR_recovery__x_of_y_entered_template; MP_QSTR_recovery__you_have_entered; MP_QSTR_recovery_type; + MP_QSTR_remaining_shares; MP_QSTR_request_bip39; MP_QSTR_request_complete_repaint; MP_QSTR_request_number; @@ -639,7 +630,6 @@ static void _librust_qstrs(void) { MP_QSTR_set_brightness; MP_QSTR_setting__adjust; MP_QSTR_setting__apply; - MP_QSTR_share_words; MP_QSTR_share_words__words_in_order; MP_QSTR_share_words__wrote_down_all; MP_QSTR_show_address_details; @@ -653,11 +643,11 @@ static void _librust_qstrs(void) { MP_QSTR_show_instructions; MP_QSTR_show_lockscreen; MP_QSTR_show_mismatch; - MP_QSTR_show_passphrase; MP_QSTR_show_progress; MP_QSTR_show_progress_coinjoin; MP_QSTR_show_remaining_shares; MP_QSTR_show_share_words; + MP_QSTR_show_share_words_mercury; MP_QSTR_show_simple; MP_QSTR_show_success; MP_QSTR_show_wait_text; @@ -669,7 +659,6 @@ static void _librust_qstrs(void) { MP_QSTR_sign_message__message_size; MP_QSTR_sign_message__verify_address; MP_QSTR_skip_first_paint; - MP_QSTR_spending_amount; MP_QSTR_storage_msg__processing; MP_QSTR_storage_msg__starting; MP_QSTR_storage_msg__verifying_pin; @@ -683,12 +672,12 @@ static void _librust_qstrs(void) { MP_QSTR_summary_title; MP_QSTR_text; MP_QSTR_text_confirm; - MP_QSTR_text_info; + MP_QSTR_text_footer; MP_QSTR_text_mono; MP_QSTR_time_ms; MP_QSTR_timer; MP_QSTR_title; - MP_QSTR_total_amount; + MP_QSTR_title_success; MP_QSTR_total_fee_new; MP_QSTR_total_len; MP_QSTR_touch_event; @@ -738,7 +727,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 86e49a6676..719b691768 100644 --- a/core/embed/rust/src/ui/api/firmware_upy.rs +++ b/core/embed/rust/src/ui/api/firmware_upy.rs @@ -1,6 +1,10 @@ use crate::{ + io::BinaryData, micropython::{ - macros::{obj_fn_kw, obj_module}, + gc::Gc, + iter::IterBuf, + list::List, + macros::{obj_fn_0, obj_fn_1, obj_fn_kw, obj_module}, map::Map, module::Module, obj::Obj, @@ -8,22 +12,816 @@ use crate::{ util, }, strutil::TString, + trezorhal::model, ui::{ backlight::BACKLIGHT_LEVELS_OBJ, + component::Empty, layout::{ base::LAYOUT_STATE, - obj::{LayoutObj, ATTACH_TYPE_OBJ}, + obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ}, result::{CANCELLED, CONFIRMED, INFO}, + util::{upy_disable_animation, RecoveryType}, }, ui_features::ModelUI, ui_features_fw::UIFeaturesFirmware, }, }; +use heapless::Vec; + +/// Dummy implementation so that we can use `Empty` in a return type of unimplemented trait +/// function +impl ComponentMsgObj for Empty { + fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { + Ok(Obj::const_none()) + } +} // free-standing functions exported to MicroPython mirror `trait // UIFeaturesFirmware` +// NOTE: `disable_animation` not a part of trait UiFeaturesFirmware -extern "C" fn show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { +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_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()?; + let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; + let description: Option = kwargs + .get(Qstr::MP_QSTR_description) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let extra: Option = kwargs + .get(Qstr::MP_QSTR_extra) + .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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + + let layout = ModelUI::confirm_address(title, data, description, extra, verb, chunkify)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + 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()?; + let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; + let description: Option = kwargs + .get(Qstr::MP_QSTR_description) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; + let extra: Option = kwargs + .get(Qstr::MP_QSTR_extra) + .unwrap_or_else(|_| Obj::const_none()) + .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 verb_info: Option = kwargs + .get(Qstr::MP_QSTR_verb_info) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let info: bool = kwargs.get_or(Qstr::MP_QSTR_info, false)?; + let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; + let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + let page_counter: bool = kwargs.get_or(Qstr::MP_QSTR_page_counter, false)?; + let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, false)?; + let cancel: bool = kwargs.get_or(Qstr::MP_QSTR_cancel, false)?; + + let layout_obj = ModelUI::confirm_blob( + title, + data, + description, + text_mono, + extra, + subtitle, + verb, + verb_cancel, + verb_info, + info, + hold, + chunkify, + page_counter, + prompt_screen, + cancel, + )?; + Ok(layout_obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_blob_intro(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 data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; + 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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + + let layout_obj = + ModelUI::confirm_blob_intro(title, data, subtitle, verb, verb_cancel, chunkify)?; + Ok(layout_obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let max_rounds: TString = kwargs.get(Qstr::MP_QSTR_max_rounds)?.try_into()?; + let max_feerate: TString = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?; + + let layout = ModelUI::confirm_coinjoin(max_rounds, max_feerate)?; + Ok(LayoutObj::new_root(layout)?.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()?; + let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + let verb: Option = kwargs + .get(Qstr::MP_QSTR_verb) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + + let layout = ModelUI::confirm_emphasized(title, items, verb)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + 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()?; + let app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; + let icon: Option = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?; + let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; + + let layout = ModelUI::confirm_fido(title, app_name, icon, accounts)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +// TODO: there was `no_mangle` attribute in TT, should we apply it? +extern "C" fn new_confirm_firmware_update( + n_args: usize, + args: *const Obj, + kwargs: *mut Map, +) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; + let fingerprint: TString = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?; + + let layout = ModelUI::confirm_firmware_update(description, fingerprint)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_homescreen(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 image: Obj = kwargs.get(Qstr::MP_QSTR_image)?; + + let jpeg: BinaryData = image.try_into()?; + + let layout = ModelUI::confirm_homescreen(title, jpeg)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_modify_fee(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 sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; + let user_fee_change: TString = kwargs.get(Qstr::MP_QSTR_user_fee_change)?.try_into()?; + let total_fee_new: TString = kwargs.get(Qstr::MP_QSTR_total_fee_new)?.try_into()?; + let fee_rate_amount: Option = kwargs + .get(Qstr::MP_QSTR_fee_rate_amount) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + + let layout = ModelUI::confirm_modify_fee( + title, + sign, + user_fee_change, + total_fee_new, + fee_rate_amount, + )?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; + let amount_change: TString = kwargs.get(Qstr::MP_QSTR_amount_change)?.try_into()?; + let amount_new: TString = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?; + + let layout = ModelUI::confirm_modify_output(sign, amount_change, amount_new)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_more(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 button_style_confirm: bool = + kwargs.get_or(Qstr::MP_QSTR_button_style_confirm, false)?; + let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + + let layout = ModelUI::confirm_more(title, button, button_style_confirm, items)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_properties(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 items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; + + let layout = ModelUI::confirm_properties(title, items, hold)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let recovery: bool = kwargs.get(Qstr::MP_QSTR_recovery)?.try_into()?; + + let layout = ModelUI::confirm_reset_device(recovery)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let amount: TString = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?; + let amount_label: TString = kwargs.get(Qstr::MP_QSTR_amount_label)?.try_into()?; + let fee: TString = kwargs.get(Qstr::MP_QSTR_fee)?.try_into()?; + let fee_label: TString = kwargs.get(Qstr::MP_QSTR_fee_label)?.try_into()?; + let title: Option = kwargs + .get(Qstr::MP_QSTR_title) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let account_items: Option = kwargs + .get(Qstr::MP_QSTR_account_items) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let extra_items: Option = kwargs + .get(Qstr::MP_QSTR_extra_items) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let extra_title: Option = kwargs + .get(Qstr::MP_QSTR_extra_title) + .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 layout = ModelUI::confirm_summary( + amount, + amount_label, + fee, + fee_label, + title, + account_items, + extra_items, + extra_title, + verb_cancel, + )?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_value(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 subtitle: Option = kwargs + .get(Qstr::MP_QSTR_subtitle) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let description: Option = kwargs + .get(Qstr::MP_QSTR_description) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let value: Obj = kwargs.get(Qstr::MP_QSTR_value)?; + let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false)?; + let verb: Option = kwargs + .get(Qstr::MP_QSTR_verb) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let verb_info: Option = kwargs + .get(Qstr::MP_QSTR_verb_info) + .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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; + + let layout_obj = ModelUI::confirm_value( + title, + value, + description, + subtitle, + verb, + verb_info, + verb_cancel, + info_button, + hold, + chunkify, + text_mono, + )?; + Ok(layout_obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_confirm_with_info(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 info_button: TString = kwargs.get(Qstr::MP_QSTR_info_button)?.try_into()?; + let verb_cancel: Option> = kwargs + .get(Qstr::MP_QSTR_verb_cancel) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + + let layout = ModelUI::confirm_with_info(title, button, info_button, verb_cancel, items)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_continue_recovery_homepage( + n_args: usize, + args: *const Obj, + kwargs: *mut Map, +) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let text: TString = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?; // #shares entered + let subtext: Option = kwargs.get(Qstr::MP_QSTR_subtext)?.try_into_option()?; // #shares remaining + let button: Option = kwargs + .get(Qstr::MP_QSTR_button) + .and_then(Obj::try_into_option) + .unwrap_or(None); + let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; + let show_instructions: bool = kwargs.get_or(Qstr::MP_QSTR_show_instructions, false)?; + let remaining_shares: Option = kwargs + .get(Qstr::MP_QSTR_remaining_shares)? + .try_into_option()?; // info about remaining shares + + let obj = ModelUI::continue_recovery_homepage( + text, + subtext, + button, + recovery_type, + show_instructions, + remaining_shares, + )?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_flow_confirm_output(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()?; + let subtitle: Option = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into_option()?; + let message: Obj = kwargs.get(Qstr::MP_QSTR_message)?; + let amount: Option = kwargs.get(Qstr::MP_QSTR_amount)?.try_into_option()?; + let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; + let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; + let account_path: Option = + kwargs.get(Qstr::MP_QSTR_account_path)?.try_into_option()?; + let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?; + let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?; + + let address: Option = kwargs.get(Qstr::MP_QSTR_address)?.try_into_option()?; + let address_title: Option = + kwargs.get(Qstr::MP_QSTR_address_title)?.try_into_option()?; + let summary_items: Option = + kwargs.get(Qstr::MP_QSTR_summary_items)?.try_into_option()?; + let fee_items: Option = kwargs.get(Qstr::MP_QSTR_fee_items)?.try_into_option()?; + let summary_title: Option = + kwargs.get(Qstr::MP_QSTR_summary_title)?.try_into_option()?; + let summary_br_code: Option = kwargs + .get(Qstr::MP_QSTR_summary_br_code)? + .try_into_option()?; + let summary_br_name: Option = kwargs + .get(Qstr::MP_QSTR_summary_br_name)? + .try_into_option()?; + let cancel_text: Option = + kwargs.get(Qstr::MP_QSTR_cancel_text)?.try_into_option()?; + + let layout = ModelUI::flow_confirm_output( + title, + subtitle, + message, + amount, + chunkify, + text_mono, + account, + account_path, + br_code, + br_name, + address, + address_title, + summary_items, + fee_items, + summary_title, + summary_br_code, + summary_br_name, + cancel_text, + )?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_flow_confirm_set_new_pin( + 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 layout = ModelUI::flow_confirm_set_new_pin(title, description)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_flow_get_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()?; + let description: Option = + kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; + let extra: Option = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; + let address: Obj = kwargs.get(Qstr::MP_QSTR_address)?; + let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + let address_qr: TString = kwargs.get(Qstr::MP_QSTR_address_qr)?.try_into()?; + let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?; + let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; + let path: Option = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?; + let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?; + let title_success: TString = kwargs.get(Qstr::MP_QSTR_title_success)?.try_into()?; + let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?; + let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?; + + let layout = ModelUI::flow_get_address( + address, + title, + description, + extra, + chunkify, + address_qr, + case_sensitive, + account, + path, + xpubs, + title_success, + br_code, + br_name, + )?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_multiple_pages_texts(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 verb: TString = kwargs.get(Qstr::MP_QSTR_verb)?.try_into()?; + let items: Gc = kwargs.get(Qstr::MP_QSTR_items)?.try_into()?; + + let layout = ModelUI::multiple_pages_texts(title, verb, items)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_prompt_backup() -> Obj { + let block = || { + let layout = ModelUI::prompt_backup()?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_or_raise(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()?; + let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; + let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; + + let layout = ModelUI::request_bip39(prompt, prefill_word, can_go_back)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_request_slip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = |_args: &[Obj], kwargs: &Map| { + let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; + let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; + let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; + + let layout = ModelUI::request_slip39(prompt, prefill_word, can_go_back)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_request_number(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 count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?; + let min_count: u32 = kwargs.get(Qstr::MP_QSTR_min_count)?.try_into()?; + let max_count: u32 = kwargs.get(Qstr::MP_QSTR_max_count)?.try_into()?; + let description: Option = kwargs + .get(Qstr::MP_QSTR_description) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let more_info_callback: Option = kwargs + .get(Qstr::MP_QSTR_more_info_callback) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + + let more_info_cb = more_info_callback.and_then(|callback| { + let cb = move |n: u32| { + let text = callback.call_with_n_args(&[n.try_into().unwrap()]).unwrap(); + TString::try_from(text).unwrap() + }; + Some(cb) + }); + + let layout = ModelUI::request_number( + title, + count, + min_count, + max_count, + description, + more_info_cb, + )?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_request_pin(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()?; + let subprompt: TString = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?; + let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?; + let warning: bool = kwargs.get_or(Qstr::MP_QSTR_wrong_pin, false)?; + + let layout = ModelUI::request_pin(prompt, subprompt, allow_cancel, warning)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_request_passphrase(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()?; + let max_len: u32 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?; + + let layout = ModelUI::request_passphrase(prompt, max_len)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_select_word(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 words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?; + let words: [TString<'static>; 3] = util::iter_into_array(words_iterable)?; + + let layout = ModelUI::select_word(title, description, words)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; + + let layout = ModelUI::select_word_count(recovery_type)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_set_brightness(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let current: Option = kwargs.get(Qstr::MP_QSTR_current)?.try_into_option()?; + + let layout = ModelUI::set_brightness(current)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_address_details(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let qr_title: TString<'static> = kwargs.get(Qstr::MP_QSTR_qr_title)?.try_into()?; + let details_title: TString = kwargs.get(Qstr::MP_QSTR_details_title)?.try_into()?; + let address: TString = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?; + let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?; + let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; + let path: Option = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?; + let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?; + + let layout = ModelUI::show_address_details( + qr_title, + address, + case_sensitive, + details_title, + account, + path, + xpubs, + )?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_checklist(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 active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; + let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + + let items: [TString<'static>; 3] = util::iter_into_array(items)?; + + let layout = ModelUI::show_checklist(title, button, active, items)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_danger(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 value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; + let verb_cancel: Option = kwargs + .get(Qstr::MP_QSTR_verb_cancel) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + + let layout = ModelUI::show_danger(title, description, value, verb_cancel)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + 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, + kwargs: *mut Map, +) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let lines_iterable: Obj = kwargs.get(Qstr::MP_QSTR_lines)?; + let lines: [TString; 4] = util::iter_into_array(lines_iterable)?; + + let layout = ModelUI::show_group_share_success(lines)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let label: TString<'static> = kwargs + .get(Qstr::MP_QSTR_label)? + .try_into_option()? + .unwrap_or_else(|| model::FULL_NAME.into()); + let notification: Option> = + kwargs.get(Qstr::MP_QSTR_notification)?.try_into_option()?; + let notification_level: u8 = kwargs.get_or(Qstr::MP_QSTR_notification_level, 0)?; + let hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?; + let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; + + let layout = ModelUI::show_homescreen(label, hold, notification, notification_level)?; + let layout_obj = LayoutObj::new_root(layout)?; + if skip_first_paint { + layout_obj.skip_first_paint(); + } + Ok(layout_obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_info(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()?; @@ -38,6 +836,218 @@ extern "C" fn show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Ob unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_show_info_with_cancel(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 items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + let horizontal: bool = kwargs.get_or(Qstr::MP_QSTR_horizontal, false)?; + let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + + let layout = ModelUI::show_info_with_cancel(title, items, horizontal, chunkify)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let label: TString<'static> = kwargs + .get(Qstr::MP_QSTR_label)? + .try_into_option()? + .unwrap_or_else(|| model::FULL_NAME.into()); + let bootscreen: bool = kwargs.get(Qstr::MP_QSTR_bootscreen)?.try_into()?; + let coinjoin_authorized: bool = kwargs.get_or(Qstr::MP_QSTR_coinjoin_authorized, false)?; + let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; + + let layout = ModelUI::show_lockscreen(label, bootscreen, coinjoin_authorized)?; + let layout_obj = LayoutObj::new_root(layout)?; + if skip_first_paint { + layout_obj.skip_first_paint(); + } + Ok(layout_obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_mismatch(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 layout = ModelUI::show_mismatch(title)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; + let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; + let title: Option = kwargs + .get(Qstr::MP_QSTR_title) + .and_then(Obj::try_into_option) + .unwrap_or(None); + + let layout = ModelUI::show_progress(description, indeterminate, title)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_progress_coinjoin(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 indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; + let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; + let skip_first_paint: bool = kwargs.get_or(Qstr::MP_QSTR_skip_first_paint, false)?; + + let obj = ModelUI::show_progress_coinjoin(title, indeterminate, time_ms, skip_first_paint)?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let pages_iterable: Obj = kwargs.get(Qstr::MP_QSTR_pages)?; + let layout = ModelUI::show_remaining_shares(pages_iterable)?; + Ok(LayoutObj::new_root(layout)?.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 words: Obj = kwargs.get(Qstr::MP_QSTR_words)?; + let title: Option = kwargs + .get(Qstr::MP_QSTR_title) + .and_then(Obj::try_into_option) + .unwrap_or(None); + + let words: Vec = util::iter_into_vec(words)?; + + let layout = ModelUI::show_share_words(words, title)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_share_words_mercury( + n_args: usize, + args: *const Obj, + kwargs: *mut Map, +) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let words: Obj = kwargs.get(Qstr::MP_QSTR_words)?; + let subtitle: Option = kwargs + .get(Qstr::MP_QSTR_subtitle) + .and_then(Obj::try_into_option) + .unwrap_or(None); + let instructions: Obj = kwargs.get(Qstr::MP_QSTR_instructions)?; + let text_footer: Option = kwargs + .get(Qstr::MP_QSTR_text_footer) + .and_then(Obj::try_into_option) + .unwrap_or(None); + let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?; + + let words: Vec = util::iter_into_vec(words)?; + + let layout = ModelUI::show_share_words_mercury( + words, + subtitle, + instructions, + text_footer, + text_confirm, + )?; + Ok(LayoutObj::new_root(layout)?.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 text: TString = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?; + let title: Option = kwargs + .get(Qstr::MP_QSTR_title) + .and_then(Obj::try_into_option) + .unwrap_or(None); + let button: Option = kwargs + .get(Qstr::MP_QSTR_button) + .and_then(Obj::try_into_option) + .unwrap_or(None); + + let obj = ModelUI::show_simple(text, title, button)?; + Ok(obj.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 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()?; + + let layout = ModelUI::show_wait_text(message)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + + 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()?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj { + let block = || { + let buffer = data.try_into()?; + Ok(ModelUI::check_homescreen_format(buffer, false).into()) + }; + + unsafe { util::try_or_raise(block) } +} + #[no_mangle] pub static mp_module_trezorui_api: Module = obj_module! { /// from trezor import utils @@ -134,6 +1144,430 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// INFO: UiResult Qstr::MP_QSTR_INFO => INFO.as_obj(), + /// def check_homescreen_format(data: bytes) -> bool: + /// """Check homescreen format and dimensions.""" + Qstr::MP_QSTR_check_homescreen_format => obj_fn_1!(upy_check_homescreen_format).as_obj(), + + /// def disable_animation(disable: bool) -> None: + /// """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 confirm_address( + /// *, + /// title: str, + /// data: str | bytes, + /// description: str | None, + /// extra: str | None, + /// verb: str | None = None, + /// chunkify: bool = False, + /// ) -> LayoutObj[UiResult]: + /// """Confirm address.""" + Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(), + + /// def confirm_blob( + /// *, + /// title: str, + /// data: str | bytes, + /// description: str | None, + /// text_mono: bool = True, + /// extra: str | None = None, + /// subtitle: str | None = None, + /// verb: str | None = None, + /// verb_cancel: str | None = None, + /// verb_info: str | None = None, + /// info: bool = True, + /// hold: bool = False, + /// chunkify: bool = False, + /// page_counter: bool = False, + /// prompt_screen: bool = False, + /// cancel: bool = False, + /// ) -> LayoutObj[UiResult]: + /// """Confirm byte sequence data.""" + Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), + + /// def confirm_blob_intro( + /// *, + /// title: str, + /// data: str | bytes, + /// subtitle: str | None = None, + /// verb: str | None = None, + /// verb_cancel: str | None = None, + /// chunkify: bool = False, + /// ) -> LayoutObj[UiResult]: + /// """Confirm byte sequence data by showing only the first page of the data + /// and instructing the user to access the menu in order to view all the data, + /// which can then be confirmed using confirm_blob.""" + Qstr::MP_QSTR_confirm_blob_intro => obj_fn_kw!(0, new_confirm_blob_intro).as_obj(), + + /// def confirm_coinjoin( + /// *, + /// max_rounds: str, + /// max_feerate: str, + /// ) -> LayoutObj[UiResult]: + /// """Confirm coinjoin authorization.""" + Qstr::MP_QSTR_confirm_coinjoin => obj_fn_kw!(0, new_confirm_coinjoin).as_obj(), + + /// def confirm_emphasized( + /// *, + /// title: str, + /// items: Iterable[str | tuple[bool, str]], + /// verb: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Confirm formatted text that has been pre-split in python. For tuples + /// the first component is a bool indicating whether this part is emphasized.""" + Qstr::MP_QSTR_confirm_emphasized => obj_fn_kw!(0, new_confirm_emphasized).as_obj(), + + /// def confirm_fido( + /// *, + /// title: str, + /// app_name: str, + /// icon_name: str | None, + /// accounts: list[str | None], + /// ) -> LayoutObj[int | UiResult]: + /// """FIDO confirmation. + /// + /// Returns page index in case of confirmation and CANCELLED otherwise. + /// """ + Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), + + /// def confirm_firmware_update( + /// *, + /// description: str, + /// fingerprint: str, + /// ) -> LayoutObj[UiResult]: + /// """Ask whether to update firmware, optionally show fingerprint.""" + Qstr::MP_QSTR_confirm_firmware_update => obj_fn_kw!(0, new_confirm_firmware_update).as_obj(), + + /// def confirm_homescreen( + /// *, + /// title: str, + /// image: bytes, + /// ) -> LayoutObj[UiResult]: + /// """Confirm homescreen.""" + Qstr::MP_QSTR_confirm_homescreen => obj_fn_kw!(0, new_confirm_homescreen).as_obj(), + + /// def confirm_modify_fee( + /// *, + /// title: str, + /// sign: int, + /// user_fee_change: str, + /// total_fee_new: str, + /// fee_rate_amount: str | None, + /// ) -> LayoutObj[UiResult]: + /// """Decrease or increase transaction fee.""" + Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(), + + /// def confirm_modify_output( + /// *, + /// sign: int, + /// amount_change: str, + /// amount_new: str, + /// ) -> LayoutObj[UiResult]: + /// """Decrease or increase output amount.""" + Qstr::MP_QSTR_confirm_modify_output => obj_fn_kw!(0, new_confirm_modify_output).as_obj(), + + /// def confirm_more( + /// *, + /// title: str, + /// button: str, + /// button_style_confirm: bool = False, + /// items: Iterable[tuple[int, str | bytes]], + /// ) -> LayoutObj[UiResult]: + /// """Confirm long content with the possibility to go back from any page. + /// Meant to be used with confirm_with_info on model TT and TR.""" + Qstr::MP_QSTR_confirm_more => obj_fn_kw!(0, new_confirm_more).as_obj(), + + /// def confirm_properties( + /// *, + /// title: str, + /// items: list[tuple[str | None, str | bytes | None, bool]], + /// hold: bool = False, + /// ) -> LayoutObj[UiResult]: + /// """Confirm list of key-value pairs. The third component in the tuple should be True if + /// the value is to be rendered as binary with monospace font, False otherwise.""" + Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(), + + /// def confirm_reset_device(recovery: bool) -> LayoutObj[UiResult]: + /// """Confirm TOS before creating wallet creation or wallet recovery.""" + Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, new_confirm_reset_device).as_obj(), + + /// def confirm_summary( + /// *, + /// amount: str, + /// amount_label: str, + /// fee: str, + /// fee_label: str, + /// title: str | None = None, + /// account_items: Iterable[tuple[str, str]] | None = None, + /// extra_items: Iterable[tuple[str, str]] | None = None, + /// extra_title: str | None = None, + /// verb_cancel: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Confirm summary of a transaction.""" + Qstr::MP_QSTR_confirm_summary => obj_fn_kw!(0, new_confirm_summary).as_obj(), + + /// def confirm_value( + /// *, + /// title: str, + /// value: str, + /// description: str | None, + /// subtitle: str | None, + /// verb: str | None = None, + /// verb_info: str | None = None, + /// verb_cancel: str | None = None, + /// info_button: bool = False, + /// hold: bool = False, + /// chunkify: bool = False, + /// text_mono: bool = True, + /// ) -> LayoutObj[UiResult]: + /// """Confirm value. Merge of confirm_total and confirm_output.""" + Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(), + + /// def confirm_with_info( + /// *, + /// title: str, + /// button: str, + /// info_button: str, + /// verb_cancel: str | None = None, + /// items: Iterable[tuple[int, str | bytes]], + /// ) -> LayoutObj[UiResult]: + /// """Confirm given items but with third button. Always single page + /// without scrolling. In mercury, the button is placed in + /// context menu.""" + Qstr::MP_QSTR_confirm_with_info => obj_fn_kw!(0, new_confirm_with_info).as_obj(), + + /// def continue_recovery_homepage( + /// *, + /// text: str, + /// subtext: str | None, + /// button: str | None, + /// recovery_type: RecoveryType, + /// show_instructions: bool = False, # unused on TT + /// remaining_shares: Iterable[tuple[str, str]] | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Device recovery homescreen.""" + Qstr::MP_QSTR_continue_recovery_homepage => obj_fn_kw!(0, new_continue_recovery_homepage).as_obj(), + + /// def flow_confirm_output( + /// *, + /// title: str | None, + /// subtitle: str | None, + /// message: str, + /// amount: str | None, + /// chunkify: bool, + /// text_mono: bool, + /// account: str | None, + /// account_path: str | None, + /// br_code: ButtonRequestType, + /// br_name: str, + /// address: str | None, + /// address_title: str | None, + /// summary_items: Iterable[tuple[str, str]] | None = None, + /// fee_items: Iterable[tuple[str, str]] | None = None, + /// summary_title: str | None = None, + /// summary_br_code: ButtonRequestType | None = None, + /// summary_br_name: str | None = None, + /// cancel_text: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page.""" + Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, new_flow_confirm_output).as_obj(), + + // TODO: supply more arguments for Wipe code setting (mercury) + /// + /// def flow_confirm_set_new_pin( + /// *, + /// title: str, + /// description: str, + /// ) -> LayoutObj[UiResult]: + /// """Confirm new PIN setup with an option to cancel action.""" + Qstr::MP_QSTR_flow_confirm_set_new_pin => obj_fn_kw!(0, new_flow_confirm_set_new_pin).as_obj(), + + /// def flow_get_address( + /// *, + /// address: str | bytes, + /// title: str, + /// description: str | None, + /// extra: str | None, + /// chunkify: bool, + /// address_qr: str, + /// case_sensitive: bool, + /// account: str | None, + /// path: str | None, + /// xpubs: list[tuple[str, str]], + /// title_success: str, + /// br_code: ButtonRequestType, + /// br_name: str, + /// ) -> LayoutObj[UiResult]: + /// """Get address / receive funds.""" + Qstr::MP_QSTR_flow_get_address => obj_fn_kw!(0, new_flow_get_address).as_obj(), + + /// def multiple_pages_texts( + /// *, + /// title: str, + /// verb: str, + /// items: list[str], + /// ) -> LayoutObj[UiResult]: + /// """Show multiple texts, each on its own page. TR specific.""" + Qstr::MP_QSTR_multiple_pages_texts => obj_fn_kw!(0, new_multiple_pages_texts).as_obj(), + + /// def prompt_backup() -> LayoutObj[UiResult]: + /// """Strongly recommend user to do a backup.""" + Qstr::MP_QSTR_prompt_backup => obj_fn_0!(new_prompt_backup).as_obj(), + + /// def request_bip39( + /// *, + /// prompt: str, + /// prefill_word: str, + /// can_go_back: bool, + /// ) -> LayoutObj[str]: + /// """BIP39 word input keyboard.""" + Qstr::MP_QSTR_request_bip39 => obj_fn_kw!(0, new_request_bip39).as_obj(), + + /// def request_slip39( + /// *, + /// prompt: str, + /// prefill_word: str, + /// can_go_back: bool, + /// ) -> LayoutObj[str]: + /// """SLIP39 word input keyboard.""" + Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(), + + /// def request_number( + /// *, + /// title: str, + /// count: int, + /// min_count: int, + /// max_count: int, + /// description: str | None = None, + /// more_info_callback: Callable[[int], str] | None = None, + /// ) -> LayoutObj[tuple[UiResult, int]]: + /// """Number input with + and - buttons, optional static description and optional dynamic + /// description.""" + Qstr::MP_QSTR_request_number => obj_fn_kw!(0, new_request_number).as_obj(), + + /// def request_pin( + /// *, + /// prompt: str, + /// subprompt: str, + /// allow_cancel: bool = True, + /// wrong_pin: bool = False, + /// ) -> LayoutObj[str | UiResult]: + /// """Request pin on device.""" + Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(), + + /// def request_passphrase( + /// *, + /// prompt: str, + /// max_len: int, + /// ) -> LayoutObj[str | UiResult]: + /// """Passphrase input keyboard.""" + Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(), + + /// def select_word( + /// *, + /// title: str, + /// description: str, + /// words: Iterable[str], + /// ) -> LayoutObj[int]: + /// """Select mnemonic word from three possibilities - seed check after backup. The + /// iterable must be of exact size. Returns index in range `0..3`.""" + Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(), + + /// def select_word_count( + /// *, + /// recovery_type: RecoveryType, + /// ) -> LayoutObj[int | str]: # TR returns str + /// """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. + /// For unlocking a repeated backup, select from 20 or 33.""" + Qstr::MP_QSTR_select_word_count => obj_fn_kw!(0, new_select_word_count).as_obj(), + + /// def set_brightness( + /// *, + /// current: int | None = None + /// ) -> LayoutObj[UiResult]: + /// """Show the brightness configuration dialog.""" + Qstr::MP_QSTR_set_brightness => obj_fn_kw!(0, new_set_brightness).as_obj(), + + /// def show_address_details( + /// *, + /// qr_title: str, + /// address: str, + /// case_sensitive: bool, + /// details_title: str, + /// account: str | None, + /// path: str | None, + /// xpubs: list[tuple[str, str]], + /// ) -> LayoutObj[UiResult]: + /// """Show address details - QR code, account, path, cosigner xpubs.""" + Qstr::MP_QSTR_show_address_details => obj_fn_kw!(0, new_show_address_details).as_obj(), + + /// def show_checklist( + /// *, + /// title: str, + /// items: Iterable[str], + /// active: int, + /// button: str, + /// ) -> LayoutObj[UiResult]: + /// """Checklist of backup steps. Active index is highlighted, previous items have check + /// mark next to them. Limited to 3 items.""" + Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), + + /// def show_danger( + /// *, + /// title: str, + /// description: str, + /// value: str = "", + /// verb_cancel: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Warning modal that makes it easier to cancel than to continue.""" + Qstr::MP_QSTR_show_danger => obj_fn_kw!(0, new_show_danger).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], + /// ) -> LayoutObj[UiResult]: + /// """Shown after successfully finishing a group.""" + Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(), + + /// def show_homescreen( + /// *, + /// label: str | None, + /// hold: bool, + /// notification: str | None, + /// notification_level: int = 0, + /// skip_first_paint: bool, + /// ) -> LayoutObj[UiResult]: + /// """Idle homescreen.""" + Qstr::MP_QSTR_show_homescreen => obj_fn_kw!(0, new_show_homescreen).as_obj(), + /// def show_info( /// *, /// title: str, @@ -142,7 +1576,121 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// time_ms: int = 0, /// ) -> LayoutObj[UiResult]: /// """Info screen.""" - Qstr::MP_QSTR_show_info => obj_fn_kw!(0, show_info).as_obj(), + Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(), + + /// def show_info_with_cancel( + /// *, + /// title: str, + /// items: Iterable[Tuple[str, str]], + /// horizontal: bool = False, + /// chunkify: bool = False, + /// ) -> LayoutObj[UiResult]: + /// """Show metadata for outgoing transaction.""" + Qstr::MP_QSTR_show_info_with_cancel => obj_fn_kw!(0, new_show_info_with_cancel).as_obj(), + + /// def show_lockscreen( + /// *, + /// label: str | None, + /// bootscreen: bool, + /// skip_first_paint: bool, + /// coinjoin_authorized: bool = False, + /// ) -> LayoutObj[UiResult]: + /// """Homescreen for locked device.""" + Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), + + /// def show_mismatch(*, title: str) -> LayoutObj[UiResult]: + /// """Warning of receiving address mismatch.""" + Qstr::MP_QSTR_show_mismatch => obj_fn_kw!(0, new_show_mismatch).as_obj(), + + /// def show_progress( + /// *, + /// description: str, + /// indeterminate: bool = False, + /// title: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Show progress loader. Please note that the number of lines reserved on screen for + /// description is determined at construction time. If you want multiline descriptions + /// make sure the initial description has at least that amount of lines.""" + Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(), + + /// def show_progress_coinjoin( + /// *, + /// title: str, + /// indeterminate: bool = False, + /// time_ms: int = 0, + /// skip_first_paint: bool = False, + /// ) -> LayoutObj[UiResult]: + /// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when + /// time_ms timeout is passed.""" + Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(), + + /// def show_remaining_shares( + /// *, + /// pages: Iterable[tuple[str, str]], + /// ) -> LayoutObj[UiResult]: + /// """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_share_words( + /// *, + /// words: Iterable[str], + /// title: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Show mnemonic for backup.""" + Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), + + /// def show_share_words_mercury( + /// *, + /// words: Iterable[str], + /// subtitle: str | None, + /// instructions: Iterable[str], + /// text_footer: str | None, + /// text_confirm: str, + /// ) -> LayoutObj[UiResult]: + /// """Show mnemonic for wallet backup preceded by an instruction screen and followed by a + /// confirmation screen.""" + Qstr::MP_QSTR_show_share_words_mercury => obj_fn_kw!(0, new_show_share_words_mercury).as_obj(), + + /// def show_simple( + /// *, + /// text: str, + /// title: str | None = None, + /// button: str | None = None, + /// ) -> LayoutObj[UiResult]: + /// """Simple dialog with text. TT: optional button.""" + Qstr::MP_QSTR_show_simple => obj_fn_kw!(0, new_show_simple).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(), /// class BacklightLevels: /// """Backlight levels. Values dynamically update based on user settings.""" diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs index 755b6b617a..5811db63a2 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs @@ -1,6 +1,6 @@ use crate::{ error, - micropython::{gc::Gc, list::List, map::Map, obj::Obj, qstr::Qstr, util}, + micropython::{gc::Gc, list::List}, strutil::TString, translations::TR, ui::{ @@ -14,7 +14,6 @@ use crate::{ FlowController, FlowMsg, SwipeFlow, SwipePage, }, geometry::Direction, - layout::obj::LayoutObj, }, }; @@ -39,6 +38,7 @@ pub enum ConfirmFido { static CRED_SELECTED: AtomicUsize = AtomicUsize::new(0); static SINGLE_CRED: AtomicBool = AtomicBool::new(false); +const EXTRA_PADDING: i16 = 6; impl FlowController for ConfirmFido { #[inline] @@ -50,7 +50,7 @@ impl FlowController for ConfirmFido { match (self, direction) { (Self::Intro, Direction::Left) => Self::Menu.swipe(direction), (Self::Menu, Direction::Right) => { - if Self::single_cred() { + if single_cred() { Self::Details.swipe_right() } else { Self::Intro.swipe_right() @@ -69,7 +69,7 @@ impl FlowController for ConfirmFido { (_, FlowMsg::Info) => Self::Menu.goto(), (Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled), (Self::Menu, FlowMsg::Cancelled) => { - if Self::single_cred() { + if single_cred() { Self::Details.swipe_right() } else { Self::Intro.swipe_right() @@ -88,11 +88,6 @@ impl FlowController for ConfirmFido { } } -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmFido::new_obj) } -} - fn footer_update_fn( content: &SwipeContent TString<'static>>>>, ctx: &mut EventCtx, @@ -103,124 +98,120 @@ fn footer_update_fn( footer.update_page_counter(ctx, current_page, total_pages); } -impl ConfirmFido { - const EXTRA_PADDING: i16 = 6; +fn single_cred() -> bool { + SINGLE_CRED.load(Ordering::Relaxed) +} - fn single_cred() -> bool { - SINGLE_CRED.load(Ordering::Relaxed) +pub fn new_confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon_name: Option>, + accounts: Gc, +) -> Result { + let num_accounts = accounts.len(); + SINGLE_CRED.store(num_accounts <= 1, Ordering::Relaxed); + CRED_SELECTED.store(0, Ordering::Relaxed); + + let content_intro = Frame::left_aligned( + title, + SwipeContent::new(Paragraphs::new(Paragraph::new::( + &theme::TEXT_MAIN_GREY_LIGHT, + TR::fido__select_intro.into(), + ))), + ) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()) + .with_swipe(Direction::Right, SwipeSettings::immediate()) + .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)); + + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let label_fn = move |page_index| { + let account = unwrap!(accounts.get(page_index)); + account + .try_into() + .unwrap_or_else(|_| TString::from_str("-")) + }; + + let content_choose_credential = Frame::left_aligned( + TR::fido__title_select_credential.into(), + SwipeContent::new(SwipePage::vertical(PagedVerticalMenu::new( + num_accounts, + label_fn, + ))), + ) + .with_subtitle(TR::fido__title_for_authentication.into()) + .with_menu_button() + .with_footer_page_hint( + TR::fido__more_credentials.into(), + TR::buttons__go_back.into(), + TR::instructions__swipe_up.into(), + TR::instructions__swipe_down.into(), + ) + .register_footer_update_fn(footer_update_fn) + .with_swipe(Direction::Down, SwipeSettings::default()) + .with_swipe(Direction::Right, SwipeSettings::immediate()) + .with_vertical_pages() + .map(|msg| match msg { + FrameMsg::Button(_) => Some(FlowMsg::Info), + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + }); + + let get_account = move || { + let current = CRED_SELECTED.load(Ordering::Relaxed); + let account = unwrap!(accounts.get(current)); + account.try_into().unwrap_or_else(|_| TString::from_str("")) + }; + let content_details = Frame::left_aligned( + TR::fido__title_credential_details.into(), + SwipeContent::new(FidoCredential::new(icon_name, app_name, get_account)), + ) + .with_footer(TR::instructions__swipe_up.into(), Some(title)) + .with_swipe(Direction::Up, SwipeSettings::default()) + .with_swipe(Direction::Right, SwipeSettings::immediate()); + let content_details = if single_cred() { + content_details.with_menu_button() + } else { + content_details.with_cancel_button() } + .map(|msg| match msg { + FrameMsg::Button(bm) => Some(bm), + _ => None, + }); - fn new_obj(_args: &[Obj], kwargs: &Map) -> Result { - let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; - let icon_name: Option = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?; - let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; - let num_accounts = accounts.len(); - SINGLE_CRED.store(num_accounts <= 1, Ordering::Relaxed); - CRED_SELECTED.store(0, Ordering::Relaxed); - - let content_intro = Frame::left_aligned( - title, - SwipeContent::new(Paragraphs::new(Paragraph::new::( - &theme::TEXT_MAIN_GREY_LIGHT, - TR::fido__select_intro.into(), - ))), - ) + let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm()) .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_swipe(Direction::Up, SwipeSettings::default()) - .with_swipe(Direction::Right, SwipeSettings::immediate()) - .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)); - - // Closure to lazy-load the information on given page index. - // Done like this to allow arbitrarily many pages without - // the need of any allocation here in Rust. - let label_fn = move |page_index| { - let account = unwrap!(accounts.get(page_index)); - account - .try_into() - .unwrap_or_else(|_| TString::from_str("-")) - }; - - let content_choose_credential = Frame::left_aligned( - TR::fido__title_select_credential.into(), - SwipeContent::new(SwipePage::vertical(PagedVerticalMenu::new( - num_accounts, - label_fn, - ))), - ) - .with_subtitle(TR::fido__title_for_authentication.into()) - .with_menu_button() - .with_footer_page_hint( - TR::fido__more_credentials.into(), - TR::buttons__go_back.into(), - TR::instructions__swipe_up.into(), - TR::instructions__swipe_down.into(), - ) - .register_footer_update_fn(footer_update_fn) + .with_footer(TR::instructions__tap_to_confirm.into(), None) .with_swipe(Direction::Down, SwipeSettings::default()) .with_swipe(Direction::Right, SwipeSettings::immediate()) - .with_vertical_pages() .map(|msg| match msg { + FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed), FrameMsg::Button(_) => Some(FlowMsg::Info), - FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), - }); - - let get_account = move || { - let current = CRED_SELECTED.load(Ordering::Relaxed); - let account = unwrap!(accounts.get(current)); - account.try_into().unwrap_or_else(|_| TString::from_str("")) - }; - let content_details = Frame::left_aligned( - TR::fido__title_credential_details.into(), - SwipeContent::new(FidoCredential::new(icon_name, app_name, get_account)), - ) - .with_footer(TR::instructions__swipe_up.into(), Some(title)) - .with_swipe(Direction::Up, SwipeSettings::default()) - .with_swipe(Direction::Right, SwipeSettings::immediate()); - let content_details = if Self::single_cred() { - content_details.with_menu_button() - } else { - content_details.with_cancel_button() - } - .map(|msg| match msg { - FrameMsg::Button(bm) => Some(bm), _ => None, }); - let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm()) - .with_menu_button() - .with_footer(TR::instructions__tap_to_confirm.into(), None) - .with_swipe(Direction::Down, SwipeSettings::default()) - .with_swipe(Direction::Right, SwipeSettings::immediate()) - .map(|msg| match msg { - FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed), - FrameMsg::Button(_) => Some(FlowMsg::Info), - _ => None, - }); + let content_menu = Frame::left_aligned( + "".into(), + VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()), + ) + .with_cancel_button() + .with_swipe(Direction::Right, SwipeSettings::immediate()) + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); - let content_menu = Frame::left_aligned( - "".into(), - VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()), - ) - .with_cancel_button() - .with_swipe(Direction::Right, SwipeSettings::immediate()) - .map(|msg| match msg { - FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), - FrameMsg::Button(_) => Some(FlowMsg::Cancelled), - }); - - let initial_page = if Self::single_cred() { - &ConfirmFido::Details - } else { - &ConfirmFido::Intro - }; - let res = SwipeFlow::new(initial_page)? - .with_page(&ConfirmFido::Intro, content_intro)? - .with_page(&ConfirmFido::ChooseCredential, content_choose_credential)? - .with_page(&ConfirmFido::Details, content_details)? - .with_page(&ConfirmFido::Tap, content_tap)? - .with_page(&ConfirmFido::Menu, content_menu)?; - Ok(LayoutObj::new_root(res)?.into()) - } + let initial_page = if single_cred() { + &ConfirmFido::Details + } else { + &ConfirmFido::Intro + }; + SwipeFlow::new(initial_page)? + .with_page(&ConfirmFido::Intro, content_intro)? + .with_page(&ConfirmFido::ChooseCredential, content_choose_credential)? + .with_page(&ConfirmFido::Details, content_details)? + .with_page(&ConfirmFido::Tap, content_tap)? + .with_page(&ConfirmFido::Menu, content_menu) } diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_homescreen.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_homescreen.rs new file mode 100644 index 0000000000..ee32668f6a --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_homescreen.rs @@ -0,0 +1,108 @@ +use crate::{ + error, + strutil::TString, + translations::TR, + ui::{ + component::{swipe_detect::SwipeSettings, CachedJpeg, ComponentExt}, + flow::{ + base::{Decision, DecisionBuilder}, + FlowController, FlowMsg, SwipeFlow, + }, + geometry::Direction, + model_mercury::{ + component::{ + Frame, FrameMsg, PromptMsg, PromptScreen, SwipeContent, VerticalMenu, + VerticalMenuChoiceMsg, + }, + theme, + }, + }, +}; + +/// Flow for a setting of homescreen wallpaper showing a preview of the image, +/// menu to cancel and tap to confirm prompt. +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum ConfirmHomescreen { + Homescreen, + Menu, + Confirm, +} + +impl FlowController for ConfirmHomescreen { + #[inline] + fn index(&'static self) -> usize { + *self as usize + } + + fn handle_swipe(&'static self, direction: Direction) -> Decision { + match (self, direction) { + (Self::Homescreen, Direction::Left) => Self::Menu.swipe(direction), + (Self::Homescreen, Direction::Up) => Self::Confirm.swipe(direction), + (Self::Menu, Direction::Right) => Self::Homescreen.swipe(direction), + (Self::Confirm, Direction::Down) => Self::Homescreen.swipe(direction), + (Self::Confirm, Direction::Left) => Self::Menu.swipe(direction), + _ => self.do_nothing(), + } + } + + fn handle_event(&'static self, msg: FlowMsg) -> Decision { + match (self, msg) { + (Self::Homescreen, FlowMsg::Info) => Self::Menu.goto(), + (Self::Menu, FlowMsg::Cancelled) => Self::Homescreen.swipe_right(), + (Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled), + (Self::Confirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed), + (Self::Confirm, FlowMsg::Info) => Self::Menu.goto(), + _ => self.do_nothing(), + } + } +} + +pub fn new_confirm_homescreen( + title: TString<'static>, + image: CachedJpeg, +) -> Result { + let content_homescreen = Frame::left_aligned(title, SwipeContent::new(image)) + .with_menu_button() + .with_footer( + TR::instructions__swipe_up.into(), + Some(TR::buttons__change.into()), + ) + .with_swipe(Direction::Up, SwipeSettings::default()) + // Homescreen + Tap to confirm + .with_pages(|_| 2) + .map(|msg| match msg { + FrameMsg::Button(_) => Some(FlowMsg::Info), + _ => None, + }); + + let content_menu = Frame::left_aligned( + TString::empty(), + VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()), + ) + .with_cancel_button() + .with_swipe(Direction::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::homescreen__title_set.into(), + SwipeContent::new(PromptScreen::new_tap_to_confirm()), + ) + .with_menu_button() + .with_footer(TR::instructions__tap_to_confirm.into(), None) + .with_swipe(Direction::Down, SwipeSettings::default()) + .with_swipe(Direction::Left, SwipeSettings::default()) + .map(|msg| match msg { + FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed), + FrameMsg::Button(_) => Some(FlowMsg::Info), + _ => None, + }); + + let res = SwipeFlow::new(&ConfirmHomescreen::Homescreen)? + .with_page(&ConfirmHomescreen::Homescreen, content_homescreen)? + .with_page(&ConfirmHomescreen::Menu, content_menu)? + .with_page(&ConfirmHomescreen::Confirm, content_confirm)?; + Ok(res) +} diff --git a/core/embed/rust/src/ui/model_mercury/flow/continue_recovery.rs b/core/embed/rust/src/ui/model_mercury/flow/continue_recovery_homepage.rs similarity index 98% rename from core/embed/rust/src/ui/model_mercury/flow/continue_recovery.rs rename to core/embed/rust/src/ui/model_mercury/flow/continue_recovery_homepage.rs index 6f37a24806..8dbdfe73b2 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/continue_recovery.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/continue_recovery_homepage.rs @@ -154,11 +154,11 @@ fn footer_update_fn( footer.update_page_counter(ctx, current_page, total_pages); } -pub fn new_continue_recovery( - first_screen: bool, - recovery_type: RecoveryType, +pub fn new_continue_recovery_homepage( text: TString<'static>, subtext: Option>, + recovery_type: RecoveryType, + show_instructions: bool, // 1st screen of the recovery process pages: Option>, ) -> Result { let (title, cancel_btn, cancel_title, cancel_intro) = match recovery_type { @@ -179,7 +179,7 @@ pub fn new_continue_recovery( let mut pars_main = ParagraphVecShort::new(); let footer_instruction; let footer_description; - if first_screen { + if show_instructions { pars_main.add(Paragraph::new( &theme::TEXT_MAIN_GREY_EXTRA_LIGHT, TR::recovery__enter_each_word, @@ -246,7 +246,7 @@ pub fn new_continue_recovery( _ => None, }); - let res = if first_screen { + let res = if show_instructions { let content_menu = Frame::left_aligned( TString::empty(), VerticalMenu::empty().danger(theme::ICON_CANCEL, cancel_btn.into()), diff --git a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs index c81b28a109..d0d99ca6f3 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs @@ -96,6 +96,7 @@ pub fn new_get_address( account: Option>, path: Option>, xpubs: Obj, // TODO: get rid of Obj + title_success: TString<'static>, br_code: u16, br_name: TString<'static>, ) -> Result { @@ -141,7 +142,7 @@ pub fn new_get_address( let content_confirmed = Frame::left_aligned( TR::words__title_success.into(), - StatusScreen::new_success_timeout(TR::address__confirmed.into()), + StatusScreen::new_success_timeout(title_success), ) .with_footer(TR::instructions__continue_in_app.into(), None) .with_result_icon(theme::ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT) diff --git a/core/embed/rust/src/ui/model_mercury/flow/mod.rs b/core/embed/rust/src/ui/model_mercury/flow/mod.rs index cbfc7cfb08..150455bddb 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/mod.rs @@ -2,11 +2,12 @@ pub mod confirm_action; #[cfg(feature = "universal_fw")] pub mod confirm_fido; pub mod confirm_firmware_update; +pub mod confirm_homescreen; pub mod confirm_output; pub mod confirm_reset; pub mod confirm_set_new_pin; pub mod confirm_summary; -pub mod continue_recovery; +pub mod continue_recovery_homepage; pub mod get_address; pub mod prompt_backup; pub mod request_number; @@ -24,11 +25,12 @@ pub use confirm_action::{ #[cfg(feature = "universal_fw")] pub use confirm_fido::new_confirm_fido; pub use confirm_firmware_update::new_confirm_firmware_update; +pub use confirm_homescreen::new_confirm_homescreen; pub use confirm_output::new_confirm_output; pub use confirm_reset::new_confirm_reset; pub use confirm_set_new_pin::SetNewPin; pub use confirm_summary::new_confirm_summary; -pub use continue_recovery::new_continue_recovery; +pub use continue_recovery_homepage::new_continue_recovery_homepage; pub use get_address::GetAddress; pub use prompt_backup::PromptBackup; pub use request_number::RequestNumber; diff --git a/core/embed/rust/src/ui/model_mercury/flow/request_number.rs b/core/embed/rust/src/ui/model_mercury/flow/request_number.rs index 9bfce9414d..2f74939111 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/request_number.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/request_number.rs @@ -67,8 +67,6 @@ pub fn new_request_number( max_count: u32, description: TString<'static>, info_closure: impl Fn(u32) -> TString<'static> + 'static, - br_code: u16, - br_name: TString<'static>, ) -> Result { NUM_DISPLAYED.store(count as u16, Ordering::Relaxed); @@ -94,8 +92,7 @@ pub fn new_request_number( NUM_DISPLAYED.store(n as u16, Ordering::Relaxed); Some(FlowMsg::Choice(n as usize)) } - }) - .one_button_request(ButtonRequest::from_num(br_code, br_name)); + }); let content_menu = Frame::left_aligned( TString::empty(), diff --git a/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs b/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs index d3ef86ae7b..c15bcb143f 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs @@ -78,15 +78,15 @@ fn footer_updating_func( } pub fn new_show_share_words( - title: TString<'static>, - subtitle: TString<'static>, share_words_vec: Vec, 33>, - description: Option>, + subtitle: TString<'static>, instructions_paragraphs: ParagraphVecShort<'static>, + text_footer: Option>, text_confirm: TString<'static>, ) -> Result { let nwords = share_words_vec.len(); let paragraphs_spacing = 8; + let title = TR::reset__recovery_wallet_backup_title.into(); let content_instruction = Frame::left_aligned( title, @@ -97,7 +97,7 @@ pub fn new_show_share_words( ), ) .with_subtitle(TR::words__instructions.into()) - .with_footer(TR::instructions__swipe_up.into(), description) + .with_footer(TR::instructions__swipe_up.into(), text_footer) .with_swipe(Direction::Up, SwipeSettings::default()) .map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed)) .one_button_request(ButtonRequestCode::ResetDevice.with_name("share_words")) diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 4431e9e478..a0642a1f27 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -62,8 +62,6 @@ use crate::{ }, }; -const CONFIRM_BLOB_INTRO_MARGIN: usize = 24; - impl TryFrom for Obj { type Error = Error; @@ -226,1262 +224,6 @@ where } } -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()?; - - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); - for item in IterBuf::new().try_iterate(items)? { - if item.is_str() { - ops = ops.text_normal(TString::try_from(item)?) - } else { - let [emphasis, text]: [Obj; 2] = util::iter_into_array(item)?; - let text: TString = text.try_into()?; - if emphasis.try_into()? { - ops = ops.text_demibold(text); - } else { - ops = ops.text_normal(text); - } - } - } - - new_confirm_action_simple( - FormattedText::new(ops).vertically_centered(), - ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), - ConfirmActionStrings::new(title, None, None, Some(title)), - false, - None, - 0, - false, - ) - .and_then(LayoutObj::new_root) - .map(Into::into) - }; - 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()?; - let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; - let description: Option = kwargs - .get(Qstr::MP_QSTR_description) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; - let extra: Option = kwargs - .get(Qstr::MP_QSTR_extra) - .unwrap_or_else(|_| Obj::const_none()) - .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 verb_info: Option = kwargs - .get(Qstr::MP_QSTR_verb_info) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let info: bool = kwargs.get_or(Qstr::MP_QSTR_info, true)?; - let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - let page_counter: bool = kwargs.get_or(Qstr::MP_QSTR_page_counter, false)?; - let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, true)?; - let cancel: bool = kwargs.get_or(Qstr::MP_QSTR_cancel, false)?; - - ConfirmBlobParams::new(title, data, description) - .with_description_font(&theme::TEXT_SUB_GREY) - .with_text_mono(text_mono) - .with_subtitle(subtitle) - .with_verb(verb) - .with_verb_cancel(verb_cancel) - .with_verb_info(if info { - Some(verb_info.unwrap_or(TR::words__title_information.into())) - } else { - None - }) - .with_extra(extra) - .with_chunkify(chunkify) - .with_page_counter(page_counter) - .with_cancel(cancel) - .with_prompt(prompt_screen) - .with_hold(hold) - .into_flow() - .and_then(LayoutObj::new_root) - .map(Into::into) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_blob_intro(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 data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; - 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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - ConfirmBlobParams::new(title, data, Some(TR::instructions__view_all_data.into())) - .with_verb(verb) - .with_verb_info(Some(TR::buttons__view_all_data.into())) - .with_description_font(&theme::TEXT_SUB_GREEN_LIME) - .with_subtitle(subtitle) - .with_verb_cancel(verb_cancel) - .with_footer_description(Some( - TR::buttons__confirm.into(), /* or words__confirm?? */ - )) - .with_chunkify(chunkify) - .with_page_limit(Some(1)) - .with_frame_margin(CONFIRM_BLOB_INTRO_MARGIN) - .into_flow() - .and_then(LayoutObj::new_root) - .map(Into::into) - }; - 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_firmware_update( - n_args: usize, - args: *const Obj, - kwargs: *mut Map, -) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let fingerprint: TString = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?; - - let flow = - flow::confirm_firmware_update::new_confirm_firmware_update(description, fingerprint)?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_properties(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 hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let paragraphs = PropsList::new( - items, - &theme::TEXT_NORMAL, - &theme::TEXT_MONO, - &theme::TEXT_MONO, - )?; - - new_confirm_action_simple( - paragraphs.into_paragraphs(), - ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), - ConfirmActionStrings::new(title, None, None, hold.then_some(title)), - hold, - None, - 0, - false, - ) - .and_then(LayoutObj::new_root) - .map(Into::into) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_homescreen(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 image: Obj = kwargs.get(Qstr::MP_QSTR_image)?; - - let jpeg: BinaryData = image.try_into()?; - - let obj = if jpeg.is_empty() { - // Incoming data may be empty, meaning we should - // display default homescreen message. - let paragraphs = ParagraphVecShort::from_iter([Paragraph::new( - &theme::TEXT_DEMIBOLD, - TR::homescreen__set_default, - )]) - .into_paragraphs(); - - new_confirm_action_simple( - paragraphs, - ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), - ConfirmActionStrings::new( - TR::homescreen__settings_title.into(), - Some(TR::homescreen__settings_subtitle.into()), - None, - Some(TR::homescreen__settings_title.into()), - ), - false, - None, - 0, - false, - ) - .and_then(LayoutObj::new_root) - .map(Into::into) - } else { - if !check_homescreen_format(jpeg) { - return Err(value_error!(c"Invalid image.")); - }; - - let obj = LayoutObj::new(SwipeUpScreen::new( - Frame::left_aligned(title, SwipeContent::new(CachedJpeg::new(jpeg, 1))) - .with_cancel_button() - .with_footer( - TR::instructions__swipe_up.into(), - Some(TR::buttons__change.into()), - ) - .with_swipe(Direction::Up, SwipeSettings::default()), - )); - Ok(obj?.into()) - }; - obj - }; - - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_reset(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let recovery: bool = kwargs.get(Qstr::MP_QSTR_recovery)?.try_into()?; - - let flow = flow::confirm_reset::new_confirm_reset(recovery)?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_set_new_pin(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 flow = flow::confirm_set_new_pin::new_set_new_pin(title, description)?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_output(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()?; - let subtitle: Option = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into_option()?; - - let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; - let account_path: Option = - kwargs.get(Qstr::MP_QSTR_account_path)?.try_into_option()?; - - let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?; - let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?; - - let message: Obj = kwargs.get(Qstr::MP_QSTR_message)?; - let amount: Option = kwargs.get(Qstr::MP_QSTR_amount)?.try_into_option()?; - - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; - - let address: Option = kwargs.get(Qstr::MP_QSTR_address)?.try_into_option()?; - let address_title: Option = - kwargs.get(Qstr::MP_QSTR_address_title)?.try_into_option()?; - - let summary_items: Option = - kwargs.get(Qstr::MP_QSTR_summary_items)?.try_into_option()?; - let fee_items: Option = kwargs.get(Qstr::MP_QSTR_fee_items)?.try_into_option()?; - - let summary_title: Option = - kwargs.get(Qstr::MP_QSTR_summary_title)?.try_into_option()?; - let summary_br_name: Option = kwargs - .get(Qstr::MP_QSTR_summary_br_name)? - .try_into_option()?; - let summary_br_code: Option = kwargs - .get(Qstr::MP_QSTR_summary_br_code)? - .try_into_option()?; - - let address_title = address_title.unwrap_or(TR::words__address.into()); - let cancel_text: Option = - kwargs.get(Qstr::MP_QSTR_cancel_text)?.try_into_option()?; - - let main_params = ConfirmBlobParams::new(title.unwrap_or(TString::empty()), message, None) - .with_subtitle(subtitle) - .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_chunkify(chunkify) - .with_text_mono(text_mono) - .with_swipe_up(); - - let content_amount_params = amount.map(|amount| { - ConfirmBlobParams::new(TR::words__amount.into(), amount, None) - .with_subtitle(subtitle) - .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_text_mono(text_mono) - .with_swipe_up() - .with_swipe_down() - }); - - let address_params = address.map(|address| { - ConfirmBlobParams::new(address_title, address, None) - .with_cancel_button() - .with_chunkify(true) - .with_text_mono(true) - .with_swipe_right() - }); - - let mut fee_items_params = - ShowInfoParams::new(TR::confirm_total__title_fee.into()).with_cancel_button(); - if fee_items.is_some() { - for pair in IterBuf::new().try_iterate(fee_items.unwrap())? { - let [label, value]: [TString; 2] = util::iter_into_array(pair)?; - fee_items_params = unwrap!(fee_items_params.add(label, value)); - } - } - - let summary_items_params: Option = if summary_items.is_some() { - let mut summary = - ShowInfoParams::new(summary_title.unwrap_or(TR::words__title_summary.into())) - .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_swipe_up() - .with_swipe_down(); - for pair in IterBuf::new().try_iterate(summary_items.unwrap())? { - let [label, value]: [TString; 2] = util::iter_into_array(pair)?; - summary = unwrap!(summary.add(label, value)); - } - Some(summary) - } else { - None - }; - - let flow = flow::confirm_output::new_confirm_output( - main_params, - account, - account_path, - br_name, - br_code, - content_amount_params, - address_params, - address_title, - summary_items_params, - fee_items_params, - summary_br_name, - summary_br_code, - cancel_text, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let amount: TString = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?; - let amount_label: TString = kwargs.get(Qstr::MP_QSTR_amount_label)?.try_into()?; - let fee: TString = kwargs.get(Qstr::MP_QSTR_fee)?.try_into()?; - let fee_label: TString = kwargs.get(Qstr::MP_QSTR_fee_label)?.try_into()?; - let title: Option = kwargs - .get(Qstr::MP_QSTR_title) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let account_items: Option = kwargs - .get(Qstr::MP_QSTR_account_items) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let extra_items: Option = kwargs - .get(Qstr::MP_QSTR_extra_items) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let extra_title: Option = kwargs - .get(Qstr::MP_QSTR_extra_title) - .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 mut summary_params = ShowInfoParams::new(title.unwrap_or(TString::empty())) - .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_swipe_up(); - summary_params = unwrap!(summary_params.add(amount_label, amount)); - summary_params = unwrap!(summary_params.add(fee_label, fee)); - - // collect available info - let account_params = if let Some(items) = account_items { - let mut account_params = - ShowInfoParams::new(TR::send__send_from.into()).with_cancel_button(); - for pair in IterBuf::new().try_iterate(items)? { - let [label, value]: [TString; 2] = util::iter_into_array(pair)?; - account_params = unwrap!(account_params.add(label, value)); - } - Some(account_params) - } else { - None - }; - let extra_params = if let Some(items) = extra_items { - let extra_title = extra_title.unwrap_or(TR::buttons__more_info.into()); - let mut extra_params = ShowInfoParams::new(extra_title).with_cancel_button(); - for pair in IterBuf::new().try_iterate(items)? { - let [label, value]: [TString; 2] = util::iter_into_array(pair)?; - extra_params = unwrap!(extra_params.add(label, value)); - } - Some(extra_params) - } else { - None - }; - - let flow = flow::new_confirm_summary( - summary_params, - account_params, - extra_params, - extra_title, - verb_cancel, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_set_brightness(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let current: Option = kwargs.get(Qstr::MP_QSTR_current)?.try_into_option()?; - let flow = flow::set_brightness::new_set_brightness(current)?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_info_with_cancel(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 items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - let _horizontal: bool = kwargs.get_or(Qstr::MP_QSTR_horizontal, false)?; // FIXME - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - let mut paragraphs = ParagraphVecShort::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [key, value]: [Obj; 2] = util::iter_into_array(para)?; - let key: TString = key.try_into()?; - let value: TString = value.try_into()?; - paragraphs.add(Paragraph::new(&theme::TEXT_SUB_GREY, key).no_break()); - if chunkify { - paragraphs.add(Paragraph::new( - theme::get_chunkified_text_style(value.len()), - value, - )); - } else { - paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); - } - } - - let obj = LayoutObj::new(SwipeUpScreen::new( - Frame::left_aligned(title, SwipeContent::new(paragraphs.into_paragraphs())) - .with_cancel_button(), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_value(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 subtitle: Option = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into_option()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let value: Obj = kwargs.get(Qstr::MP_QSTR_value)?; - let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false)?; - - let verb: Option = kwargs - .get(Qstr::MP_QSTR_verb) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let verb_info: Option = kwargs - .get(Qstr::MP_QSTR_verb_info) - .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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; - - ConfirmBlobParams::new(title, value, description) - .with_subtitle(subtitle) - .with_verb(verb) - .with_verb_cancel(verb_cancel) - .with_verb_info(if info_button { - Some(verb_info.unwrap_or(TR::words__title_information.into())) - } else { - None - }) - .with_chunkify(chunkify) - .with_text_mono(text_mono) - .with_prompt(hold) - .with_hold(hold) - .into_flow() - .and_then(LayoutObj::new_root) - .map(Into::into) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; - let amount_change: TString = kwargs.get(Qstr::MP_QSTR_amount_change)?.try_into()?; - let amount_new: TString = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?; - - let description = if sign < 0 { - TR::modify_amount__decrease_amount - } else { - TR::modify_amount__increase_amount - }; - - let paragraphs = ParagraphVecShort::from_iter([ - Paragraph::new(&theme::TEXT_NORMAL, description), - Paragraph::new(&theme::TEXT_MONO, amount_change), - Paragraph::new(&theme::TEXT_NORMAL, TR::modify_amount__new_amount), - Paragraph::new(&theme::TEXT_MONO, amount_new), - ]) - .into_paragraphs(); - - 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_swipe(Direction::Up, SwipeSettings::default()), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_modify_fee(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 sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; - let user_fee_change: TString = kwargs.get(Qstr::MP_QSTR_user_fee_change)?.try_into()?; - let total_fee_new: TString = kwargs.get(Qstr::MP_QSTR_total_fee_new)?.try_into()?; - - let (description, change, total_label) = match sign { - s if s < 0 => ( - TR::modify_fee__decrease_fee, - user_fee_change, - TR::modify_fee__new_transaction_fee, - ), - s if s > 0 => ( - TR::modify_fee__increase_fee, - user_fee_change, - TR::modify_fee__new_transaction_fee, - ), - _ => ( - TR::modify_fee__no_change, - "".into(), - TR::modify_fee__transaction_fee, - ), - }; - - let paragraphs = ParagraphVecShort::from_iter([ - Paragraph::new(&theme::TEXT_SUB_GREY, description), - Paragraph::new(&theme::TEXT_MONO, change), - Paragraph::new(&theme::TEXT_SUB_GREY, total_label), - Paragraph::new(&theme::TEXT_MONO, total_fee_new), - ]); - - let flow = flow::new_confirm_action_simple( - paragraphs.into_paragraphs(), - ConfirmActionExtra::Menu( - ConfirmActionMenuStrings::new() - .with_verb_info(Some(TR::words__title_information.into())), - ), - ConfirmActionStrings::new(title, None, None, Some(title)), - true, - None, - 0, - false, - )?; - - Ok(LayoutObj::new_root(flow)?.into()) - }; - 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()?; - let subtitle: TString = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into()?; - let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_words)?; - let share_words_vec: Vec = util::iter_into_vec(share_words_obj)?; - 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 text_info: Obj = kwargs.get(Qstr::MP_QSTR_text_info)?; - let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?; - - let mut instructions_paragraphs = ParagraphVecShort::new(); - for item in IterBuf::new().try_iterate(text_info)? { - let text: TString = item.try_into()?; - instructions_paragraphs.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text)); - } - - let flow = flow::show_share_words::new_show_share_words( - title, - subtitle, - share_words_vec, - description, - instructions_paragraphs, - text_confirm, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - 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_mismatch(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 = TR::addr_mismatch__contact_support_at.into(); - let url: TString = TR::addr_mismatch__support_url.into(); - let button: TString = TR::buttons__quit.into(); - - let paragraphs = ParagraphVecShort::from_iter([ - Paragraph::new(&theme::TEXT_NORMAL, description).centered(), - Paragraph::new(&theme::TEXT_DEMIBOLD, url).centered(), - ]) - .into_paragraphs(); - - let obj = LayoutObj::new(SwipeUpScreen::new( - Frame::left_aligned(title, SwipeContent::new(paragraphs)) - .with_cancel_button() - .with_footer(TR::instructions__swipe_up.into(), Some(button)) - .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())?; - - let obj = LayoutObj::new(Border::new( - theme::borders(), - Paragraphs::new(Paragraph::new(&theme::TEXT_DEMIBOLD, description)), - ))?; - - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_with_info(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 info_button: TString = kwargs.get(Qstr::MP_QSTR_info_button)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecShort::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [font, text]: [Obj; 2] = util::iter_into_array(para)?; - let style: &TextStyle = theme::textstyle_number(font.try_into()?); - let text: TString = text.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - if paragraphs.is_full() { - break; - } - } - - let flow = flow::new_confirm_action_simple( - paragraphs.into_paragraphs(), - ConfirmActionExtra::Menu( - ConfirmActionMenuStrings::new().with_verb_info(Some(info_button)), - ), - ConfirmActionStrings::new(title, None, None, None) - .with_footer_description(Some(button)), - false, - None, - 0, - false, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let max_rounds: TString = kwargs.get(Qstr::MP_QSTR_max_rounds)?.try_into()?; - let max_feerate: TString = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?; - - let paragraphs = ParagraphVecShort::from_iter([ - Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_rounds), - Paragraph::new(&theme::TEXT_MONO, max_rounds), - Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_mining_fee), - Paragraph::new(&theme::TEXT_MONO, max_feerate), - ]) - .into_paragraphs(); - - new_confirm_action_simple( - paragraphs, - ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), - ConfirmActionStrings::new( - TR::coinjoin__title.into(), - None, - None, - Some(TR::coinjoin__title.into()), - ), - true, - None, - 0, - false, - ) - .and_then(LayoutObj::new_root) - .map(Into::into) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_continue_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let first_screen: bool = kwargs.get(Qstr::MP_QSTR_first_screen)?.try_into()?; - let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; - let text: TString = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?; // #shares entered - let subtext: Option = kwargs.get(Qstr::MP_QSTR_subtext)?.try_into_option()?; // #shares remaining - let pages: Option = kwargs.get(Qstr::MP_QSTR_pages)?.try_into_option()?; // info about remaining shares - - let pages_vec = if let Some(pages_obj) = pages { - let mut vec = ParagraphVecLong::new(); - for page in IterBuf::new().try_iterate(pages_obj)? { - let [title, description]: [TString; 2] = util::iter_into_array(page)?; - vec.add(Paragraph::new(&theme::TEXT_SUB_GREY, title)) - .add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, description).break_after()); - } - Some(vec) - } else { - None - }; - - let flow = flow::continue_recovery::new_continue_recovery( - first_screen, - recovery_type, - text, - subtext, - pages_vec, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_get_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()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let extra: Option = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; - let address: Obj = kwargs.get(Qstr::MP_QSTR_address)?; - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - let address_qr: TString = kwargs.get(Qstr::MP_QSTR_address_qr)?.try_into()?; - let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?; - let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; - let path: Option = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?; - let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?; - let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?; - let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?; - - let flow = flow::get_address::new_get_address( - title, - description, - extra, - address, - chunkify, - address_qr, - case_sensitive, - account, - path, - xpubs, - br_code, - br_name, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_prompt_backup() -> Obj { - let block = || { - let flow = flow::prompt_backup::new_prompt_backup()?; - let obj = LayoutObj::new_root(flow)?; - Ok(obj.into()) - }; - unsafe { util::try_or_raise(block) } -} - -extern "C" fn new_request_number(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 count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?; - let min_count: u32 = kwargs.get(Qstr::MP_QSTR_min_count)?.try_into()?; - let max_count: u32 = kwargs.get(Qstr::MP_QSTR_max_count)?.try_into()?; - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let info_cb: Obj = kwargs.get(Qstr::MP_QSTR_info)?; - assert!(info_cb != Obj::const_none()); - let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?; - let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?; - - let mp_info_closure = move |num: u32| { - // TODO: Handle error - let text = info_cb - .call_with_n_args(&[num.try_into().unwrap()]) - .unwrap(); - TString::try_from(text).unwrap() - }; - - let flow = flow::request_number::new_request_number( - title, - count, - min_count, - max_count, - description, - mp_info_closure, - br_code, - br_name, - )?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_passphrase(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()?; - let _max_len: usize = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?; - - let flow = flow::request_passphrase::new_request_passphrase()?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_pin(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()?; - let subprompt: TString = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?; - let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?; - let warning: bool = kwargs.get_or(Qstr::MP_QSTR_wrong_pin, false)?; - let warning = if warning { - Some(TR::pin__wrong_pin.into()) - } else { - None - }; - Ok(LayoutObj::new(PinKeyboard::new(prompt, subprompt, warning, allow_cancel))?.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()?; - let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; - let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; - let obj = LayoutObj::new(MnemonicKeyboard::new( - prefill_word.map(Bip39Input::prefilled_word), - prompt, - can_go_back, - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_slip39(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()?; - let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; - let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; - let obj = LayoutObj::new(MnemonicKeyboard::new( - prefill_word.map(Slip39Input::prefilled_word), - prompt, - can_go_back, - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_select_word(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 words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?; - let words: [TString; 3] = util::iter_into_array(words_iterable)?; - - let content = VerticalMenu::select_word(words); - let frame_with_menu = Frame::left_aligned(title, content).with_subtitle(description); - Ok(LayoutObj::new(frame_with_menu)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_checklist(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 active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() { - let style = match i.cmp(&active) { - Ordering::Less => &theme::TEXT_CHECKLIST_DONE, - Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, - Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, - }; - let text: TString = item.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - let checklist_content = Checklist::from_paragraphs( - theme::ICON_CHEVRON_RIGHT, - theme::ICON_BULLET_CHECKMARK, - active, - paragraphs - .into_paragraphs() - .with_spacing(theme::CHECKLIST_SPACING), - ) - .with_check_width(theme::CHECKLIST_CHECK_WIDTH) - .with_numerals() - .with_icon_done_color(theme::GREEN) - .with_done_offset(theme::CHECKLIST_DONE_OFFSET); - - let obj = LayoutObj::new(SwipeUpScreen::new( - Frame::left_aligned(title, SwipeContent::new(checklist_content)) - .with_footer(TR::instructions__swipe_up.into(), None) - .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_tutorial() -> Obj { - let block = || { - let flow = flow::show_tutorial::new_show_tutorial()?; - let obj = LayoutObj::new_root(flow)?; - Ok(obj.into()) - }; - unsafe { util::try_or_raise(block) } -} - -extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; - let content = if matches!(recovery_type, RecoveryType::UnlockRepeatedBackup) { - SelectWordCount::new_multishare() - } else { - SelectWordCount::new_all() - }; - let obj = LayoutObj::new(Frame::left_aligned( - TR::recovery__num_of_words.into(), - content, - ))?; - Ok(obj.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, - kwargs: *mut Map, -) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let lines_iterable: Obj = kwargs.get(Qstr::MP_QSTR_lines)?; - let lines: [TString; 4] = util::iter_into_array(lines_iterable)?; - - let paragraphs = ParagraphVecShort::from_iter([ - Paragraph::new(&theme::TEXT_NORMAL_GREY_EXTRA_LIGHT, lines[0]).centered(), - Paragraph::new(&theme::TEXT_DEMIBOLD, lines[1]).centered(), - Paragraph::new(&theme::TEXT_NORMAL_GREY_EXTRA_LIGHT, lines[2]).centered(), - Paragraph::new(&theme::TEXT_DEMIBOLD, lines[3]).centered(), - ]) - .into_paragraphs() - .with_placement(geometry::LinearPlacement::vertical().align_at_center()); - - let obj = LayoutObj::new(SwipeUpScreen::new( - Frame::left_aligned("".into(), SwipeContent::new(paragraphs)) - .with_footer(TR::instructions__swipe_up.into(), None) - .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_progress(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; - let title: Option = kwargs - .get(Qstr::MP_QSTR_title) - .and_then(Obj::try_into_option) - .unwrap_or(None); - - let (title, description) = if let Some(title) = title { - (title, description) - } else { - (description, "".into()) - }; - - Ok(LayoutObj::new(Progress::new(title, indeterminate, description))?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_progress_coinjoin(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 indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; - let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; - let skip_first_paint: bool = kwargs.get_or(Qstr::MP_QSTR_skip_first_paint, false)?; - - // The second type parameter is actually not used in `new()` but we need to - // provide it. - let progress = CoinJoinProgress::::new(title, indeterminate)?; - let obj = if time_ms > 0 && indeterminate { - let timeout = Timeout::new(time_ms); - LayoutObj::new((timeout, progress.map(|_msg| None)))? - } else { - LayoutObj::new(progress)? - }; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let label: TString<'static> = kwargs - .get(Qstr::MP_QSTR_label)? - .try_into_option()? - .unwrap_or_else(|| model::FULL_NAME.into()); - let notification: Option> = - kwargs.get(Qstr::MP_QSTR_notification)?.try_into_option()?; - let notification_level: u8 = kwargs.get_or(Qstr::MP_QSTR_notification_level, 0)?; - let hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?; - let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - - let notification = notification.map(|w| (w, notification_level)); - let obj = LayoutObj::new(Homescreen::new(label, notification, hold))?; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let label: TString<'static> = kwargs - .get(Qstr::MP_QSTR_label)? - .try_into_option()? - .unwrap_or_else(|| model::FULL_NAME.into()); - let bootscreen: bool = kwargs.get(Qstr::MP_QSTR_bootscreen)?.try_into()?; - let coinjoin_authorized: bool = kwargs.get_or(Qstr::MP_QSTR_coinjoin_authorized, false)?; - let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - - let obj = LayoutObj::new(Lockscreen::new(label, bootscreen, coinjoin_authorized))?; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj { - let block = || { - let buffer = data.try_into()?; - Ok(check_homescreen_format(buffer).into()) - }; - - unsafe { util::try_or_raise(block) } -} - -extern "C" fn new_show_wait_text(message: Obj) -> Obj { - let block = || { - let message: TString<'static> = message.try_into()?; - Ok(LayoutObj::new(Connect::new(message, theme::FG, theme::BG))?.into()) - }; - - unsafe { util::try_or_raise(block) } -} - -extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - #[cfg(feature = "universal_fw")] - return flow::confirm_fido::new_confirm_fido(n_args, args, kwargs); - #[cfg(not(feature = "universal_fw"))] - panic!(); -} - -extern "C" fn new_show_danger(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 value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; - let verb_cancel: Option = kwargs - .get(Qstr::MP_QSTR_verb_cancel) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - - let flow = flow::show_danger::new_show_danger(title, description, value, verb_cancel)?; - Ok(LayoutObj::new_root(flow)?.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - #[no_mangle] pub static mp_module_trezorui2: Module = obj_module! { /// from trezor import utils @@ -1489,480 +231,4 @@ pub static mp_module_trezorui2: Module = obj_module! { /// Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(), - /// def disable_animation(disable: bool) -> None: - /// """Disable animations, debug builds only.""" - Qstr::MP_QSTR_disable_animation => obj_fn_1!(upy_disable_animation).as_obj(), - - /// def check_homescreen_format(data: bytes) -> bool: - /// """Check homescreen format and dimensions.""" - Qstr::MP_QSTR_check_homescreen_format => obj_fn_1!(upy_check_homescreen_format).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 confirm_emphasized( - /// *, - /// title: str, - /// items: Iterable[str | tuple[bool, str]], - /// verb: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm formatted text that has been pre-split in python. For tuples - /// the first component is a bool indicating whether this part is emphasized.""" - Qstr::MP_QSTR_confirm_emphasized => obj_fn_kw!(0, new_confirm_emphasized).as_obj(), - - /// def confirm_homescreen( - /// *, - /// title: str, - /// image: bytes, - /// ) -> LayoutObj[UiResult]: - /// """Confirm homescreen.""" - Qstr::MP_QSTR_confirm_homescreen => obj_fn_kw!(0, new_confirm_homescreen).as_obj(), - - /// def confirm_blob( - /// *, - /// title: str, - /// data: str | bytes, - /// description: str | None, - /// text_mono: bool = True, - /// extra: str | None = None, - /// subtitle: str | None = None, - /// verb: str | None = None, - /// verb_cancel: str | None = None, - /// verb_info: str | None = None, - /// info: bool = True, - /// hold: bool = False, - /// chunkify: bool = False, - /// page_counter: bool = False, - /// prompt_screen: bool = False, - /// cancel: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm byte sequence data.""" - Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), - - /// def confirm_blob_intro( - /// *, - /// title: str, - /// data: str | bytes, - /// subtitle: str | None = None, - /// verb: str | None = None, - /// verb_cancel: str | None = None, - /// chunkify: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm byte sequence data by showing only the first page of the data - /// and instructing the user to access the menu in order to view all the data, - /// which can then be confirmed using confirm_blob.""" - Qstr::MP_QSTR_confirm_blob_intro => obj_fn_kw!(0, new_confirm_blob_intro).as_obj(), - - /// def confirm_properties( - /// *, - /// title: str, - /// items: list[tuple[str | None, str | bytes | None, bool]], - /// hold: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm list of key-value pairs. The third component in the tuple should be True if - /// the value is to be rendered as binary with monospace font, False otherwise.""" - Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(), - - /// def flow_confirm_reset(recovery: bool) -> LayoutObj[UiResult]: - /// """Confirm TOS before creating wallet creation or wallet recovery.""" - Qstr::MP_QSTR_flow_confirm_reset => obj_fn_kw!(0, new_confirm_reset).as_obj(), - - // TODO: supply more arguments for Wipe code setting when figma done - /// def flow_confirm_set_new_pin( - /// *, - /// title: str, - /// description: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm new PIN setup with an option to cancel action.""" - Qstr::MP_QSTR_flow_confirm_set_new_pin => obj_fn_kw!(0, new_confirm_set_new_pin).as_obj(), - - /// def show_info_with_cancel( - /// *, - /// title: str, - /// items: Iterable[Tuple[str, str]], - /// horizontal: bool = False, - /// chunkify: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Show metadata for outgoing transaction.""" - Qstr::MP_QSTR_show_info_with_cancel => obj_fn_kw!(0, new_show_info_with_cancel).as_obj(), - - /// def confirm_value( - /// *, - /// title: str, - /// value: str, - /// description: str | None, - /// subtitle: str | None, - /// verb: str | None = None, - /// verb_info: str | None = None, - /// verb_cancel: str | None = None, - /// info_button: bool = False, - /// hold: bool = False, - /// chunkify: bool = False, - /// text_mono: bool = True, - /// ) -> LayoutObj[UiResult]: - /// """Confirm value. Merge of confirm_total and confirm_output.""" - Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(), - - /// def confirm_modify_output( - /// *, - /// sign: int, - /// amount_change: str, - /// amount_new: str, - /// ) -> LayoutObj[UiResult]: - /// """Decrease or increase output amount.""" - Qstr::MP_QSTR_confirm_modify_output => obj_fn_kw!(0, new_confirm_modify_output).as_obj(), - - /// def confirm_modify_fee( - /// *, - /// title: str, - /// sign: int, - /// user_fee_change: str, - /// total_fee_new: str, - /// fee_rate_amount: str | None, # ignored - /// ) -> LayoutObj[UiResult]: - /// """Decrease or increase transaction fee.""" - Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(), - - /// def confirm_fido( - /// *, - /// title: str, - /// app_name: str, - /// icon_name: str | None, - /// accounts: list[str | None], - /// ) -> LayoutObj[int | UiResult]: - /// """FIDO confirmation. - /// - /// Returns page index in case of confirmation and CANCELLED otherwise. - /// """ - 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_danger( - /// *, - /// title: str, - /// description: str, - /// value: str = "", - /// verb_cancel: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Warning modal that makes it easier to cancel than to continue.""" - Qstr::MP_QSTR_show_danger => obj_fn_kw!(0, new_show_danger).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_mismatch(*, title: str) -> LayoutObj[UiResult]: - /// """Warning modal, receiving address mismatch.""" - Qstr::MP_QSTR_show_mismatch => obj_fn_kw!(0, new_show_mismatch).as_obj(), - - /// def show_simple( - /// *, - /// title: str | None, - /// description: str = "", - /// button: str = "", - /// ) -> LayoutObj[UiResult]: - /// """Simple dialog with text and one button.""" - Qstr::MP_QSTR_show_simple => obj_fn_kw!(0, new_show_simple).as_obj(), - - /// def confirm_with_info( - /// *, - /// title: str, - /// button: str, - /// info_button: str, - /// items: Iterable[tuple[int, str]], - /// ) -> LayoutObj[UiResult]: - /// """Confirm given items but with third button. In mercury, the button is placed in - /// context menu.""" - Qstr::MP_QSTR_confirm_with_info => obj_fn_kw!(0, new_confirm_with_info).as_obj(), - - /// def confirm_coinjoin( - /// *, - /// max_rounds: str, - /// max_feerate: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm coinjoin authorization.""" - Qstr::MP_QSTR_confirm_coinjoin => obj_fn_kw!(0, new_confirm_coinjoin).as_obj(), - - /// def request_pin( - /// *, - /// prompt: str, - /// subprompt: str, - /// allow_cancel: bool = True, - /// wrong_pin: bool = False, - /// ) -> LayoutObj[str | UiResult]: - /// """Request pin on device.""" - Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(), - - /// def flow_request_passphrase( - /// *, - /// prompt: str, - /// max_len: int, - /// ) -> LayoutObj[str | UiResult]: - /// """Passphrase input keyboard.""" - Qstr::MP_QSTR_flow_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(), - - /// def request_bip39( - /// *, - /// prompt: str, - /// prefill_word: str, - /// can_go_back: bool, - /// ) -> LayoutObj[str]: - /// """BIP39 word input keyboard.""" - Qstr::MP_QSTR_request_bip39 => obj_fn_kw!(0, new_request_bip39).as_obj(), - - /// def request_slip39( - /// *, - /// prompt: str, - /// prefill_word: str, - /// can_go_back: bool, - /// ) -> LayoutObj[str]: - /// """SLIP39 word input keyboard.""" - Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(), - - /// def select_word( - /// *, - /// title: str, - /// description: str, - /// words: Iterable[str], - /// ) -> LayoutObj[int]: - /// """Select mnemonic word from three possibilities - seed check after backup. The - /// iterable must be of exact size. Returns index in range `0..3`.""" - Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(), - - /// def flow_prompt_backup() -> LayoutObj[UiResult]: - /// """Prompt a user to create backup with an option to skip.""" - Qstr::MP_QSTR_flow_prompt_backup => obj_fn_0!(new_prompt_backup).as_obj(), - - /// def flow_show_share_words( - /// *, - /// title: str, - /// subtitle: str, - /// words: Iterable[str], - /// description: str, - /// text_info: Iterable[str], - /// text_confirm: str, - /// ) -> LayoutObj[UiResult]: - /// """Show wallet backup words preceded by an instruction screen and followed by - /// confirmation.""" - Qstr::MP_QSTR_flow_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), - - /// def flow_request_number( - /// *, - /// title: str, - /// count: int, - /// min_count: int, - /// max_count: int, - /// description: str, - /// info: Callable[[int], str] | None = None, - /// br_code: ButtonRequestType, - /// br_name: str, - /// ) -> LayoutObj[tuple[UiResult, int]]: - /// """Number input with + and - buttons, description, and context menu with cancel and - /// info.""" - Qstr::MP_QSTR_flow_request_number => obj_fn_kw!(0, new_request_number).as_obj(), - - /// def set_brightness( - /// *, - /// current: int | None = None - /// ) -> LayoutObj[UiResult]: - /// """Show the brightness configuration dialog.""" - Qstr::MP_QSTR_set_brightness => obj_fn_kw!(0, new_set_brightness).as_obj(), - - /// def show_checklist( - /// *, - /// title: str, - /// items: Iterable[str], - /// active: int, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Checklist of backup steps. Active index is highlighted, previous items have check - /// mark next to them.""" - Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), - - /// def flow_continue_recovery( - /// *, - /// first_screen: bool, - /// recovery_type: RecoveryType, - /// text: str, - /// subtext: str | None = None, - /// pages: Iterable[tuple[str, str]] | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Device recovery homescreen.""" - Qstr::MP_QSTR_flow_continue_recovery => obj_fn_kw!(0, new_continue_recovery).as_obj(), - - /// def select_word_count( - /// *, - /// recovery_type: RecoveryType, - /// ) -> LayoutObj[int | str]: # merucry returns int - /// """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. - /// For unlocking a repeated backup, select from 20 or 33.""" - Qstr::MP_QSTR_select_word_count => obj_fn_kw!(0, new_select_word_count).as_obj(), - - /// def show_group_share_success( - /// *, - /// lines: Iterable[str] - /// ) -> LayoutObj[UiResult]: - /// """Shown after successfully finishing a group.""" - Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(), - - /// def show_progress( - /// *, - /// title: str, - /// indeterminate: bool = False, - /// description: str = "", - /// ) -> LayoutObj[UiResult]: - /// """Show progress loader. Please note that the number of lines reserved on screen for - /// description is determined at construction time. If you want multiline descriptions - /// make sure the initial description has at least that amount of lines.""" - Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(), - - /// def show_progress_coinjoin( - /// *, - /// title: str, - /// indeterminate: bool = False, - /// time_ms: int = 0, - /// skip_first_paint: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - /// time_ms timeout is passed.""" - Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(), - - /// def show_homescreen( - /// *, - /// label: str | None, - /// hold: bool, - /// notification: str | None, - /// notification_level: int = 0, - /// skip_first_paint: bool, - /// ) -> LayoutObj[UiResult]: - /// """Idle homescreen.""" - Qstr::MP_QSTR_show_homescreen => obj_fn_kw!(0, new_show_homescreen).as_obj(), - - /// def show_lockscreen( - /// *, - /// label: str | None, - /// bootscreen: bool, - /// skip_first_paint: bool, - /// coinjoin_authorized: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Homescreen for locked device.""" - Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), - - /// def confirm_firmware_update( - /// *, - /// description: str, - /// fingerprint: str, - /// ) -> LayoutObj[UiResult]: - /// """Ask whether to update firmware, optionally show fingerprint.""" - Qstr::MP_QSTR_confirm_firmware_update => obj_fn_kw!(0, new_confirm_firmware_update).as_obj(), - - /// def tutorial() -> LayoutObj[UiResult]: - /// """Show user how to interact with the device.""" - Qstr::MP_QSTR_tutorial => obj_fn_0!(new_show_tutorial).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 flow_get_address( - /// *, - /// address: str | bytes, - /// title: str, - /// description: str | None, - /// extra: str | None, - /// chunkify: bool, - /// address_qr: str | None, - /// case_sensitive: bool, - /// account: str | None, - /// path: str | None, - /// xpubs: list[tuple[str, str]], - /// title_success: str, - /// br_code: ButtonRequestType, - /// br_name: str, - /// ) -> LayoutObj[UiResult]: - /// """Get address / receive funds.""" - Qstr::MP_QSTR_flow_get_address => obj_fn_kw!(0, new_get_address).as_obj(), - - /// def flow_confirm_output( - /// *, - /// title: str | None, - /// subtitle: str | None, - /// message: str, - /// amount: str | None, - /// chunkify: bool, - /// text_mono: bool, - /// account: str | None, - /// account_path: str | None, - /// br_code: ButtonRequestType, - /// br_name: str, - /// address: str | None, - /// address_title: str | None, - /// summary_items: Iterable[tuple[str, str]] | None = None, - /// fee_items: Iterable[tuple[str, str]] | None = None, - /// summary_title: str | None = None, - /// summary_br_code: ButtonRequestType | None = None, - /// summary_br_name: str | None = None, - /// cancel_text: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page.""" - Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(), - - /// def confirm_summary( - /// *, - /// amount: str, - /// amount_label: str, - /// fee: str, - /// fee_label: str, - /// title: str | None = None, - /// account_items: Iterable[tuple[str, str]] | None = None, - /// extra_items: Iterable[tuple[str, str]] | None = None, - /// extra_title: str | None = None, - /// verb_cancel: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm summary of a transaction.""" - Qstr::MP_QSTR_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 e4bb653400..3f1d50520c 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 @@ -1,25 +1,914 @@ +use core::cmp::Ordering; + use crate::{ - error::Error, - micropython::gc::Gc, + error::{value_error, Error}, + io::BinaryData, + micropython::{gc::Gc, iter::IterBuf, list::List, obj::Obj, util}, strutil::TString, translations::TR, ui::{ component::{ + connect::Connect, swipe_detect::SwipeSettings, - text::paragraphs::{Paragraph, Paragraphs}, + text::{ + op::OpTextLayout, + paragraphs::{ + Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, + Paragraphs, VecExt, + }, + TextStyle, + }, + Border, CachedJpeg, ComponentExt, Empty, FormattedText, Never, Timeout, + }, + geometry::{self, Direction}, + layout::{ + obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, + util::{ConfirmBlob, PropsList, RecoveryType, StrOrBytes}, }, - geometry::Direction, - layout::obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, ui_features_fw::UIFeaturesFirmware, }, }; use super::{ - component::{Frame, SwipeContent, SwipeUpScreen}, + component::{ + check_homescreen_format, Bip39Input, CoinJoinProgress, Frame, Homescreen, Lockscreen, + MnemonicKeyboard, PinKeyboard, Progress, SelectWordCount, Slip39Input, StatusScreen, + SwipeContent, SwipeUpScreen, VerticalMenu, + }, + flow::{ + self, new_confirm_action_simple, ConfirmActionExtra, ConfirmActionMenuStrings, + ConfirmActionStrings, ConfirmBlobParams, ShowInfoParams, + }, theme, ModelMercuryFeatures, }; impl UIFeaturesFirmware for ModelMercuryFeatures { + fn confirm_action( + title: TString<'static>, + action: Option>, + description: Option>, + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + hold: bool, + hold_danger: bool, + reverse: bool, + prompt_screen: bool, + prompt_title: Option>, + ) -> Result { + let flow = flow::confirm_action::new_confirm_action( + title, + action, + description, + subtitle, + verb_cancel, + reverse, + hold, + prompt_screen, + prompt_title.unwrap_or(TString::empty()), + )?; + Ok(flow) + } + + fn confirm_address( + title: TString<'static>, + data: Obj, + description: Option>, + _extra: Option>, + _verb: Option>, + chunkify: bool, + ) -> Result { + // confirm_value is used instead + Err::, Error>(Error::ValueError( + c"confirm_address not implemented", + )) + } + + fn confirm_blob( + title: TString<'static>, + data: Obj, + description: Option>, + text_mono: bool, + extra: Option>, + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + verb_info: Option>, + info: bool, + hold: bool, + chunkify: bool, + page_counter: bool, + prompt_screen: bool, + cancel: bool, + ) -> Result, Error> { + ConfirmBlobParams::new(title, data, description) + .with_description_font(&theme::TEXT_SUB_GREY) + .with_text_mono(text_mono) + .with_subtitle(subtitle) + .with_verb(verb) + .with_verb_cancel(verb_cancel) + .with_verb_info(if info { + Some(verb_info.unwrap_or(TR::words__title_information.into())) + } else { + None + }) + .with_extra(extra) + .with_chunkify(chunkify) + .with_page_counter(page_counter) + .with_cancel(cancel) + .with_prompt(prompt_screen) + .with_hold(hold) + .into_flow() + .and_then(LayoutObj::new_root) + .map(Into::into) + } + + fn confirm_blob_intro( + title: TString<'static>, + data: Obj, + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + chunkify: bool, + ) -> Result, Error> { + const CONFIRM_BLOB_INTRO_MARGIN: usize = 24; + ConfirmBlobParams::new(title, data, Some(TR::instructions__view_all_data.into())) + .with_verb(verb) + .with_verb_info(Some(TR::buttons__view_all_data.into())) + .with_description_font(&theme::TEXT_SUB_GREEN_LIME) + .with_subtitle(subtitle) + .with_verb_cancel(verb_cancel) + .with_footer_description(Some( + TR::buttons__confirm.into(), /* or words__confirm?? */ + )) + .with_chunkify(chunkify) + .with_page_limit(Some(1)) + .with_frame_margin(CONFIRM_BLOB_INTRO_MARGIN) + .into_flow() + .and_then(LayoutObj::new_root) + .map(Into::into) + } + + fn confirm_homescreen( + title: TString<'static>, + image: BinaryData<'static>, + ) -> Result { + let layout = if image.is_empty() { + // Incoming data may be empty, meaning we should + // display default homescreen message. + let paragraphs = ParagraphVecShort::from_iter([Paragraph::new( + &theme::TEXT_DEMIBOLD, + TR::homescreen__set_default, + )]) + .into_paragraphs(); + + new_confirm_action_simple( + paragraphs, + ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), + ConfirmActionStrings::new( + TR::homescreen__settings_title.into(), + Some(TR::homescreen__settings_subtitle.into()), + None, + Some(TR::homescreen__settings_title.into()), + ), + false, + None, + 0, + false, + )? + } else { + if !check_homescreen_format(image) { + return Err(value_error!(c"Invalid image.")); + }; + + flow::confirm_homescreen::new_confirm_homescreen(title, CachedJpeg::new(image, 1))? + }; + Ok(layout) + } + + fn confirm_coinjoin( + max_rounds: TString<'static>, + max_feerate: TString<'static>, + ) -> Result { + let paragraphs = ParagraphVecShort::from_iter([ + Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_rounds), + Paragraph::new(&theme::TEXT_MONO, max_rounds), + Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_mining_fee), + Paragraph::new(&theme::TEXT_MONO, max_feerate), + ]) + .into_paragraphs(); + + let flow = flow::new_confirm_action_simple( + paragraphs, + ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), + ConfirmActionStrings::new( + TR::coinjoin__title.into(), + None, + None, + Some(TR::coinjoin__title.into()), + ), + true, + None, + 0, + false, + )?; + Ok(flow) + } + + fn confirm_emphasized( + title: TString<'static>, + items: Obj, + verb: Option>, + ) -> Result { + let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); + for item in IterBuf::new().try_iterate(items)? { + if item.is_str() { + ops = ops.text_normal(TString::try_from(item)?) + } else { + let [emphasis, text]: [Obj; 2] = util::iter_into_array(item)?; + let text: TString = text.try_into()?; + if emphasis.try_into()? { + ops = ops.text_demibold(text); + } else { + ops = ops.text_normal(text); + } + } + } + + let flow = flow::new_confirm_action_simple( + FormattedText::new(ops).vertically_centered(), + ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), + ConfirmActionStrings::new(title, None, None, Some(title)), + false, + None, + 0, + false, + )?; + Ok(flow) + } + + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, + ) -> Result { + #[cfg(feature = "universal_fw")] + return Ok(flow::confirm_fido::new_confirm_fido( + title, app_name, icon, accounts, + )?); + #[cfg(not(feature = "universal_fw"))] + Err::, Error>(Error::ValueError( + c"confirm_fido not used in bitcoin-only firmware", + )) + } + + fn confirm_firmware_update( + description: TString<'static>, + fingerprint: TString<'static>, + ) -> Result { + let flow = + flow::confirm_firmware_update::new_confirm_firmware_update(description, fingerprint)?; + Ok(flow) + } + + fn confirm_modify_fee( + title: TString<'static>, + sign: i32, + user_fee_change: TString<'static>, + total_fee_new: TString<'static>, + fee_rate_amount: Option>, + ) -> Result { + let (description, change, total_label) = match sign { + s if s < 0 => ( + TR::modify_fee__decrease_fee, + user_fee_change, + TR::modify_fee__new_transaction_fee, + ), + s if s > 0 => ( + TR::modify_fee__increase_fee, + user_fee_change, + TR::modify_fee__new_transaction_fee, + ), + _ => ( + TR::modify_fee__no_change, + "".into(), + TR::modify_fee__transaction_fee, + ), + }; + + let paragraphs = ParagraphVecShort::from_iter([ + Paragraph::new(&theme::TEXT_SUB_GREY, description), + Paragraph::new(&theme::TEXT_MONO, change), + Paragraph::new(&theme::TEXT_SUB_GREY, total_label), + Paragraph::new(&theme::TEXT_MONO, total_fee_new), + ]); + + let flow = flow::new_confirm_action_simple( + paragraphs.into_paragraphs(), + ConfirmActionExtra::Menu( + ConfirmActionMenuStrings::new() + .with_verb_info(Some(TR::words__title_information.into())), + ), + ConfirmActionStrings::new(title, None, None, Some(title)), + true, + None, + 0, + false, + )?; + Ok(flow) + } + + fn confirm_modify_output( + sign: i32, + amount_change: TString<'static>, + amount_new: TString<'static>, + ) -> Result { + let description = if sign < 0 { + TR::modify_amount__decrease_amount + } else { + TR::modify_amount__increase_amount + }; + + let paragraphs = ParagraphVecShort::from_iter([ + Paragraph::new(&theme::TEXT_SUB_GREY, description), + Paragraph::new(&theme::TEXT_MONO, amount_change), + Paragraph::new(&theme::TEXT_SUB_GREY, TR::modify_amount__new_amount), + Paragraph::new(&theme::TEXT_MONO, amount_new), + ]) + .into_paragraphs(); + + let layout = RootComponent::new(SwipeUpScreen::new( + Frame::left_aligned(TR::modify_amount__title.into(), paragraphs) + .with_cancel_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()), + )); + Ok(layout) + } + + fn confirm_more( + _title: TString<'static>, + _button: TString<'static>, + _button_style_confirm: bool, + _items: Obj, + ) -> Result { + Err::, Error>(Error::ValueError( + c"confirm_more not implemented", + )) + } + + fn confirm_reset_device(recovery: bool) -> Result { + let flow = flow::confirm_reset::new_confirm_reset(recovery)?; + Ok(flow) + } + + fn confirm_summary( + amount: TString<'static>, + amount_label: TString<'static>, + fee: TString<'static>, + fee_label: TString<'static>, + title: Option>, + account_items: Option, + extra_items: Option, + extra_title: Option>, + verb_cancel: Option>, + ) -> Result { + let mut summary_params = ShowInfoParams::new(title.unwrap_or(TString::empty())) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe_up(); + summary_params = unwrap!(summary_params.add(amount_label, amount)); + summary_params = unwrap!(summary_params.add(fee_label, fee)); + + // collect available info + let account_params = if let Some(items) = account_items { + let mut account_params = + ShowInfoParams::new(TR::send__send_from.into()).with_cancel_button(); + for pair in IterBuf::new().try_iterate(items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + account_params = unwrap!(account_params.add(label, value)); + } + Some(account_params) + } else { + None + }; + let extra_params = if let Some(items) = extra_items { + let extra_title = extra_title.unwrap_or(TR::buttons__more_info.into()); + let mut extra_params = ShowInfoParams::new(extra_title).with_cancel_button(); + for pair in IterBuf::new().try_iterate(items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + extra_params = unwrap!(extra_params.add(label, value)); + } + Some(extra_params) + } else { + None + }; + + let flow = flow::new_confirm_summary( + summary_params, + account_params, + extra_params, + extra_title, + verb_cancel, + )?; + Ok(flow) + } + + fn confirm_properties( + title: TString<'static>, + items: Obj, + hold: bool, + ) -> Result { + let paragraphs = PropsList::new( + items, + &theme::TEXT_NORMAL, + &theme::TEXT_MONO, + &theme::TEXT_MONO, + )?; + + let flow = flow::new_confirm_action_simple( + paragraphs.into_paragraphs(), + ConfirmActionExtra::Menu(ConfirmActionMenuStrings::new()), + ConfirmActionStrings::new(title, None, None, hold.then_some(title)), + hold, + None, + 0, + false, + )?; + Ok(flow) + } + + fn confirm_value( + title: TString<'static>, + value: Obj, + description: Option>, + subtitle: Option>, + verb: Option>, + verb_info: Option>, + verb_cancel: Option>, + info_button: bool, + hold: bool, + chunkify: bool, + text_mono: bool, + ) -> Result, Error> { + ConfirmBlobParams::new(title, value, description) + .with_subtitle(subtitle) + .with_verb(verb) + .with_verb_cancel(verb_cancel) + .with_verb_info(if info_button { + Some(verb_info.unwrap_or(TR::words__title_information.into())) + } else { + None + }) + .with_chunkify(chunkify) + .with_text_mono(text_mono) + .with_prompt(hold) + .with_hold(hold) + .into_flow() + .and_then(LayoutObj::new_root) + .map(Into::into) + } + + fn confirm_with_info( + title: TString<'static>, + button: TString<'static>, + info_button: TString<'static>, + verb_cancel: Option>, + items: Obj, + ) -> Result { + let mut paragraphs = ParagraphVecShort::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [font, text]: [Obj; 2] = util::iter_into_array(para)?; + let style: &TextStyle = theme::textstyle_number(font.try_into()?); + let text: TString = text.try_into()?; + paragraphs.add(Paragraph::new(style, text)); + if paragraphs.is_full() { + break; + } + } + + let flow = flow::new_confirm_action_simple( + paragraphs.into_paragraphs(), + ConfirmActionExtra::Menu( + ConfirmActionMenuStrings::new().with_verb_info(Some(info_button)), + ), + ConfirmActionStrings::new(title, None, None, None) + .with_footer_description(Some(button)), + false, + None, + 0, + false, + )?; + Ok(flow) + } + + fn check_homescreen_format(image: BinaryData, __accept_toif: bool) -> bool { + super::component::check_homescreen_format(image) + } + + fn continue_recovery_homepage( + text: TString<'static>, + subtext: Option>, + _button: Option>, + recovery_type: RecoveryType, + show_instructions: bool, + remaining_shares: Option, + ) -> Result, Error> { + let pages_vec = if let Some(pages_obj) = remaining_shares { + let mut vec = ParagraphVecLong::new(); + for page in crate::micropython::iter::IterBuf::new().try_iterate(pages_obj)? { + let [title, description]: [TString; 2] = util::iter_into_array(page)?; + vec.add(Paragraph::new(&theme::TEXT_SUB_GREY, title)) + .add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, description).break_after()); + } + Some(vec) + } else { + None + }; + + let flow = flow::continue_recovery_homepage::new_continue_recovery_homepage( + text, + subtext, + recovery_type, + show_instructions, + pages_vec, + )?; + LayoutObj::new_root(flow) + } + + fn flow_confirm_output( + title: Option>, + subtitle: Option>, + message: Obj, + amount: Option, + chunkify: bool, + text_mono: bool, + account: Option>, + account_path: Option>, + br_code: u16, + br_name: TString<'static>, + address: Option, + address_title: Option>, + summary_items: Option, + fee_items: Option, + summary_title: Option>, + summary_br_code: Option, + summary_br_name: Option>, + cancel_text: Option>, + ) -> Result { + let address_title = address_title.unwrap_or(TR::words__address.into()); + + let main_params = ConfirmBlobParams::new(title.unwrap_or(TString::empty()), message, None) + .with_subtitle(subtitle) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_chunkify(chunkify) + .with_text_mono(text_mono) + .with_swipe_up(); + + let content_amount_params = amount.map(|amount| { + ConfirmBlobParams::new(TR::words__amount.into(), amount, None) + .with_subtitle(subtitle) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_text_mono(text_mono) + .with_swipe_up() + .with_swipe_down() + }); + + let address_params = address.map(|address| { + ConfirmBlobParams::new(address_title, address, None) + .with_cancel_button() + .with_chunkify(true) + .with_text_mono(true) + .with_swipe_right() + }); + + let mut fee_items_params = + ShowInfoParams::new(TR::confirm_total__title_fee.into()).with_cancel_button(); + if fee_items.is_some() { + for pair in IterBuf::new().try_iterate(fee_items.unwrap())? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + fee_items_params = unwrap!(fee_items_params.add(label, value)); + } + } + + let summary_items_params: Option = if summary_items.is_some() { + let mut summary = + ShowInfoParams::new(summary_title.unwrap_or(TR::words__title_summary.into())) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe_up() + .with_swipe_down(); + for pair in IterBuf::new().try_iterate(summary_items.unwrap())? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + summary = unwrap!(summary.add(label, value)); + } + Some(summary) + } else { + None + }; + + let flow = flow::confirm_output::new_confirm_output( + main_params, + account, + account_path, + br_name, + br_code, + content_amount_params, + address_params, + address_title, + summary_items_params, + fee_items_params, + summary_br_name, + summary_br_code, + cancel_text, + )?; + Ok(flow) + } + + fn flow_confirm_set_new_pin( + title: TString<'static>, + description: TString<'static>, + ) -> Result { + let flow = flow::confirm_set_new_pin::new_set_new_pin(title, description)?; + Ok(flow) + } + + fn flow_get_address( + address: Obj, + title: TString<'static>, + description: Option>, + extra: Option>, + chunkify: bool, + address_qr: TString<'static>, + case_sensitive: bool, + account: Option>, + path: Option>, + xpubs: Obj, + title_success: TString<'static>, + br_code: u16, + br_name: TString<'static>, + ) -> Result { + let flow = flow::get_address::new_get_address( + title, + description, + extra, + address, + chunkify, + address_qr, + case_sensitive, + account, + path, + xpubs, + title_success, + br_code, + br_name, + )?; + Ok(flow) + } + + fn multiple_pages_texts( + _title: TString<'static>, + _verb: TString<'static>, + _items: Gc, + ) -> Result { + Err::, Error>(Error::ValueError( + c"multiple_pages_texts not implemented", + )) + } + + fn prompt_backup() -> Result { + let flow = flow::prompt_backup::new_prompt_backup()?; + Ok(flow) + } + + fn request_bip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result { + let layout = RootComponent::new(MnemonicKeyboard::new( + prefill_word.map(Bip39Input::prefilled_word), + prompt, + can_go_back, + )); + Ok(layout) + } + + fn request_slip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result { + let layout = RootComponent::new(MnemonicKeyboard::new( + prefill_word.map(Slip39Input::prefilled_word), + prompt, + can_go_back, + )); + + Ok(layout) + } + + fn request_number( + title: TString<'static>, + count: u32, + min_count: u32, + max_count: u32, + description: Option>, + more_info_callback: Option TString<'static> + 'static>, + ) -> Result { + debug_assert!( + description.is_some(), + "Description is required for request_number" + ); + debug_assert!( + more_info_callback.is_some(), + "More info callback is required for request_number" + ); + let flow = flow::request_number::new_request_number( + title, + count, + min_count, + max_count, + description.unwrap(), + more_info_callback.unwrap(), + )?; + Ok(flow) + } + + fn request_pin( + prompt: TString<'static>, + subprompt: TString<'static>, + allow_cancel: bool, + warning: bool, + ) -> Result { + let warning = if warning { + Some(TR::pin__wrong_pin.into()) + } else { + None + }; + + let layout = RootComponent::new(PinKeyboard::new(prompt, subprompt, warning, allow_cancel)); + Ok(layout) + } + + fn request_passphrase( + prompt: TString<'static>, + max_len: u32, + ) -> Result { + let flow = flow::request_passphrase::new_request_passphrase()?; + Ok(flow) + } + + fn select_word( + title: TString<'static>, + description: TString<'static>, + words: [TString<'static>; 3], + ) -> Result { + let content = VerticalMenu::select_word(words); + let layout = + RootComponent::new(Frame::left_aligned(title, content).with_subtitle(description)); + Ok(layout) + } + + fn select_word_count(recovery_type: RecoveryType) -> Result { + let content = if matches!(recovery_type, RecoveryType::UnlockRepeatedBackup) { + SelectWordCount::new_multishare() + } else { + SelectWordCount::new_all() + }; + let layout = RootComponent::new(Frame::left_aligned( + TR::recovery__num_of_words.into(), + content, + )); + Ok(layout) + } + + fn set_brightness(current_brightness: Option) -> Result { + let flow = flow::set_brightness::new_set_brightness(current_brightness)?; + Ok(flow) + } + + fn show_address_details( + _qr_title: TString<'static>, + _address: TString<'static>, + _case_sensitive: bool, + _details_title: TString<'static>, + _account: Option>, + _path: Option>, + _xpubs: Obj, + ) -> Result { + Err::, Error>(Error::ValueError( + c"show_address_details not implemented", + )) + } + + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + for (i, item) in items.into_iter().enumerate() { + let style = match i.cmp(&active) { + Ordering::Less => &theme::TEXT_CHECKLIST_DONE, + Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, + Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, + }; + paragraphs.add(Paragraph::new(style, item)); + } + + let checklist_content = Checklist::from_paragraphs( + theme::ICON_CHEVRON_RIGHT, + theme::ICON_BULLET_CHECKMARK, + active, + paragraphs + .into_paragraphs() + .with_spacing(theme::CHECKLIST_SPACING), + ) + .with_check_width(theme::CHECKLIST_CHECK_WIDTH) + .with_numerals() + .with_icon_done_color(theme::GREEN) + .with_done_offset(theme::CHECKLIST_DONE_OFFSET); + + let layout = RootComponent::new(SwipeUpScreen::new( + Frame::left_aligned(title, SwipeContent::new(checklist_content)) + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()), + )); + Ok(layout) + } + + fn show_danger( + title: TString<'static>, + description: TString<'static>, + value: TString<'static>, + verb_cancel: Option>, + ) -> Result { + let flow = flow::show_danger::new_show_danger(title, description, value, verb_cancel)?; + Ok(flow) + } + + 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 { + let paragraphs = ParagraphVecShort::from_iter([ + Paragraph::new(&theme::TEXT_NORMAL_GREY_EXTRA_LIGHT, lines[0]).centered(), + Paragraph::new(&theme::TEXT_DEMIBOLD, lines[1]).centered(), + Paragraph::new(&theme::TEXT_NORMAL_GREY_EXTRA_LIGHT, lines[2]).centered(), + Paragraph::new(&theme::TEXT_DEMIBOLD, lines[3]).centered(), + ]) + .into_paragraphs() + .with_placement(geometry::LinearPlacement::vertical().align_at_center()); + + let layout = RootComponent::new(SwipeUpScreen::new( + Frame::left_aligned("".into(), SwipeContent::new(paragraphs)) + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()), + )); + Ok(layout) + } + + fn show_homescreen( + label: TString<'static>, + hold: bool, + notification: Option>, + notification_level: u8, + ) -> Result { + let notification = notification.map(|w| (w, notification_level)); + let layout = RootComponent::new(Homescreen::new(label, notification, hold)); + Ok(layout) + } + fn show_info( title: TString<'static>, description: TString<'static>, @@ -34,4 +923,220 @@ impl UIFeaturesFirmware for ModelMercuryFeatures { ))?; Ok(obj) } + + fn show_info_with_cancel( + title: TString<'static>, + items: Obj, + _horizontal: bool, + chunkify: bool, + ) -> Result { + let mut paragraphs = ParagraphVecShort::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [key, value]: [Obj; 2] = util::iter_into_array(para)?; + let key: TString = key.try_into()?; + let value: TString = value.try_into()?; + paragraphs.add(Paragraph::new(&theme::TEXT_SUB_GREY, key).no_break()); + if chunkify { + paragraphs.add(Paragraph::new( + theme::get_chunkified_text_style(value.len()), + value, + )); + } else { + paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); + } + } + + let layout = RootComponent::new(SwipeUpScreen::new( + Frame::left_aligned(title, SwipeContent::new(paragraphs.into_paragraphs())) + .with_cancel_button(), + )); + Ok(layout) + } + + fn show_lockscreen( + label: TString<'static>, + bootscreen: bool, + coinjoin_authorized: bool, + ) -> Result { + let layout = RootComponent::new(Lockscreen::new(label, bootscreen, coinjoin_authorized)); + Ok(layout) + } + + fn show_mismatch(title: TString<'static>) -> Result { + let description: TString = TR::addr_mismatch__contact_support_at.into(); + let url: TString = TR::addr_mismatch__support_url.into(); + let button: TString = TR::buttons__quit.into(); + + let paragraphs = ParagraphVecShort::from_iter([ + Paragraph::new(&theme::TEXT_NORMAL, description).centered(), + Paragraph::new(&theme::TEXT_DEMIBOLD, url).centered(), + ]) + .into_paragraphs(); + + let layout = RootComponent::new(SwipeUpScreen::new( + Frame::left_aligned(title, SwipeContent::new(paragraphs)) + .with_cancel_button() + .with_footer(TR::instructions__swipe_up.into(), Some(button)) + .with_swipe(Direction::Up, SwipeSettings::default()), + )); + + Ok(layout) + } + + fn show_progress( + description: TString<'static>, + indeterminate: bool, + title: Option>, + ) -> Result { + let (title, description) = if let Some(title) = title { + (title, description) + } else { + (description, "".into()) + }; + + let layout = RootComponent::new(Progress::new(title, indeterminate, description)); + Ok(layout) + } + + fn show_progress_coinjoin( + title: TString<'static>, + indeterminate: bool, + time_ms: u32, + skip_first_paint: bool, + ) -> Result, Error> { + let progress = CoinJoinProgress::::new(title, indeterminate)?; + let obj = if time_ms > 0 && indeterminate { + let timeout = Timeout::new(time_ms); + LayoutObj::new((timeout, progress.map(|_msg| None)))? + } else { + LayoutObj::new(progress)? + }; + if skip_first_paint { + obj.skip_first_paint(); + } + Ok(obj) + } + + fn show_share_words( + words: heapless::Vec, 33>, + _title: Option>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"use share_words_mercury instead", + )) + } + + fn show_share_words_mercury( + words: heapless::Vec, 33>, + subtitle: Option>, + instructions: crate::micropython::obj::Obj, + text_footer: Option>, + text_confirm: TString<'static>, + ) -> Result { + let mut instructions_paragraphs = ParagraphVecShort::new(); + for item in crate::micropython::iter::IterBuf::new().try_iterate(instructions)? { + let text: TString = item.try_into()?; + instructions_paragraphs.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text)); + } + + let flow = flow::show_share_words::new_show_share_words( + words, + subtitle.unwrap_or(TString::empty()), + instructions_paragraphs, + text_footer, + text_confirm, + )?; + Ok(flow) + } + + fn show_remaining_shares( + pages_iterable: crate::micropython::obj::Obj, // TODO: replace Obj + ) -> Result { + // Mercury: remaining shares is a part of `continue_recovery` flow + Err::, Error>(Error::ValueError( + c"show_remaining_shares not implemented", + )) + } + + fn show_simple( + text: TString<'static>, + _title: Option>, + _button: Option>, + ) -> Result, Error> { + let obj = LayoutObj::new(Border::new( + theme::borders(), + Paragraphs::new(Paragraph::new(&theme::TEXT_DEMIBOLD, text)), + ))?; + Ok(obj) + } + + 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/component/share_words.rs b/core/embed/rust/src/ui/model_tr/component/share_words.rs index fe8d6ea6e2..77687cf7e6 100644 --- a/core/embed/rust/src/ui/model_tr/component/share_words.rs +++ b/core/embed/rust/src/ui/model_tr/component/share_words.rs @@ -92,23 +92,24 @@ impl<'a> ShareWords<'a> { fn render_words<'s>(&'s self, target: &mut impl Renderer<'s>) { let mut y_offset = 0; // Showing the word index and the words itself - for i in 0..WORDS_PER_PAGE { + for (word_idx, word) in self + .share_words + .iter() + .enumerate() + .skip(self.page_index * WORDS_PER_PAGE) + .take(WORDS_PER_PAGE) + { + let ordinal = word_idx + 1; y_offset += NUMBER_FONT.line_height() + EXTRA_LINE_HEIGHT; - let index = self.word_index() + i; - if index >= self.share_words.len() { - break; - } - let word = &self.share_words[index]; - let baseline = self.area.top_left() + Offset::y(y_offset); - let ordinal = uformat!("{}.", index + 1); + let base = self.area.top_left() + Offset::y(y_offset); - shape::Text::new(baseline + Offset::x(NUMBER_X_OFFSET), &ordinal) + let ordinal_txt = uformat!("{}.", ordinal); + shape::Text::new(base + Offset::x(NUMBER_X_OFFSET), &ordinal_txt) .with_font(NUMBER_FONT) .with_fg(theme::FG) .render(target); - word.map(|w| { - shape::Text::new(baseline + Offset::x(WORD_X_OFFSET), w) + shape::Text::new(base + Offset::x(WORD_X_OFFSET), w) .with_font(WORD_FONT) .with_fg(theme::FG) .render(target); @@ -170,13 +171,15 @@ impl<'a> crate::trace::Trace for ShareWords<'a> { self.get_final_text() } else { let mut content = ShortString::new(); - for i in 0..WORDS_PER_PAGE { - let index = self.word_index() + i; - if index >= self.share_words.len() { - break; - } - self.share_words[index] - .map(|word| unwrap!(uwrite!(content, "{}. {}\n", index + 1, word))); + for (word_idx, word) in self + .share_words + .iter() + .enumerate() + .skip(self.page_index * WORDS_PER_PAGE) + .take(WORDS_PER_PAGE) + { + let ordinal = word_idx + 1; + word.map(|w| unwrap!(uwrite!(content, "{}. {}\n", ordinal, w))); } content }; diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index d15b35af76..c59af4b2f5 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -49,9 +49,8 @@ use crate::{ layout::{ obj::{ComponentMsgObj, LayoutObj}, result::{CANCELLED, CONFIRMED, INFO}, - util::{upy_disable_animation, ConfirmBlob, RecoveryType}, + util::{ConfirmBlob, RecoveryType}, }, - model_tr::component::check_homescreen_format, }, }; @@ -271,1335 +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()?; - let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let extra: Option = kwargs - .get(Qstr::MP_QSTR_extra) - .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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - let style = if chunkify { - // Chunkifying the address into smaller pieces when requested - &theme::TEXT_MONO_ADDRESS_CHUNKS - } else { - &theme::TEXT_MONO_DATA - }; - - let paragraphs = ConfirmBlob { - description: description.unwrap_or("".into()), - extra: extra.unwrap_or("".into()), - data: data.try_into()?, - description_font: &theme::TEXT_BOLD, - extra_font: &theme::TEXT_NORMAL, - data_font: style, - } - .into_paragraphs(); - - content_in_button_page( - title, - paragraphs, - verb.unwrap_or(TR::buttons__confirm.into()), - verb_cancel, - hold, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_properties(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 hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [key, value, is_data]: [Obj; 3] = util::iter_into_array(para)?; - let key = key.try_into_option::()?; - let value = value.try_into_option::()?; - let is_data: bool = is_data.try_into()?; - - if let Some(key) = key { - if value.is_some() { - // Decreasing the margin between key and value (default is 5 px, we use 2 px) - // (this enables 4 lines - 2 key:value pairs - on the same screen) - paragraphs.add( - Paragraph::new(&theme::TEXT_BOLD, key) - .no_break() - .with_bottom_padding(2), - ); - } else { - paragraphs.add(Paragraph::new(&theme::TEXT_BOLD, key)); - } - } - if let Some(value) = value { - let style = if is_data { - &theme::TEXT_MONO_DATA - } else { - &theme::TEXT_MONO - }; - paragraphs.add(Paragraph::new(style, value)); - } - } - let button_text = if hold { - TR::buttons__hold_to_confirm.into() - } else { - TR::buttons__confirm.into() - }; - - content_in_button_page( - title, - paragraphs.into_paragraphs(), - button_text, - Some("".into()), - hold, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_homescreen(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 image: Obj = kwargs.get(Qstr::MP_QSTR_image)?; - let obj = LayoutObj::new(ConfirmHomescreen::new(title, image.try_into()?))?; - Ok(obj.into()) - }; - - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_reset_device(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<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - - let ops = OpTextLayout::new(theme::TEXT_NORMAL) - .text_normal(TR::reset__by_continuing) - .next_page() - .text_normal(TR::reset__more_info_at) - .newline() - .text_bold(TR::reset__tos_link); - let formatted = FormattedText::new(ops).vertically_centered(); - - content_in_button_page(title, formatted, button, Some("".into()), false) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_backup(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], _kwargs: &Map| { - let get_page = move |page_index| match page_index { - 0 => { - let btn_layout = ButtonLayout::text_none_arrow_wide(TR::buttons__skip.into()); - let btn_actions = ButtonActions::cancel_none_next(); - let ops = OpTextLayout::new(theme::TEXT_NORMAL) - .text_normal(TR::backup__new_wallet_created) - .newline() - .text_normal(TR::backup__it_should_be_backed_up_now); - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted) - .with_title(TR::words__title_success.into()) - } - 1 => { - let btn_layout = ButtonLayout::up_arrow_none_text(TR::buttons__back_up.into()); - let btn_actions = ButtonActions::prev_none_confirm(); - let ops = - OpTextLayout::new(theme::TEXT_NORMAL).text_normal(TR::backup__recover_anytime); - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted) - .with_title(TR::backup__title_backup_wallet.into()) - } - _ => unreachable!(), - }; - let pages = FlowPages::new(get_page, 2); - - 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_address_details(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let address: TString = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?; - let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?; - let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; - let path: Option = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?; - - let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?; - - let mut ad = AddressDetails::new(address, case_sensitive, account, path)?; - - for i in IterBuf::new().try_iterate(xpubs)? { - let [xtitle, text]: [TString; 2] = util::iter_into_array(i)?; - ad.add_xpub(xtitle, text)?; - } - - let obj = LayoutObj::new(ad)?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_value(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 value: TString = kwargs.get(Qstr::MP_QSTR_value)?.try_into()?; - - let verb: Option> = kwargs - .get(Qstr::MP_QSTR_verb) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_BOLD, description), - Paragraph::new(&theme::TEXT_MONO, value), - ]); - - content_in_button_page( - title, - paragraphs, - verb.unwrap_or(TR::buttons__confirm.into()), - Some("".into()), - hold, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_joint_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let spending_amount: TString = kwargs.get(Qstr::MP_QSTR_spending_amount)?.try_into()?; - let total_amount: TString = kwargs.get(Qstr::MP_QSTR_total_amount)?.try_into()?; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_BOLD, TR::joint__you_are_contributing), - Paragraph::new(&theme::TEXT_MONO, spending_amount), - Paragraph::new(&theme::TEXT_BOLD, TR::joint__to_the_total_amount), - Paragraph::new(&theme::TEXT_MONO, total_amount), - ]); - - content_in_button_page( - TR::joint__title.into(), - paragraphs, - TR::buttons__hold_to_confirm.into(), - Some("".into()), - true, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; - let amount_change: TString = kwargs.get(Qstr::MP_QSTR_amount_change)?.try_into()?; - let amount_new: TString = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?; - - let description = if sign < 0 { - TR::modify_amount__decrease_amount - } else { - TR::modify_amount__increase_amount - }; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, description), - Paragraph::new(&theme::TEXT_MONO, amount_change).break_after(), - Paragraph::new(&theme::TEXT_BOLD, TR::modify_amount__new_amount), - Paragraph::new(&theme::TEXT_MONO, amount_new), - ]); - - content_in_button_page( - TR::modify_amount__title.into(), - paragraphs, - TR::buttons__confirm.into(), - Some("".into()), - false, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_output_address(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let address: TString = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?; - let address_label: TString = kwargs.get(Qstr::MP_QSTR_address_label)?.try_into()?; - let address_title: TString = kwargs.get(Qstr::MP_QSTR_address_title)?.try_into()?; - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - let get_page = move |page_index| { - assert!(page_index == 0); - // RECIPIENT + address - let btn_layout = ButtonLayout::cancel_none_text(TR::buttons__continue.into()); - let btn_actions = ButtonActions::cancel_none_confirm(); - // Not putting hyphens in the address. - // Potentially adding address label in different font. - let mut ops = OpTextLayout::new(theme::TEXT_MONO_DATA); - if !address_label.is_empty() { - // NOTE: need to explicitly turn off the chunkification before rendering the - // address label (for some reason it does not help to turn it off after - // rendering the chunks) - if chunkify { - ops = ops.chunkify_text(None); - } - ops = ops.text_normal(address_label).newline(); - } - if chunkify { - // Chunkifying the address into smaller pieces when requested - ops = ops.chunkify_text(Some((theme::MONO_CHUNKS, 2))); - } - ops = ops.text_mono(address); - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted).with_title(address_title) - }; - 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_confirm_output_amount(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let amount: TString = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?; - let amount_title: TString = kwargs.get(Qstr::MP_QSTR_amount_title)?.try_into()?; - - let get_page = move |page_index| { - assert!(page_index == 0); - // AMOUNT + amount - let btn_layout = ButtonLayout::up_arrow_none_text(TR::buttons__confirm.into()); - let btn_actions = ButtonActions::cancel_none_confirm(); - let ops = OpTextLayout::new(theme::TEXT_MONO).text_mono(amount); - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted).with_title(amount_title) - }; - 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_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let amount: TString = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?; - let amount_label: TString = kwargs.get(Qstr::MP_QSTR_amount_label)?.try_into()?; - let fee: TString = kwargs.get(Qstr::MP_QSTR_fee)?.try_into()?; - let fee_label: TString = kwargs.get(Qstr::MP_QSTR_fee_label)?.try_into()?; - let _title: Option = kwargs - .get(Qstr::MP_QSTR_title) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let account_items: Option = kwargs - .get(Qstr::MP_QSTR_account_items) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let extra_items: Option = kwargs - .get(Qstr::MP_QSTR_extra_items) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let extra_title: Option = kwargs - .get(Qstr::MP_QSTR_extra_title) - .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()?; - - // collect available info pages - let mut info_pages: Vec<(TString, Obj), 2> = Vec::new(); - if let Some(info) = extra_items { - // put extra items first as it's typically used for fee info - let extra_title = extra_title.unwrap_or(TR::words__title_information.into()); - unwrap!(info_pages.push((extra_title, info))); - } - if let Some(info) = account_items { - unwrap!(info_pages.push((TR::confirm_total__title_sending_from.into(), info))); - } - - // button layouts and actions - let verb_cancel: TString = verb_cancel.unwrap_or(TString::empty()); - let btns_summary_page = move |has_pages_after: bool| -> (ButtonLayout, ButtonActions) { - // if there are no info pages, the right button is not needed - // if verb_cancel is "^", the left button is an arrow pointing up - let left_btn = Some(ButtonDetails::from_text_possible_icon(verb_cancel)); - let right_btn = has_pages_after.then(|| { - ButtonDetails::text("i".into()) - .with_fixed_width(theme::BUTTON_ICON_WIDTH) - .with_font(Font::NORMAL) - }); - let middle_btn = Some(ButtonDetails::armed_text(TR::buttons__confirm.into())); - - ( - ButtonLayout::new(left_btn, middle_btn, right_btn), - if has_pages_after { - ButtonActions::cancel_confirm_next() - } else { - ButtonActions::cancel_confirm_none() - }, - ) - }; - let btns_info_page = |is_last: bool| -> (ButtonLayout, ButtonActions) { - // on the last info page, the right button is not needed - if is_last { - ( - ButtonLayout::arrow_none_none(), - ButtonActions::prev_none_none(), - ) - } else { - ( - ButtonLayout::arrow_none_arrow(), - ButtonActions::prev_none_next(), - ) - } - }; - - let total_pages = 1 + info_pages.len(); - let get_page = move |page_index| { - match page_index { - 0 => { - // Total amount + fee - let (btn_layout, btn_actions) = btns_summary_page(!info_pages.is_empty()); - - let ops = OpTextLayout::new(theme::TEXT_MONO) - .text_bold(amount_label) - .newline() - .text_mono(amount) - .newline() - .newline() - .text_bold(fee_label) - .newline() - .text_mono(fee); - - let formatted = FormattedText::new(ops); - Page::new(btn_layout, btn_actions, formatted) - } - i => { - // Other info pages as provided - let (title, info_obj) = &info_pages[i - 1]; - let is_last = i == total_pages - 1; - let (btn_layout, btn_actions) = btns_info_page(is_last); - - let mut ops = OpTextLayout::new(theme::TEXT_MONO); - for item in unwrap!(IterBuf::new().try_iterate(*info_obj)) { - let [key, value]: [Obj; 2] = unwrap!(util::iter_into_array(item)); - if !ops.is_empty() { - // Each key-value pair is on its own page - ops = ops.next_page(); - } - ops = ops - .text_bold(unwrap!(TString::try_from(key))) - .newline() - .text_mono(unwrap!(TString::try_from(value))); - } - - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted) - .with_slim_arrows() - .with_title(*title) - } - } - }; - let pages = FlowPages::new(get_page, total_pages); - - let obj = LayoutObj::new(Flow::new(pages).with_scrollbar(false))?; - Ok(obj.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()?; - let address: TString = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?; - let verb: TString<'static> = - kwargs.get_or(Qstr::MP_QSTR_verb, TR::buttons__confirm.into())?; - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - let get_page = move |page_index| { - assert!(page_index == 0); - - let btn_layout = ButtonLayout::cancel_armed_info(verb); - let btn_actions = ButtonActions::cancel_confirm_info(); - let style = if chunkify { - // Chunkifying the address into smaller pieces when requested - theme::TEXT_MONO_ADDRESS_CHUNKS - } else { - theme::TEXT_MONO_DATA - }; - let ops = OpTextLayout::new(style).text_mono(address); - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted).with_title(title) - }; - 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) } -} - -/// General pattern of most tutorial screens. -/// (title, text, btn_layout, btn_actions, text_y_offset) -fn tutorial_screen( - title: TString<'static>, - text: TR, - btn_layout: ButtonLayout, - btn_actions: ButtonActions, -) -> Page { - let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text); - let formatted = FormattedText::new(ops).vertically_centered(); - Page::new(btn_layout, btn_actions, formatted).with_title(title) -} - -extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], _kwargs: &Map| { - const PAGE_COUNT: usize = 7; - - let get_page = move |page_index| { - // Lazy-loaded list of screens to show, with custom content, - // buttons and actions triggered by these buttons. - // Cancelling the first screen will point to the last one, - // which asks for confirmation whether user wants to - // really cancel the tutorial. - match page_index { - // title, text, btn_layout, btn_actions - 0 => tutorial_screen( - TR::tutorial__title_hello.into(), - TR::tutorial__welcome_press_right, - ButtonLayout::cancel_none_arrow(), - ButtonActions::last_none_next(), - ), - 1 => tutorial_screen( - "".into(), - TR::tutorial__use_trezor, - ButtonLayout::arrow_none_arrow(), - ButtonActions::prev_none_next(), - ), - 2 => tutorial_screen( - TR::buttons__hold_to_confirm.into(), - TR::tutorial__press_and_hold, - ButtonLayout::arrow_none_htc(TR::buttons__hold_to_confirm.into()), - ButtonActions::prev_none_next(), - ), - 3 => tutorial_screen( - TR::tutorial__title_screen_scroll.into(), - TR::tutorial__scroll_down, - ButtonLayout::arrow_none_text(TR::buttons__continue.into()), - ButtonActions::prev_none_next(), - ), - 4 => tutorial_screen( - TR::buttons__confirm.into(), - TR::tutorial__middle_click, - ButtonLayout::none_armed_none(TR::buttons__confirm.into()), - ButtonActions::none_next_none(), - ), - 5 => tutorial_screen( - TR::tutorial__title_tutorial_complete.into(), - TR::tutorial__ready_to_use, - ButtonLayout::text_none_text( - TR::buttons__again.into(), - TR::buttons__continue.into(), - ), - ButtonActions::beginning_none_confirm(), - ), - 6 => tutorial_screen( - TR::tutorial__title_skip.into(), - TR::tutorial__sure_you_want_skip, - ButtonLayout::arrow_none_text(TR::buttons__skip.into()), - ButtonActions::beginning_none_cancel(), - ), - _ => unreachable!(), - } - }; - - let pages = FlowPages::new(get_page, PAGE_COUNT); - - // Setting the ignore-second-button to mimic all the Choice pages, to teach user - // that they should really press both buttons at the same time to achieve - // middle-click. - let obj = LayoutObj::new( - Flow::new(pages) - .with_scrollbar(false) - .with_ignore_second_button_ms(constant::IGNORE_OTHER_BTN_MS), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; - let user_fee_change: TString = kwargs.get(Qstr::MP_QSTR_user_fee_change)?.try_into()?; - let total_fee_new: TString = kwargs.get(Qstr::MP_QSTR_total_fee_new)?.try_into()?; - let fee_rate_amount: Option = kwargs - .get(Qstr::MP_QSTR_fee_rate_amount)? - .try_into_option()?; - - let (description, change) = match sign { - s if s < 0 => (TR::modify_fee__decrease_fee, user_fee_change), - s if s > 0 => (TR::modify_fee__increase_fee, user_fee_change), - _ => (TR::modify_fee__no_change, "".into()), - }; - - let mut paragraphs_vec = ParagraphVecShort::new(); - paragraphs_vec - .add(Paragraph::new(&theme::TEXT_BOLD, description)) - .add(Paragraph::new(&theme::TEXT_MONO, change)) - .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__transaction_fee).no_break()) - .add(Paragraph::new(&theme::TEXT_MONO, total_fee_new)); - - if let Some(fee_rate_amount) = fee_rate_amount { - paragraphs_vec - .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__fee_rate).no_break()) - .add(Paragraph::new(&theme::TEXT_MONO, fee_rate_amount)); - } - - content_in_button_page( - TR::modify_fee__title.into(), - paragraphs_vec.into_paragraphs(), - TR::buttons__confirm.into(), - Some("".into()), - false, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_multiple_pages_texts(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 verb: TString = kwargs.get(Qstr::MP_QSTR_verb)?.try_into()?; - let items: Gc = kwargs.get(Qstr::MP_QSTR_items)?.try_into()?; - - // Cache the page count so that we can move `items` into the closure. - let page_count = items.len(); - - // Closure to lazy-load the information on given page index. - // Done like this to allow arbitrarily many pages without - // the need of any allocation here in Rust. - let get_page = move |page_index| { - let item_obj = unwrap!(items.get(page_index)); - let text = unwrap!(TString::try_from(item_obj)); - - let (btn_layout, btn_actions) = if page_count == 1 { - // There is only one page - ( - ButtonLayout::cancel_none_text(verb), - ButtonActions::cancel_none_confirm(), - ) - } else if page_index == 0 { - // First page - ( - ButtonLayout::cancel_none_arrow_wide(), - ButtonActions::cancel_none_next(), - ) - } else if page_index == page_count - 1 { - // Last page - ( - ButtonLayout::up_arrow_none_text(verb), - ButtonActions::prev_none_confirm(), - ) - } else { - // Page in the middle - ( - ButtonLayout::up_arrow_none_arrow_wide(), - ButtonActions::prev_none_next(), - ) - }; - - let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text); - let formatted = FormattedText::new(ops).vertically_centered(); - - Page::new(btn_layout, btn_actions, formatted) - }; - - let pages = FlowPages::new(get_page, page_count); - let obj = LayoutObj::new(Flow::new(pages).with_common_title(title))?; - Ok(obj.into()) - }; - 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()?; - let app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; - let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; - - // Cache the page count so that we can move `accounts` into the closure. - let page_count = accounts.len(); - - // Closure to lazy-load the information on given page index. - // Done like this to allow arbitrarily many pages without - // the need of any allocation here in Rust. - let get_page = move |page_index| { - let account_obj = unwrap!(accounts.get(page_index)); - let account = TString::try_from(account_obj).unwrap_or_else(|_| TString::empty()); - - let (btn_layout, btn_actions) = if page_count == 1 { - // There is only one page - ( - ButtonLayout::cancel_none_text(TR::buttons__confirm.into()), - ButtonActions::cancel_none_confirm(), - ) - } else if page_index == 0 { - // First page - ( - ButtonLayout::cancel_armed_arrow(TR::buttons__select.into()), - ButtonActions::cancel_confirm_next(), - ) - } else if page_index == page_count - 1 { - // Last page - ( - ButtonLayout::arrow_armed_none(TR::buttons__select.into()), - ButtonActions::prev_confirm_none(), - ) - } else { - // Page in the middle - ( - ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()), - ButtonActions::prev_confirm_next(), - ) - }; - - let ops = OpTextLayout::new(theme::TEXT_NORMAL) - .newline() - .text_normal(app_name) - .newline() - .text_bold(account); - let formatted = FormattedText::new(ops); - - Page::new(btn_layout, btn_actions, formatted) - }; - - let pages = FlowPages::new(get_page, page_count); - // Returning the page index in case of confirmation. - let obj = LayoutObj::new( - Flow::new(pages) - .with_common_title(title) - .with_return_confirmed_index(), - )?; - Ok(obj.into()) - }; - 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(); - let paragraph = Paragraph::new(&theme::TEXT_NORMAL, text).centered(); - let content = Paragraphs::new([paragraph]); - let obj = LayoutObj::new(content)?; - Ok(obj.into()) - }; - unsafe { util::try_or_raise(block) } -} - -extern "C" fn new_show_waiting_text(message: Obj) -> Obj { - let block = || { - let text: TString = message.try_into()?; - let paragraph = Paragraph::new(&theme::TEXT_NORMAL, text).centered(); - let content = Paragraphs::new([paragraph]); - let obj = LayoutObj::new(content)?; - Ok(obj.into()) - }; - unsafe { util::try_or_raise(block) } -} - -extern "C" fn new_show_mismatch(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 get_page = move |page_index| { - assert!(page_index == 0); - - let btn_layout = ButtonLayout::arrow_none_text(TR::buttons__quit.into()); - let btn_actions = ButtonActions::cancel_none_confirm(); - let ops = OpTextLayout::new(theme::TEXT_NORMAL) - .text_bold_upper(title) - .newline() - .newline_half() - .text_normal(TR::addr_mismatch__contact_support_at) - .newline() - .text_bold(TR::addr_mismatch__support_url); - let formatted = FormattedText::new(ops); - 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_confirm_with_info(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<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let verb_cancel: Option> = kwargs - .get(Qstr::MP_QSTR_verb_cancel) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecShort::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [font, text]: [Obj; 2] = util::iter_into_array(para)?; - let style: &TextStyle = theme::textstyle_number(font.try_into()?); - let text: TString = text.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - if paragraphs.is_full() { - break; - } - } - - let obj = LayoutObj::new(Frame::new( - title, - ShowMore::>::new( - paragraphs.into_paragraphs(), - verb_cancel, - button, - ), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_more(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<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [font, text]: [Obj; 2] = util::iter_into_array(para)?; - let style: &TextStyle = theme::textstyle_number(font.try_into()?); - let text: TString = text.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - content_in_button_page( - title, - paragraphs.into_paragraphs(), - button, - Some("<".into()), - false, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let max_rounds: TString = kwargs.get(Qstr::MP_QSTR_max_rounds)?.try_into()?; - let max_feerate: TString = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?; - - // Decreasing bottom padding between paragraphs to fit one screen - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_rounds).with_bottom_padding(2), - Paragraph::new(&theme::TEXT_MONO, max_rounds), - Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_mining_fee) - .with_bottom_padding(2) - .no_break(), - Paragraph::new(&theme::TEXT_MONO, max_feerate).with_bottom_padding(2), - ]); - - content_in_button_page( - TR::coinjoin__title.into(), - paragraphs, - TR::buttons__hold_to_confirm.into(), - None, - true, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; - let subprompt: TString = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?; - - let obj = LayoutObj::new(PinEntry::new(prompt, subprompt))?; - - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; - - let obj = LayoutObj::new(Frame::new(prompt, PassphraseEntry::new()).with_title_centered())?; - Ok(obj.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 = |_args: &[Obj], kwargs: &Map| { - let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; - let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; - let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; - - let obj = LayoutObj::new( - Frame::new( - prompt, - prefill_word - .map(|s| WordlistEntry::prefilled_word(s, WordlistType::Bip39, can_go_back)), - ) - .with_title_centered(), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_slip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; - let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; - let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; - - let obj = LayoutObj::new( - Frame::new( - prompt, - prefill_word - .map(|s| WordlistEntry::prefilled_word(s, WordlistType::Slip39, can_go_back)), - ) - .with_title_centered(), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - // we ignore passed in `title` and use `description` in its place - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?; - // There are only 3 words, but SimpleChoice requires 5 elements - let words: Vec, 5> = util::iter_into_vec(words_iterable)?; - - // Returning the index of the selected word, not the word itself - let obj = LayoutObj::new( - Frame::new( - description, - SimpleChoice::new(words, false) - .with_show_incomplete() - .with_return_index(), - ) - .with_title_centered(), - )?; - Ok(obj.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 = |_args: &[Obj], kwargs: &Map| { - let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?; - let share_words: Vec = util::iter_into_vec(share_words_obj)?; - - let cancel_btn = Some(ButtonDetails::up_arrow_icon()); - let confirm_btn = - Some(ButtonDetails::text(TR::buttons__hold_to_confirm.into()).with_default_duration()); - - let obj = LayoutObj::new( - ButtonPage::new(ShareWords::new(share_words), theme::BG) - .with_cancel_btn(cancel_btn) - .with_confirm_btn(confirm_btn), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_number(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 min_count: u32 = kwargs.get(Qstr::MP_QSTR_min_count)?.try_into()?; - let max_count: u32 = kwargs.get(Qstr::MP_QSTR_max_count)?.try_into()?; - let count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?; - - let obj = LayoutObj::new( - Frame::new(title, NumberInput::new(min_count, max_count, count)).with_title_centered(), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_checklist(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 active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() { - let style = match i.cmp(&active) { - Ordering::Less => &theme::TEXT_NORMAL, - Ordering::Equal => &theme::TEXT_BOLD, - Ordering::Greater => &theme::TEXT_NORMAL, - }; - let text: TString = item.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - let confirm_btn = Some(ButtonDetails::text(button)); - - let obj = LayoutObj::new( - ButtonPage::new( - Checklist::from_paragraphs( - theme::ICON_ARROW_RIGHT_FAT, - theme::ICON_TICK_FAT, - active, - paragraphs - .into_paragraphs() - .with_spacing(theme::CHECKLIST_SPACING), - ) - .with_check_width(theme::CHECKLIST_CHECK_WIDTH) - .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET), - theme::BG, - ) - .with_confirm_btn(confirm_btn), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; - let show_instructions: bool = kwargs.get(Qstr::MP_QSTR_show_instructions)?.try_into()?; - - let mut paragraphs = ParagraphVecShort::new(); - paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, description)); - if show_instructions { - paragraphs - .add(Paragraph::new( - &theme::TEXT_NORMAL, - TR::recovery__enter_each_word, - )) - .add(Paragraph::new( - &theme::TEXT_NORMAL, - TR::recovery__cursor_will_change, - )); - } - - let title = match recovery_type { - RecoveryType::DryRun => TR::recovery__title_dry_run, - RecoveryType::UnlockRepeatedBackup => TR::recovery__title_dry_run, - _ => TR::recovery__title, - }; - - content_in_button_page( - title.into(), - paragraphs.into_paragraphs(), - button, - Some("".into()), - false, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = |_args: &[Obj], kwargs: &Map| { - let title: TString = TR::word_count__title.into(); - let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; - - let choices: Vec, 5> = { - let nums: &[&str] = if matches!(recovery_type, RecoveryType::UnlockRepeatedBackup) { - &["20", "33"] - } else { - &["12", "18", "20", "24", "33"] - }; - - nums.iter().map(|&num| num.into()).collect() - }; - - let obj = LayoutObj::new( - Frame::new(title, SimpleChoice::new(choices, false)).with_title_centered(), - )?; - Ok(obj.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, - kwargs: *mut Map, -) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let lines_iterable: Obj = kwargs.get(Qstr::MP_QSTR_lines)?; - let lines: [TString; 4] = util::iter_into_array(lines_iterable)?; - - let [l0, l1, l2, l3] = lines; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_MONO, l0), - Paragraph::new(&theme::TEXT_BOLD, l1), - Paragraph::new(&theme::TEXT_MONO, l2), - Paragraph::new(&theme::TEXT_BOLD, l3), - ]); - - content_in_button_page( - "".into(), - paragraphs, - TR::buttons__continue.into(), - None, - false, - ) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; - let title: Option = kwargs - .get(Qstr::MP_QSTR_title) - .and_then(Obj::try_into_option) - .unwrap_or(None); - - let mut progress = Progress::new(indeterminate, description); - if let Some(title) = title { - progress = progress.with_title(title); - }; - - let obj = LayoutObj::new(progress)?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_progress_coinjoin(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 indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; - let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; - let skip_first_paint: bool = kwargs.get_or(Qstr::MP_QSTR_skip_first_paint, false)?; - - // The second type parameter is actually not used in `new()` but we need to - // provide it. - let progress = CoinJoinProgress::new(title, indeterminate); - let obj = if time_ms > 0 && indeterminate { - let timeout = Timeout::new(time_ms); - LayoutObj::new((timeout, progress.map(|_msg| None)))? - } else { - LayoutObj::new(progress)? - }; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} -extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let label: TString = kwargs - .get(Qstr::MP_QSTR_label)? - .try_into_option()? - .unwrap_or_else(|| model::FULL_NAME.into()); - let notification: Option = - kwargs.get(Qstr::MP_QSTR_notification)?.try_into_option()?; - let notification_level: u8 = kwargs.get_or(Qstr::MP_QSTR_notification_level, 0)?; - let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - let hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?; - - let notification = notification.map(|w| (w, notification_level)); - let loader_description = hold.then_some("Locking the device...".into()); - let obj = LayoutObj::new(Homescreen::new(label, notification, loader_description))?; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let label: TString = kwargs - .get(Qstr::MP_QSTR_label)? - .try_into_option()? - .unwrap_or_else(|| model::FULL_NAME.into()); - let bootscreen: bool = kwargs.get(Qstr::MP_QSTR_bootscreen)?.try_into()?; - let coinjoin_authorized: bool = kwargs.get_or(Qstr::MP_QSTR_coinjoin_authorized, false)?; - let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - - let obj = LayoutObj::new(Lockscreen::new(label, bootscreen, coinjoin_authorized))?; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_firmware_update( - n_args: usize, - args: *const Obj, - kwargs: *mut Map, -) -> Obj { - use super::component::bl_confirm::Confirm; - let block = move |_args: &[Obj], kwargs: &Map| { - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let fingerprint: TString = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?; - - let title = TR::firmware_update__title; - let message = Label::left_aligned(description, theme::TEXT_NORMAL).vertically_centered(); - let fingerprint = Label::left_aligned( - fingerprint, - theme::TEXT_NORMAL.with_line_breaking(LineBreaking::BreakWordsNoHyphen), - ) - .vertically_centered(); - - let obj = LayoutObj::new( - Confirm::new( - theme::BG, - title.into(), - message, - None, - TR::buttons__install.as_tstring(), - false, - ) - .with_info_screen( - TR::firmware_update__title_fingerprint.as_tstring(), - fingerprint, - ), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj { - let block = || { - let image = data.try_into()?; - Ok(check_homescreen_format(image).into()) - }; - - unsafe { util::try_or_raise(block) } -} - -extern "C" fn new_show_wait_text(message: Obj) -> Obj { - let block = || { - let message: TString<'static> = message.try_into()?; - let obj = LayoutObj::new(Connect::new(message, theme::FG, theme::BG))?; - Ok(obj.into()) - }; - - unsafe { util::try_or_raise(block) } -} - #[no_mangle] pub static mp_module_trezorui2: Module = obj_module! { /// from trezor import utils @@ -1607,407 +277,4 @@ pub static mp_module_trezorui2: Module = obj_module! { /// Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(), - /// def disable_animation(disable: bool) -> None: - /// """Disable animations, debug builds only.""" - Qstr::MP_QSTR_disable_animation => obj_fn_1!(upy_disable_animation).as_obj(), - - /// def check_homescreen_format(data: bytes) -> bool: - /// """Check homescreen format and dimensions.""" - Qstr::MP_QSTR_check_homescreen_format => obj_fn_1!(upy_check_homescreen_format).as_obj(), - - /// def confirm_action( - /// *, - /// title: str, - /// action: str | None, - /// description: str | None, - /// subtitle: str | None = None, - /// verb: str = "CONFIRM", - /// verb_cancel: str | None = None, - /// hold: bool = False, - /// hold_danger: bool = False, # unused on TR - /// 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_homescreen( - /// *, - /// title: str, - /// image: bytes, - /// ) -> object: - /// """Confirm homescreen.""" - Qstr::MP_QSTR_confirm_homescreen => obj_fn_kw!(0, new_confirm_homescreen).as_obj(), - - /// def confirm_blob( - /// *, - /// title: str, - /// data: str | bytes, - /// description: str | None, - /// text_mono: bool = True, - /// extra: str | None = None, - /// subtitle: str | None = None, - /// verb: str = "CONFIRM", - /// verb_cancel: str | None = None, - /// verb_info: str | None = None, - /// info: bool = True, - /// hold: bool = False, - /// chunkify: bool = False, - /// page_counter: bool = False, - /// prompt_screen: bool = False, - /// cancel: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm byte sequence data.""" - Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), - - /// def confirm_address( - /// *, - /// title: str, - /// data: str, - /// description: str | None, # unused on TR - /// extra: str | None, # unused on TR - /// verb: str = "CONFIRM", - /// chunkify: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm address.""" - Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(), - - - /// def confirm_properties( - /// *, - /// title: str, - /// items: list[tuple[str | None, str | bytes | None, bool]], - /// hold: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm list of key-value pairs. The third component in the tuple should be True if - /// the value is to be rendered as binary with monospace font, False otherwise. - /// This only concerns the text style, you need to decode the value to UTF-8 in python.""" - Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(), - - /// def confirm_reset_device( - /// *, - /// title: str, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm TOS before device setup.""" - Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, new_confirm_reset_device).as_obj(), - - /// def confirm_backup() -> LayoutObj[UiResult]: - /// """Strongly recommend user to do backup.""" - Qstr::MP_QSTR_confirm_backup => obj_fn_kw!(0, new_confirm_backup).as_obj(), - - /// def show_address_details( - /// *, - /// address: str, - /// case_sensitive: bool, - /// account: str | None, - /// path: str | None, - /// xpubs: list[tuple[str, str]], - /// ) -> LayoutObj[UiResult]: - /// """Show address details - QR code, account, path, cosigner xpubs.""" - Qstr::MP_QSTR_show_address_details => obj_fn_kw!(0, new_show_address_details).as_obj(), - - /// def confirm_value( - /// *, - /// title: str, - /// description: str, - /// value: str, - /// verb: str | None = None, - /// verb_info: str | None = None, - /// hold: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm value.""" - Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(), - - /// def confirm_joint_total( - /// *, - /// spending_amount: str, - /// total_amount: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm total if there are external inputs.""" - Qstr::MP_QSTR_confirm_joint_total => obj_fn_kw!(0, new_confirm_joint_total).as_obj(), - - /// def confirm_modify_output( - /// *, - /// sign: int, - /// amount_change: str, - /// amount_new: str, - /// ) -> LayoutObj[UiResult]: - /// """Decrease or increase output amount.""" - Qstr::MP_QSTR_confirm_modify_output => obj_fn_kw!(0, new_confirm_modify_output).as_obj(), - - /// def confirm_output_address( - /// *, - /// address: str, - /// address_label: str, - /// address_title: str, - /// chunkify: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm output address.""" - Qstr::MP_QSTR_confirm_output_address => obj_fn_kw!(0, new_confirm_output_address).as_obj(), - - /// def confirm_output_amount( - /// *, - /// amount: str, - /// amount_title: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm output amount.""" - Qstr::MP_QSTR_confirm_output_amount => obj_fn_kw!(0, new_confirm_output_amount).as_obj(), - - /// def confirm_summary( - /// *, - /// amount: str, - /// amount_label: str, - /// fee: str, - /// fee_label: str, - /// title: str | None = None, - /// account_items: Iterable[tuple[str, str]] | None = None, - /// extra_items: Iterable[tuple[str, str]] | None = None, - /// extra_title: str | None = None, - /// verb_cancel: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm summary of a transaction.""" - Qstr::MP_QSTR_confirm_summary => obj_fn_kw!(0, new_confirm_summary).as_obj(), - - /// def tutorial() -> LayoutObj[UiResult]: - /// """Show user how to interact with the device.""" - Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(), - - /// def confirm_modify_fee( - /// *, - /// title: str, # ignored - /// sign: int, - /// user_fee_change: str, - /// total_fee_new: str, - /// fee_rate_amount: str | None, - /// ) -> LayoutObj[UiResult]: - /// """Decrease or increase transaction fee.""" - Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(), - - /// def confirm_fido( - /// *, - /// title: str, - /// app_name: str, - /// icon_name: str | None, # unused on TR - /// accounts: list[str | None], - /// ) -> LayoutObj[int | UiResult]: - /// """FIDO confirmation. - /// - /// Returns page index in case of confirmation and CANCELLED otherwise. - /// """ - Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), - - /// def multiple_pages_texts( - /// *, - /// title: str, - /// verb: str, - /// items: list[str], - /// ) -> LayoutObj[UiResult]: - /// """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(), - - /// def show_mismatch(*, title: str) -> LayoutObj[UiResult]: - /// """Warning modal, receiving address mismatch.""" - Qstr::MP_QSTR_show_mismatch => obj_fn_kw!(0, new_show_mismatch).as_obj(), - - /// def confirm_with_info( - /// *, - /// title: str, - /// button: str, - /// info_button: str, # unused on TR - /// items: Iterable[Tuple[int, str | bytes]], - /// verb_cancel: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm given items but with third button. Always single page - /// without scrolling.""" - Qstr::MP_QSTR_confirm_with_info => obj_fn_kw!(0, new_confirm_with_info).as_obj(), - - /// def confirm_more( - /// *, - /// title: str, - /// button: str, - /// items: Iterable[tuple[int, str | bytes]], - /// ) -> object: - /// """Confirm long content with the possibility to go back from any page. - /// Meant to be used with confirm_with_info.""" - Qstr::MP_QSTR_confirm_more => obj_fn_kw!(0, new_confirm_more).as_obj(), - - /// def confirm_coinjoin( - /// *, - /// max_rounds: str, - /// max_feerate: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm coinjoin authorization.""" - Qstr::MP_QSTR_confirm_coinjoin => obj_fn_kw!(0, new_confirm_coinjoin).as_obj(), - - /// def request_pin( - /// *, - /// prompt: str, - /// subprompt: str, - /// allow_cancel: bool = True, # unused on TR - /// wrong_pin: bool = False, # unused on TR - /// ) -> LayoutObj[str | UiResult]: - /// """Request pin on device.""" - Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(), - - /// def request_passphrase( - /// *, - /// prompt: str, - /// max_len: int, # unused on TR - /// ) -> LayoutObj[str | UiResult]: - /// """Get passphrase.""" - Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(), - - /// def request_bip39( - /// *, - /// prompt: str, - /// prefill_word: str, - /// can_go_back: bool, - /// ) -> LayoutObj[str]: - /// """Get recovery word for BIP39.""" - Qstr::MP_QSTR_request_bip39 => obj_fn_kw!(0, new_request_bip39).as_obj(), - - /// def request_slip39( - /// *, - /// prompt: str, - /// prefill_word: str, - /// can_go_back: bool, - /// ) -> LayoutObj[str]: - /// """SLIP39 word input keyboard.""" - Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(), - - /// def select_word( - /// *, - /// title: str, # unused on TR - /// description: str, - /// words: Iterable[str], - /// ) -> LayoutObj[int]: - /// """Select mnemonic word from three possibilities - seed check after backup. The - /// iterable must be of exact size. Returns index in range `0..3`.""" - Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(), - - /// def show_share_words( - /// *, - /// share_words: Iterable[str], - /// ) -> LayoutObj[UiResult]: - /// """Shows a backup seed.""" - Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), - - /// def request_number( - /// *, - /// title: str, - /// count: int, - /// min_count: int, - /// max_count: int, - /// description: Callable[[int], str] | None = None, # unused on TR - /// ) -> LayoutObj[tuple[UiResult, int]]: - /// """Number input with + and - buttons, description, and info button.""" - Qstr::MP_QSTR_request_number => obj_fn_kw!(0, new_request_number).as_obj(), - - /// def show_checklist( - /// *, - /// title: str, # unused on TR - /// items: Iterable[str], - /// active: int, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Checklist of backup steps. Active index is highlighted, previous items have check - /// mark next to them.""" - Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), - - /// def confirm_recovery( - /// *, - /// title: str, # unused on TR - /// description: str, - /// button: str, - /// recovery_type: RecoveryType, - /// info_button: bool, # unused on TR - /// show_instructions: bool, - /// ) -> LayoutObj[UiResult]: - /// """Device recovery homescreen.""" - Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(), - - /// def select_word_count( - /// *, - /// recovery_type: RecoveryType, - /// ) -> LayoutObj[int | str]: # TR returns str - /// """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. - /// For unlocking a repeated backup, select from 20 or 33.""" - Qstr::MP_QSTR_select_word_count => obj_fn_kw!(0, new_select_word_count).as_obj(), - - /// def show_group_share_success( - /// *, - /// lines: Iterable[str], - /// ) -> LayoutObj[int]: - /// """Shown after successfully finishing a group.""" - Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(), - - /// def show_progress( - /// *, - /// description: str, - /// indeterminate: bool = False, - /// title: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Show progress loader. Please note that the number of lines reserved on screen for - /// description is determined at construction time. If you want multiline descriptions - /// make sure the initial description has at least that amount of lines.""" - Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(), - - /// def show_progress_coinjoin( - /// *, - /// title: str, - /// indeterminate: bool = False, - /// time_ms: int = 0, - /// skip_first_paint: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - /// time_ms timeout is passed.""" - Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(), - - /// def show_homescreen( - /// *, - /// label: str | None, - /// hold: bool, # unused on TR - /// notification: str | None, - /// notification_level: int = 0, - /// skip_first_paint: bool, - /// ) -> LayoutObj[UiResult]: - /// """Idle homescreen.""" - Qstr::MP_QSTR_show_homescreen => obj_fn_kw!(0, new_show_homescreen).as_obj(), - - /// def show_lockscreen( - /// *, - /// label: str | None, - /// bootscreen: bool, - /// skip_first_paint: bool, - /// coinjoin_authorized: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Homescreen for locked device.""" - Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), - - /// def confirm_firmware_update( - /// *, - /// description: str, - /// fingerprint: str, - /// ) -> LayoutObj[UiResult]: - /// """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" - Qstr::MP_QSTR_confirm_firmware_update => obj_fn_kw!(0, new_confirm_firmware_update).as_obj(), - - /// def show_wait_text(message: str, /) -> 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(), }; diff --git a/core/embed/rust/src/ui/model_tr/mod.rs b/core/embed/rust/src/ui/model_tr/mod.rs index 8ad9254362..d7369d81d3 100644 --- a/core/embed/rust/src/ui/model_tr/mod.rs +++ b/core/embed/rust/src/ui/model_tr/mod.rs @@ -12,6 +12,8 @@ mod screens; pub mod theme; pub struct ModelTRFeatures {} + +#[cfg(feature = "micropython")] pub mod ui_features_fw; impl UIFeaturesCommon for ModelTRFeatures { 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 c914a99e55..4225942deb 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 @@ -1,19 +1,1033 @@ +use core::cmp::Ordering; + use crate::{ error::Error, - micropython::gc::Gc, + io::BinaryData, + maybe_trace::MaybeTrace, + micropython::{gc::Gc, iter::IterBuf, list::List, obj::Obj, util}, strutil::TString, + translations::TR, ui::{ component::{ - text::paragraphs::{Paragraph, Paragraphs}, ComponentExt, Timeout + connect::Connect, + text::{ + op::OpTextLayout, + paragraphs::{ + Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, + Paragraphs, VecExt, + }, + TextStyle, + }, + Component, ComponentExt, Empty, FormattedText, Label, LineBreaking, Paginate, Timeout, + }, + display::Font, + geometry, + layout::{ + obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, + util::{ConfirmBlob, RecoveryType}, + }, + model_tr::{ + component::{ButtonActions, ButtonLayout, Page}, + constant, }, - layout::obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, ui_features_fw::UIFeaturesFirmware, }, }; -use super::{component::Frame, theme, ModelTRFeatures}; +use super::{ + component::{ + AddressDetails, ButtonDetails, ButtonPage, CoinJoinProgress, ConfirmHomescreen, Flow, + FlowPages, Frame, Homescreen, Lockscreen, NumberInput, PassphraseEntry, PinEntry, Progress, + ScrollableFrame, ShareWords, ShowMore, SimpleChoice, WordlistEntry, WordlistType, + }, + theme, ModelTRFeatures, +}; + +use heapless::Vec; impl UIFeaturesFirmware for ModelTRFeatures { + fn confirm_action( + title: TString<'static>, + action: Option>, + description: Option>, + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + hold: bool, + hold_danger: bool, + reverse: bool, + prompt_screen: bool, + prompt_title: Option>, + ) -> Result { + 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.unwrap_or(TString::empty()), + verb_cancel, + hold, + ) + } + + fn confirm_address( + title: TString<'static>, + data: Obj, + description: Option>, + extra: Option>, + verb: Option>, + chunkify: bool, + ) -> Result { + let verb = verb.unwrap_or(TR::buttons__confirm.into()); + let address: TString = data.try_into()?; + + let get_page = move |page_index| { + assert!(page_index == 0); + + let btn_layout = ButtonLayout::cancel_armed_info(verb); + let btn_actions = ButtonActions::cancel_confirm_info(); + let style = if chunkify { + // Chunkifying the address into smaller pieces when requested + theme::TEXT_MONO_ADDRESS_CHUNKS + } else { + theme::TEXT_MONO_DATA + }; + let ops = OpTextLayout::new(style).text_mono(address); + let formatted = FormattedText::new(ops).vertically_centered(); + Page::new(btn_layout, btn_actions, formatted).with_title(title) + }; + let pages = FlowPages::new(get_page, 1); + + let layout = RootComponent::new(Flow::new(pages)); + Ok(layout) + } + + fn confirm_blob( + title: TString<'static>, + data: Obj, + description: Option>, + _text_mono: bool, + extra: Option>, + _subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + _verb_info: Option>, + _info: bool, + hold: bool, + chunkify: bool, + _page_counter: bool, + _prompt_screen: bool, + _cancel: bool, + ) -> Result, Error> { + let style = if chunkify { + // Chunkifying the address into smaller pieces when requested + &theme::TEXT_MONO_ADDRESS_CHUNKS + } else { + &theme::TEXT_MONO_DATA + }; + + let paragraphs = ConfirmBlob { + description: description.unwrap_or("".into()), + extra: extra.unwrap_or("".into()), + data: data.try_into()?, + description_font: &theme::TEXT_BOLD, + extra_font: &theme::TEXT_NORMAL, + data_font: style, + } + .into_paragraphs(); + + let layout = content_in_button_page( + title, + paragraphs, + verb.unwrap_or(TR::buttons__confirm.into()), + verb_cancel, + hold, + )?; + LayoutObj::new_root(layout) + } + + fn confirm_blob_intro( + _title: TString<'static>, + _data: Obj, + _subtitle: Option>, + _verb: Option>, + _verb_cancel: Option>, + _chunkify: bool, + ) -> Result, Error> { + Err::, Error>(Error::ValueError(c"confirm_blob_intro not implemented")) + } + + fn confirm_homescreen( + title: TString<'static>, + image: BinaryData<'static>, + ) -> Result { + let layout = RootComponent::new(ConfirmHomescreen::new(title, image)); + Ok(layout) + } + + fn confirm_coinjoin( + max_rounds: TString<'static>, + max_feerate: TString<'static>, + ) -> Result { + // Decreasing bottom padding between paragraphs to fit one screen + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_rounds).with_bottom_padding(2), + Paragraph::new(&theme::TEXT_MONO, max_rounds), + Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_mining_fee) + .with_bottom_padding(2) + .no_break(), + Paragraph::new(&theme::TEXT_MONO, max_feerate).with_bottom_padding(2), + ]); + + content_in_button_page( + TR::coinjoin__title.into(), + paragraphs, + TR::buttons__hold_to_confirm.into(), + None, + true, + ) + } + + fn confirm_emphasized( + _title: TString<'static>, + _items: Obj, + _verb: Option>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"confirm_emphasized not implemented", + )) + } + + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, + ) -> Result { + // Cache the page count so that we can move `accounts` into the closure. + let page_count = accounts.len(); + + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let get_page = move |page_index| { + let account_obj = unwrap!(accounts.get(page_index)); + let account = TString::try_from(account_obj).unwrap_or_else(|_| TString::empty()); + + let (btn_layout, btn_actions) = if page_count == 1 { + // There is only one page + ( + ButtonLayout::cancel_none_text(TR::buttons__confirm.into()), + ButtonActions::cancel_none_confirm(), + ) + } else if page_index == 0 { + // First page + ( + ButtonLayout::cancel_armed_arrow(TR::buttons__select.into()), + ButtonActions::cancel_confirm_next(), + ) + } else if page_index == page_count - 1 { + // Last page + ( + ButtonLayout::arrow_armed_none(TR::buttons__select.into()), + ButtonActions::prev_confirm_none(), + ) + } else { + // Page in the middle + ( + ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()), + ButtonActions::prev_confirm_next(), + ) + }; + + let ops = OpTextLayout::new(theme::TEXT_NORMAL) + .newline() + .text_normal(app_name) + .newline() + .text_bold(account); + let formatted = FormattedText::new(ops); + + Page::new(btn_layout, btn_actions, formatted) + }; + + let pages = FlowPages::new(get_page, page_count); + // Returning the page index in case of confirmation. + let obj = RootComponent::new( + Flow::new(pages) + .with_common_title(title) + .with_return_confirmed_index(), + ); + Ok(obj) + } + + fn confirm_firmware_update( + description: TString<'static>, + fingerprint: TString<'static>, + ) -> Result { + use super::component::bl_confirm::Confirm; + let title = TR::firmware_update__title; + let message = Label::left_aligned(description, theme::TEXT_NORMAL).vertically_centered(); + let fingerprint = Label::left_aligned( + fingerprint, + theme::TEXT_NORMAL.with_line_breaking(LineBreaking::BreakWordsNoHyphen), + ) + .vertically_centered(); + + let layout = RootComponent::new( + Confirm::new( + theme::BG, + title.into(), + message, + None, + TR::buttons__install.as_tstring(), + false, + ) + .with_info_screen( + TR::firmware_update__title_fingerprint.as_tstring(), + fingerprint, + ), + ); + Ok(layout) + } + + fn confirm_modify_fee( + title: TString<'static>, + sign: i32, + user_fee_change: TString<'static>, + total_fee_new: TString<'static>, + fee_rate_amount: Option>, + ) -> Result { + let (description, change) = match sign { + s if s < 0 => (TR::modify_fee__decrease_fee, user_fee_change), + s if s > 0 => (TR::modify_fee__increase_fee, user_fee_change), + _ => (TR::modify_fee__no_change, "".into()), + }; + + let mut paragraphs_vec = ParagraphVecShort::new(); + paragraphs_vec + .add(Paragraph::new(&theme::TEXT_BOLD, description)) + .add(Paragraph::new(&theme::TEXT_MONO, change)) + .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__transaction_fee).no_break()) + .add(Paragraph::new(&theme::TEXT_MONO, total_fee_new)); + + if let Some(fee_rate_amount) = fee_rate_amount { + paragraphs_vec + .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__fee_rate).no_break()) + .add(Paragraph::new(&theme::TEXT_MONO, fee_rate_amount)); + } + + content_in_button_page( + TR::modify_fee__title.into(), + paragraphs_vec.into_paragraphs(), + TR::buttons__confirm.into(), + Some("".into()), + false, + ) + } + + fn confirm_modify_output( + sign: i32, + amount_change: TString<'static>, + amount_new: TString<'static>, + ) -> Result { + let description = if sign < 0 { + TR::modify_amount__decrease_amount + } else { + TR::modify_amount__increase_amount + }; + + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_NORMAL, description), + Paragraph::new(&theme::TEXT_MONO, amount_change).break_after(), + Paragraph::new(&theme::TEXT_BOLD, TR::modify_amount__new_amount), + Paragraph::new(&theme::TEXT_MONO, amount_new), + ]); + + content_in_button_page( + TR::modify_amount__title.into(), + paragraphs, + TR::buttons__confirm.into(), + Some("".into()), + false, + ) + } + + fn confirm_more( + title: TString<'static>, + button: TString<'static>, + _button_style_confirm: bool, + items: Obj, + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [font, text]: [Obj; 2] = util::iter_into_array(para)?; + let style: &TextStyle = theme::textstyle_number(font.try_into()?); + let text: TString = text.try_into()?; + paragraphs.add(Paragraph::new(style, text)); + } + + content_in_button_page( + title, + paragraphs.into_paragraphs(), + button, + Some("<".into()), + false, + ) + } + + fn confirm_properties( + title: TString<'static>, + items: Obj, + hold: bool, + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [key, value, is_data]: [Obj; 3] = util::iter_into_array(para)?; + let key = key.try_into_option::()?; + let value = value.try_into_option::()?; + let is_data: bool = is_data.try_into()?; + + if let Some(key) = key { + if value.is_some() { + // Decreasing the margin between key and value (default is 5 px, we use 2 px) + // (this enables 4 lines - 2 key:value pairs - on the same screen) + paragraphs.add( + Paragraph::new(&theme::TEXT_BOLD, key) + .no_break() + .with_bottom_padding(2), + ); + } else { + paragraphs.add(Paragraph::new(&theme::TEXT_BOLD, key)); + } + } + if let Some(value) = value { + let style = if is_data { + &theme::TEXT_MONO_DATA + } else { + &theme::TEXT_MONO + }; + paragraphs.add(Paragraph::new(style, value)); + } + } + let button_text = if hold { + TR::buttons__hold_to_confirm.into() + } else { + TR::buttons__confirm.into() + }; + + content_in_button_page( + title, + paragraphs.into_paragraphs(), + button_text, + Some("".into()), + hold, + ) + } + + fn confirm_reset_device(recovery: bool) -> Result { + let (title, button) = if recovery { + ( + TR::recovery__title_recover.into(), + TR::reset__button_recover.into(), + ) + } else { + ( + TR::reset__title_create_wallet.into(), + TR::reset__button_create.into(), + ) + }; + let ops = OpTextLayout::new(theme::TEXT_NORMAL) + .text_normal(TR::reset__by_continuing) + .next_page() + .text_normal(TR::reset__more_info_at) + .newline() + .text_bold(TR::reset__tos_link); + let formatted = FormattedText::new(ops).vertically_centered(); + + content_in_button_page(title, formatted, button, Some("".into()), false) + } + + fn confirm_summary( + amount: TString<'static>, + amount_label: TString<'static>, + fee: TString<'static>, + fee_label: TString<'static>, + title: Option>, + account_items: Option, + extra_items: Option, + extra_title: Option>, + verb_cancel: Option>, + ) -> Result { + // collect available info pages + let mut info_pages: Vec<(TString, Obj), 2> = Vec::new(); + if let Some(info) = extra_items { + // put extra items first as it's typically used for fee info + let extra_title = extra_title.unwrap_or(TR::words__title_information.into()); + unwrap!(info_pages.push((extra_title, info))); + } + if let Some(info) = account_items { + unwrap!(info_pages.push((TR::confirm_total__title_sending_from.into(), info))); + } + + // button layouts and actions + let verb_cancel: TString = verb_cancel.unwrap_or(TString::empty()); + let btns_summary_page = move |has_pages_after: bool| -> (ButtonLayout, ButtonActions) { + // if there are no info pages, the right button is not needed + // if verb_cancel is "^", the left button is an arrow pointing up + let left_btn = Some(ButtonDetails::from_text_possible_icon(verb_cancel)); + let right_btn = has_pages_after.then(|| { + ButtonDetails::text("i".into()) + .with_fixed_width(theme::BUTTON_ICON_WIDTH) + .with_font(Font::NORMAL) + }); + let middle_btn = Some(ButtonDetails::armed_text(TR::buttons__confirm.into())); + + ( + ButtonLayout::new(left_btn, middle_btn, right_btn), + if has_pages_after { + ButtonActions::cancel_confirm_next() + } else { + ButtonActions::cancel_confirm_none() + }, + ) + }; + let btns_info_page = |is_last: bool| -> (ButtonLayout, ButtonActions) { + // on the last info page, the right button is not needed + if is_last { + ( + ButtonLayout::arrow_none_none(), + ButtonActions::prev_none_none(), + ) + } else { + ( + ButtonLayout::arrow_none_arrow(), + ButtonActions::prev_none_next(), + ) + } + }; + + let total_pages = 1 + info_pages.len(); + let get_page = move |page_index| { + match page_index { + 0 => { + // Total amount + fee + let (btn_layout, btn_actions) = btns_summary_page(!info_pages.is_empty()); + + let ops = OpTextLayout::new(theme::TEXT_MONO) + .text_bold(amount_label) + .newline() + .text_mono(amount) + .newline() + .newline() + .text_bold(fee_label) + .newline() + .text_mono(fee); + + let formatted = FormattedText::new(ops); + Page::new(btn_layout, btn_actions, formatted) + } + i => { + // Other info pages as provided + let (title, info_obj) = &info_pages[i - 1]; + let is_last = i == total_pages - 1; + let (btn_layout, btn_actions) = btns_info_page(is_last); + + let mut ops = OpTextLayout::new(theme::TEXT_MONO); + for item in unwrap!(IterBuf::new().try_iterate(*info_obj)) { + let [key, value]: [Obj; 2] = unwrap!(util::iter_into_array(item)); + if !ops.is_empty() { + // Each key-value pair is on its own page + ops = ops.next_page(); + } + ops = ops + .text_bold(unwrap!(TString::try_from(key))) + .newline() + .text_mono(unwrap!(TString::try_from(value))); + } + + let formatted = FormattedText::new(ops).vertically_centered(); + Page::new(btn_layout, btn_actions, formatted) + .with_slim_arrows() + .with_title(*title) + } + } + }; + let pages = FlowPages::new(get_page, total_pages); + + let layout = RootComponent::new(Flow::new(pages).with_scrollbar(false)); + Ok(layout) + } + + fn confirm_value( + title: TString<'static>, + value: Obj, + description: Option>, + _subtitle: Option>, + verb: Option>, + _verb_info: Option>, + verb_cancel: Option>, + _info_button: bool, + hold: bool, + _chunkify: bool, + _text_mono: bool, + ) -> Result, Error> { + let value: TString = value.try_into()?; + let description = description.unwrap_or("".into()); + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_BOLD, description), + Paragraph::new(&theme::TEXT_MONO, value), + ]); + + let layout = content_in_button_page( + title, + paragraphs, + verb.unwrap_or(TR::buttons__confirm.into()), + verb_cancel, + hold, + )?; + LayoutObj::new_root(layout) + } + + fn confirm_with_info( + title: TString<'static>, + button: TString<'static>, + _info_button: TString<'static>, + verb_cancel: Option>, + items: Obj, + ) -> Result { + let mut paragraphs = ParagraphVecShort::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [font, text]: [Obj; 2] = util::iter_into_array(para)?; + let style: &TextStyle = theme::textstyle_number(font.try_into()?); + let text: TString = text.try_into()?; + paragraphs.add(Paragraph::new(style, text)); + if paragraphs.is_full() { + break; + } + } + + let layout = RootComponent::new(Frame::new( + title, + ShowMore::>::new( + paragraphs.into_paragraphs(), + verb_cancel, + button, + ), + )); + Ok(layout) + } + + fn check_homescreen_format(image: BinaryData, _accept_toif: bool) -> bool { + super::component::check_homescreen_format(image) + } + + fn continue_recovery_homepage( + text: TString<'static>, + _subtext: Option>, + button: Option>, + recovery_type: RecoveryType, + show_instructions: bool, + _remaining_shares: Option, + ) -> Result, Error> { + let mut paragraphs = ParagraphVecShort::new(); + let button = button.unwrap_or(TString::empty()); + paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, text)); + if show_instructions { + paragraphs + .add(Paragraph::new( + &theme::TEXT_NORMAL, + TR::recovery__enter_each_word, + )) + .add(Paragraph::new( + &theme::TEXT_NORMAL, + TR::recovery__cursor_will_change, + )); + } + + let title = match recovery_type { + RecoveryType::DryRun => TR::recovery__title_dry_run, + RecoveryType::UnlockRepeatedBackup => TR::recovery__title_dry_run, + _ => TR::recovery__title, + }; + + let layout = content_in_button_page( + title.into(), + paragraphs.into_paragraphs(), + button, + Some("".into()), + false, + )?; + LayoutObj::new_root(layout) + } + + fn flow_confirm_output( + _title: Option>, + _subtitle: Option>, + _message: Obj, + _amount: Option, + _chunkify: bool, + _text_mono: bool, + _account: Option>, + _account_path: Option>, + _br_code: u16, + _br_name: TString<'static>, + _address: Option, + _address_title: Option>, + _summary_items: Option, + _fee_items: Option, + _summary_title: Option>, + _summary_br_code: Option, + _summary_br_name: Option>, + _cancel_text: Option>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"flow_confirm_output not supported", + )) + } + + fn flow_confirm_set_new_pin( + _title: TString<'static>, + _description: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"flow_confirm_set_new_pin not supported", + )) + } + + fn flow_get_address( + _address: Obj, + _title: TString<'static>, + _description: Option>, + _extra: Option>, + _chunkify: bool, + _address_qr: TString<'static>, + _case_sensitive: bool, + _account: Option>, + _path: Option>, + _xpubs: Obj, + _title_success: TString<'static>, + _br_code: u16, + _br_name: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"flow_get_address not supported", + )) + } + + fn multiple_pages_texts( + title: TString<'static>, + verb: TString<'static>, + items: Gc, + ) -> Result { + // Cache the page count so that we can move `items` into the closure. + let page_count = items.len(); + + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let get_page = move |page_index| { + let item_obj = unwrap!(items.get(page_index)); + let text = unwrap!(TString::try_from(item_obj)); + + let (btn_layout, btn_actions) = if page_count == 1 { + // There is only one page + ( + ButtonLayout::cancel_none_text(verb), + ButtonActions::cancel_none_confirm(), + ) + } else if page_index == 0 { + // First page + ( + ButtonLayout::cancel_none_arrow_wide(), + ButtonActions::cancel_none_next(), + ) + } else if page_index == page_count - 1 { + // Last page + ( + ButtonLayout::up_arrow_none_text(verb), + ButtonActions::prev_none_confirm(), + ) + } else { + // Page in the middle + ( + ButtonLayout::up_arrow_none_arrow_wide(), + ButtonActions::prev_none_next(), + ) + }; + + let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text); + let formatted = FormattedText::new(ops).vertically_centered(); + + Page::new(btn_layout, btn_actions, formatted) + }; + + let pages = FlowPages::new(get_page, page_count); + let layout = RootComponent::new(Flow::new(pages).with_common_title(title)); + Ok(layout) + } + + fn prompt_backup() -> Result { + let get_page = move |page_index| match page_index { + 0 => { + let btn_layout = ButtonLayout::text_none_arrow_wide(TR::buttons__skip.into()); + let btn_actions = ButtonActions::cancel_none_next(); + let ops = OpTextLayout::new(theme::TEXT_NORMAL) + .text_normal(TR::backup__new_wallet_created) + .newline() + .text_normal(TR::backup__it_should_be_backed_up_now); + let formatted = FormattedText::new(ops).vertically_centered(); + Page::new(btn_layout, btn_actions, formatted) + .with_title(TR::words__title_success.into()) + } + 1 => { + let btn_layout = ButtonLayout::up_arrow_none_text(TR::buttons__back_up.into()); + let btn_actions = ButtonActions::prev_none_confirm(); + let ops = + OpTextLayout::new(theme::TEXT_NORMAL).text_normal(TR::backup__recover_anytime); + let formatted = FormattedText::new(ops).vertically_centered(); + Page::new(btn_layout, btn_actions, formatted) + .with_title(TR::backup__title_backup_wallet.into()) + } + _ => unreachable!(), + }; + let pages = FlowPages::new(get_page, 2); + + let layout = RootComponent::new(Flow::new(pages)); + Ok(layout) + } + + fn request_bip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result { + let layout = RootComponent::new( + Frame::new( + prompt, + prefill_word + .map(|s| WordlistEntry::prefilled_word(s, WordlistType::Bip39, can_go_back)), + ) + .with_title_centered(), + ); + Ok(layout) + } + + fn request_slip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result { + let layout = RootComponent::new( + Frame::new( + prompt, + prefill_word + .map(|s| WordlistEntry::prefilled_word(s, WordlistType::Slip39, can_go_back)), + ) + .with_title_centered(), + ); + Ok(layout) + } + + fn request_number( + title: TString<'static>, + count: u32, + min_count: u32, + max_count: u32, + _description: Option>, + _more_info_callback: Option TString<'static> + 'static>, + ) -> Result { + let layout = RootComponent::new( + Frame::new(title, NumberInput::new(min_count, max_count, count)).with_title_centered(), + ); + Ok(layout) + } + + fn request_pin( + prompt: TString<'static>, + subprompt: TString<'static>, + allow_cancel: bool, + warning: bool, + ) -> Result { + let layout = RootComponent::new(PinEntry::new(prompt, subprompt)); + Ok(layout) + } + + fn request_passphrase( + prompt: TString<'static>, + max_len: u32, + ) -> Result { + let layout = + RootComponent::new(Frame::new(prompt, PassphraseEntry::new()).with_title_centered()); + Ok(layout) + } + + fn select_word( + title: TString<'static>, + description: TString<'static>, + words: [TString<'static>; 3], + ) -> Result { + let words: Vec, 5> = Vec::from_iter(words); + // Returning the index of the selected word, not the word itself + let layout = RootComponent::new( + Frame::new( + description, + SimpleChoice::new(words, false) + .with_show_incomplete() + .with_return_index(), + ) + .with_title_centered(), + ); + Ok(layout) + } + + fn select_word_count(recovery_type: RecoveryType) -> Result { + let title: TString = TR::word_count__title.into(); + let choices: Vec, 5> = { + let nums: &[&str] = if matches!(recovery_type, RecoveryType::UnlockRepeatedBackup) { + &["20", "33"] + } else { + &["12", "18", "20", "24", "33"] + }; + + nums.iter().map(|&num| num.into()).collect() + }; + + let layout = RootComponent::new( + Frame::new(title, SimpleChoice::new(choices, false)).with_title_centered(), + ); + Ok(layout) + } + + fn set_brightness(current_brightness: Option) -> Result { + Err::, Error>(Error::ValueError( + c"setting brightness not supported", + )) + } + + fn show_address_details( + _qr_title: TString<'static>, + address: TString<'static>, + case_sensitive: bool, + _details_title: TString<'static>, + account: Option>, + path: Option>, + xpubs: Obj, + ) -> Result { + let mut ad = AddressDetails::new(address, case_sensitive, account, path)?; + + for i in IterBuf::new().try_iterate(xpubs)? { + let [xtitle, text]: [TString; 2] = util::iter_into_array(i)?; + ad.add_xpub(xtitle, text)?; + } + + let layout = RootComponent::new(ad); + Ok(layout) + } + + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + for (i, item) in items.into_iter().enumerate() { + let style = match i.cmp(&active) { + Ordering::Less => &theme::TEXT_NORMAL, + Ordering::Equal => &theme::TEXT_BOLD, + Ordering::Greater => &theme::TEXT_NORMAL, + }; + paragraphs.add(Paragraph::new(style, item)); + } + let confirm_btn = Some(ButtonDetails::text(button)); + + let layout = RootComponent::new( + ButtonPage::new( + Checklist::from_paragraphs( + theme::ICON_ARROW_RIGHT_FAT, + theme::ICON_TICK_FAT, + active, + paragraphs + .into_paragraphs() + .with_spacing(theme::CHECKLIST_SPACING), + ) + .with_check_width(theme::CHECKLIST_CHECK_WIDTH) + .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET), + theme::BG, + ) + .with_confirm_btn(confirm_btn), + ); + Ok(layout) + } + + fn show_danger( + _title: TString<'static>, + _description: TString<'static>, + _value: TString<'static>, + _verb_cancel: Option>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"show_danger not supported", + )) + } + + 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 { + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_MONO, lines[0]), + Paragraph::new(&theme::TEXT_BOLD, lines[1]), + Paragraph::new(&theme::TEXT_MONO, lines[2]), + Paragraph::new(&theme::TEXT_BOLD, lines[3]), + ]); + content_in_button_page( + "".into(), + paragraphs, + TR::buttons__continue.into(), + None, + false, + ) + } + + fn show_homescreen( + label: TString<'static>, + hold: bool, + notification: Option>, + notification_level: u8, + ) -> Result { + let notification = notification.map(|w| (w, notification_level)); + let loader_description = hold.then_some("Locking the device...".into()); + let layout = RootComponent::new(Homescreen::new(label, notification, loader_description)); + Ok(layout) + } + fn show_info( title: TString<'static>, description: TString<'static>, @@ -35,4 +1049,297 @@ impl UIFeaturesFirmware for ModelTRFeatures { }; Ok(obj) } + + fn show_info_with_cancel( + _title: TString<'static>, + _items: Obj, + _horizontal: bool, + _chunkify: bool, + ) -> Result { + Err::, Error>(Error::ValueError( + c"show_info_with_cancel not supported", + )) + } + + fn show_lockscreen( + label: TString<'static>, + bootscreen: bool, + coinjoin_authorized: bool, + ) -> Result { + let layout = RootComponent::new(Lockscreen::new(label, bootscreen, coinjoin_authorized)); + Ok(layout) + } + + fn show_mismatch(title: TString<'static>) -> Result { + let get_page = move |page_index| { + assert!(page_index == 0); + + let btn_layout = ButtonLayout::arrow_none_text(TR::buttons__quit.into()); + let btn_actions = ButtonActions::cancel_none_confirm(); + let ops = OpTextLayout::new(theme::TEXT_NORMAL) + .text_bold_upper(title) + .newline() + .newline_half() + .text_normal(TR::addr_mismatch__contact_support_at) + .newline() + .text_bold(TR::addr_mismatch__support_url); + let formatted = FormattedText::new(ops); + Page::new(btn_layout, btn_actions, formatted) + }; + let pages = FlowPages::new(get_page, 1); + + let obj = RootComponent::new(Flow::new(pages)); + Ok(obj) + } + + fn show_progress( + description: TString<'static>, + indeterminate: bool, + title: Option>, + ) -> Result { + let mut progress = Progress::new(indeterminate, description); + if let Some(title) = title { + progress = progress.with_title(title); + }; + + let layout = RootComponent::new(progress); + Ok(layout) + } + + fn show_progress_coinjoin( + title: TString<'static>, + indeterminate: bool, + time_ms: u32, + skip_first_paint: bool, + ) -> Result, Error> { + let progress = CoinJoinProgress::new(title, indeterminate); + let obj = if time_ms > 0 && indeterminate { + let timeout = Timeout::new(time_ms); + LayoutObj::new((timeout, progress.map(|_msg| None)))? + } else { + LayoutObj::new(progress)? + }; + if skip_first_paint { + obj.skip_first_paint(); + } + Ok(obj) + } + + fn show_share_words( + words: heapless::Vec, 33>, + _title: Option>, + ) -> Result { + let cancel_btn = Some(ButtonDetails::up_arrow_icon()); + let confirm_btn = + Some(ButtonDetails::text(TR::buttons__hold_to_confirm.into()).with_default_duration()); + + let layout = RootComponent::new( + ButtonPage::new(ShareWords::new(words), theme::BG) + .with_cancel_btn(cancel_btn) + .with_confirm_btn(confirm_btn), + ); + Ok(layout) + } + + fn show_share_words_mercury( + _words: heapless::Vec, 33>, + _subtitle: Option>, + _instructions: crate::micropython::obj::Obj, + _text_footer: Option>, + _text_confirm: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"use show_share_words", + )) + } + + fn show_remaining_shares( + pages_iterable: crate::micropython::obj::Obj, // TODO: replace Obj + ) -> Result { + Err::, Error>(Error::ValueError( + c"show remaining shares not supported", + )) + } + + fn show_simple( + text: TString<'static>, + _title: Option>, + _button: Option>, + ) -> Result, Error> { + let paragraph = Paragraph::new(&theme::TEXT_NORMAL, text).centered(); + let content = Paragraphs::new([paragraph]); + let obj = LayoutObj::new(content)?; + Ok(obj) + } + + 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; + + let get_page = move |page_index| { + // Lazy-loaded list of screens to show, with custom content, + // buttons and actions triggered by these buttons. + // Cancelling the first screen will point to the last one, + // which asks for confirmation whether user wants to + // really cancel the tutorial. + match page_index { + // title, text, btn_layout, btn_actions + 0 => tutorial_screen( + TR::tutorial__title_hello.into(), + TR::tutorial__welcome_press_right, + ButtonLayout::cancel_none_arrow(), + ButtonActions::last_none_next(), + ), + 1 => tutorial_screen( + "".into(), + TR::tutorial__use_trezor, + ButtonLayout::arrow_none_arrow(), + ButtonActions::prev_none_next(), + ), + 2 => tutorial_screen( + TR::buttons__hold_to_confirm.into(), + TR::tutorial__press_and_hold, + ButtonLayout::arrow_none_htc(TR::buttons__hold_to_confirm.into()), + ButtonActions::prev_none_next(), + ), + 3 => tutorial_screen( + TR::tutorial__title_screen_scroll.into(), + TR::tutorial__scroll_down, + ButtonLayout::arrow_none_text(TR::buttons__continue.into()), + ButtonActions::prev_none_next(), + ), + 4 => tutorial_screen( + TR::buttons__confirm.into(), + TR::tutorial__middle_click, + ButtonLayout::none_armed_none(TR::buttons__confirm.into()), + ButtonActions::none_next_none(), + ), + 5 => tutorial_screen( + TR::tutorial__title_tutorial_complete.into(), + TR::tutorial__ready_to_use, + ButtonLayout::text_none_text( + TR::buttons__again.into(), + TR::buttons__continue.into(), + ), + ButtonActions::beginning_none_confirm(), + ), + 6 => tutorial_screen( + TR::tutorial__title_skip.into(), + TR::tutorial__sure_you_want_skip, + ButtonLayout::arrow_none_text(TR::buttons__skip.into()), + ButtonActions::beginning_none_cancel(), + ), + _ => unreachable!(), + } + }; + + let pages = FlowPages::new(get_page, PAGE_COUNT); + + // Setting the ignore-second-button to mimic all the Choice pages, to teach user + // that they should really press both buttons at the same time to achieve + // middle-click. + let layout = RootComponent::new( + Flow::new(pages) + .with_scrollbar(false) + .with_ignore_second_button_ms(constant::IGNORE_OTHER_BTN_MS), + ); + Ok(layout) + } +} + +/// 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)) +} + +/// General pattern of most tutorial screens. +/// (title, text, btn_layout, btn_actions, text_y_offset) +fn tutorial_screen( + title: TString<'static>, + text: TR, + btn_layout: ButtonLayout, + btn_actions: ButtonActions, +) -> Page { + let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text); + let formatted = FormattedText::new(ops).vertically_centered(); + Page::new(btn_layout, btn_actions, formatted).with_title(title) } diff --git a/core/embed/rust/src/ui/model_tt/component/mod.rs b/core/embed/rust/src/ui/model_tt/component/mod.rs index e3475d6679..738694fc48 100644 --- a/core/embed/rust/src/ui/model_tt/component/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/mod.rs @@ -23,6 +23,8 @@ mod progress; mod result; mod scroll; mod set_brightness; +#[cfg(feature = "translations")] +mod share_words; mod simple_page; mod swipe; mod welcome_screen; @@ -59,6 +61,8 @@ pub use progress::Progress; pub use result::{ResultFooter, ResultScreen, ResultStyle}; pub use scroll::ScrollBar; pub use set_brightness::SetBrightnessDialog; +#[cfg(feature = "translations")] +pub use share_words::ShareWords; pub use simple_page::SimplePage; pub use swipe::{Swipe, SwipeDirection}; pub use welcome_screen::WelcomeScreen; diff --git a/core/embed/rust/src/ui/model_tt/component/set_brightness.rs b/core/embed/rust/src/ui/model_tt/component/set_brightness.rs index a19ccc49f9..8222666fce 100644 --- a/core/embed/rust/src/ui/model_tt/component/set_brightness.rs +++ b/core/embed/rust/src/ui/model_tt/component/set_brightness.rs @@ -17,8 +17,7 @@ use super::{ pub struct SetBrightnessDialog(NumberInputSliderDialog); impl SetBrightnessDialog { - pub fn new(current: Option) -> Self { - let current = current.unwrap_or(theme::backlight::get_backlight_normal()); + pub fn new(current: u8) -> Self { Self(NumberInputSliderDialog::new( theme::backlight::get_backlight_min() as u16, theme::backlight::get_backlight_max() as u16, diff --git a/core/embed/rust/src/ui/model_tt/component/share_words.rs b/core/embed/rust/src/ui/model_tt/component/share_words.rs new file mode 100644 index 0000000000..13d236e245 --- /dev/null +++ b/core/embed/rust/src/ui/model_tt/component/share_words.rs @@ -0,0 +1,114 @@ +use crate::{ + strutil::TString, + ui::{ + component::{Component, Event, EventCtx, Never, Paginate}, + display::Font, + geometry::{Offset, Rect}, + model_tt::theme, + shape::{self, Renderer}, + }, +}; +use heapless::Vec; +use ufmt::uwrite; + +const WORDS_PER_PAGE: usize = 4; +const TOP_PADDING_OFFSET: i16 = 13; +const WORD_FONT: Font = Font::MONO; +const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less + +/// Showing the given share words. +pub struct ShareWords<'a> { + area: Rect, + share_words: Vec, MAX_WORDS>, + page_index: usize, +} + +impl<'a> ShareWords<'a> { + pub fn new(share_words: Vec, MAX_WORDS>) -> Self { + Self { + area: Rect::zero(), + share_words, + page_index: 0, + } + } + + fn total_page_count(&self) -> usize { + (self.share_words.len() + WORDS_PER_PAGE - 1) / WORDS_PER_PAGE + } +} + +impl<'a> Component for ShareWords<'a> { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + self.area = bounds; + bounds + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + None + } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let line_height = WORD_FONT.line_height(); + let ordinal_largest_on_this_page = + (WORDS_PER_PAGE * (self.page_index + 1)).min(self.share_words.len()); + let is_largest_double_digit = ordinal_largest_on_this_page >= 10; + let mut y_offset = self.area.top_left().y + TOP_PADDING_OFFSET; + + for (word_idx, word) in self + .share_words + .iter() + .enumerate() + .skip(self.page_index * WORDS_PER_PAGE) + .take(WORDS_PER_PAGE) + { + let ordinal = word_idx + 1; + let base = self.area.top_left() + Offset::y(y_offset); + word.map(|w| { + let double_digit = ordinal >= 10; + let text_fmt = if double_digit || !is_largest_double_digit { + uformat!("{}. {}", ordinal, w) + } else { + uformat!(" {}. {}", ordinal, w) + }; + shape::Text::new(base, &text_fmt) + .with_font(WORD_FONT) + .with_fg(theme::FG) + .render(target); + }); + y_offset += line_height; + } + } +} + +impl<'a> Paginate for ShareWords<'a> { + fn page_count(&self) -> usize { + self.total_page_count() + } + + fn change_page(&mut self, active_page: usize) { + self.page_index = active_page; + } +} + +// DEBUG-ONLY SECTION BELOW + +#[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 mut content = heapless::String::<64>::new(); + for (word_idx, word) in self + .share_words + .iter() + .enumerate() + .skip(self.page_index * WORDS_PER_PAGE) + .take(WORDS_PER_PAGE) + { + let ordinal = word_idx + 1; + word.map(|w| unwrap!(uwrite!(content, "{}. {}\n", ordinal, w))); + } + t.string("screen_content", content.as_str().into()); + } +} diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 53b6f28b66..5a382e23d9 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -51,7 +51,7 @@ use crate::{ layout::{ obj::{ComponentMsgObj, LayoutObj}, result::{CANCELLED, CONFIRMED, INFO}, - util::{upy_disable_animation, ConfirmBlob, PropsList, RecoveryType}, + util::{ConfirmBlob, PropsList, RecoveryType}, }, model_tt::component::check_homescreen_format, }, @@ -317,1772 +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()?; - let verb: Option = kwargs - .get(Qstr::MP_QSTR_verb) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); - for item in IterBuf::new().try_iterate(items)? { - if item.is_str() { - ops = ops.text_normal(TString::try_from(item)?) - } else { - let [emphasis, text]: [Obj; 2] = util::iter_into_array(item)?; - let text: TString = text.try_into()?; - if emphasis.try_into()? { - ops = ops.text_demibold(text); - } else { - ops = ops.text_normal(text); - } - } - } - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - ButtonPage::new(FormattedText::new(ops).vertically_centered(), theme::BG) - .with_cancel_confirm(None, verb), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -struct ConfirmBlobParams { - title: TString<'static>, - subtitle: Option>, - data: Obj, - description: Option>, - extra: Option>, - verb: Option>, - verb_cancel: Option>, - info_button: bool, - hold: bool, - chunkify: bool, - text_mono: bool, -} - -impl ConfirmBlobParams { - fn new( - title: TString<'static>, - data: Obj, - description: Option>, - verb: Option>, - verb_cancel: Option>, - hold: bool, - ) -> Self { - Self { - title, - subtitle: None, - data, - description, - extra: None, - verb, - verb_cancel, - info_button: false, - hold, - chunkify: false, - text_mono: true, - } - } - - fn with_extra(mut self, extra: Option>) -> Self { - self.extra = extra; - self - } - - fn with_subtitle(mut self, subtitle: Option>) -> Self { - self.subtitle = subtitle; - self - } - - fn with_info_button(mut self, info_button: bool) -> Self { - self.info_button = info_button; - self - } - - fn with_chunkify(mut self, chunkify: bool) -> Self { - self.chunkify = chunkify; - self - } - - fn with_text_mono(mut self, text_mono: bool) -> Self { - self.text_mono = text_mono; - self - } - - fn into_layout(self) -> Result { - let paragraphs = ConfirmBlob { - description: self.description.unwrap_or("".into()), - extra: self.extra.unwrap_or("".into()), - data: self.data.try_into()?, - description_font: &theme::TEXT_NORMAL, - extra_font: &theme::TEXT_DEMIBOLD, - data_font: if self.chunkify { - let data: TString = self.data.try_into()?; - theme::get_chunkified_text_style(data.len()) - } else if self.text_mono { - &theme::TEXT_MONO - } else { - &theme::TEXT_NORMAL - }, - } - .into_paragraphs(); - - let mut page = ButtonPage::new(paragraphs, theme::BG); - if let Some(verb) = self.verb { - page = page.with_cancel_confirm(self.verb_cancel, Some(verb)) - } - if self.hold { - page = page.with_hold()? - } - let mut frame = Frame::left_aligned(theme::label_title(), self.title, page); - if let Some(subtitle) = self.subtitle { - frame = frame.with_subtitle(theme::label_subtitle(), subtitle); - } - - if self.info_button { - frame = frame.with_info_button(); - } - let obj = LayoutObj::new(frame)?; - Ok(obj.into()) - } -} - -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()?; - let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; - let extra: Option = kwargs - .get(Qstr::MP_QSTR_extra) - .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 info: bool = kwargs.get_or(Qstr::MP_QSTR_info, false)?; - let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - ConfirmBlobParams::new(title, data, description, verb, verb_cancel, hold) - .with_text_mono(text_mono) - .with_extra(extra) - .with_chunkify(chunkify) - .with_info_button(info) - .into_layout() - }; - 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()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let verb: TString = kwargs.get_or(Qstr::MP_QSTR_verb, TR::buttons__confirm.into())?; - let extra: Option = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; - let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - let data_style = if chunkify { - let address: TString = data.try_into()?; - theme::get_chunkified_text_style(address.len()) - } else { - &theme::TEXT_MONO - }; - - let paragraphs = ConfirmBlob { - description: description.unwrap_or("".into()), - extra: extra.unwrap_or("".into()), - data: data.try_into()?, - description_font: &theme::TEXT_NORMAL, - extra_font: &theme::TEXT_DEMIBOLD, - data_font: data_style, - } - .into_paragraphs(); - - let obj = LayoutObj::new( - Frame::left_aligned( - theme::label_title(), - title, - ButtonPage::new(paragraphs, theme::BG) - .with_swipe_left() - .with_cancel_confirm(None, Some(verb)), - ) - .with_info_button(), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_properties(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 hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let paragraphs = PropsList::new( - items, - &theme::TEXT_NORMAL, - &theme::TEXT_MONO, - &theme::TEXT_MONO, - )?; - let page = if hold { - ButtonPage::new(paragraphs.into_paragraphs(), theme::BG).with_hold()? - } else { - ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) - .with_cancel_confirm(None, Some(TR::buttons__confirm.into())) - }; - 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_homescreen(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 image: Obj = kwargs.get(Qstr::MP_QSTR_image)?; - - let mut jpeg: BinaryData = image.try_into()?; - - if jpeg.is_empty() { - // Incoming data may be empty, meaning we should - // display default homescreen image. - jpeg = theme::IMAGE_HOMESCREEN.into(); - } - - if !check_homescreen_format(jpeg, false) { - return Err(value_error!(c"Invalid image.")); - }; - - let buttons = Button::cancel_confirm_text(None, Some(TR::buttons__change.into())); - let obj = LayoutObj::new(Frame::centered( - theme::label_title(), - title, - Dialog::new(Jpeg::new(jpeg, 1), buttons), - ))?; - Ok(obj.into()) - }; - - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_reset_device(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 par_array: [Paragraph<'static>; 3] = [ - Paragraph::new(&theme::TEXT_NORMAL, TR::reset__by_continuing).with_bottom_padding(17), /* simulating a carriage return */ - Paragraph::new(&theme::TEXT_NORMAL, TR::reset__more_info_at), - Paragraph::new(&theme::TEXT_DEMIBOLD, TR::reset__tos_link), - ]; - let paragraphs = Paragraphs::new(par_array); - let buttons = Button::cancel_confirm( - Button::with_icon(theme::ICON_CANCEL), - Button::with_text(button).styled(theme::button_confirm()), - true, - ); - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - Dialog::new(paragraphs, buttons), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_address_details(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let qr_title: TString<'static> = kwargs.get(Qstr::MP_QSTR_qr_title)?.try_into()?; - let details_title: TString = kwargs.get(Qstr::MP_QSTR_details_title)?.try_into()?; - let address: TString = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?; - let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?; - let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; - let path: Option = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?; - - let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?; - - let mut ad = AddressDetails::new( - qr_title, - address, - case_sensitive, - details_title, - account, - path, - )?; - - for i in IterBuf::new().try_iterate(xpubs)? { - let [xtitle, text]: [TString; 2] = util::iter_into_array(i)?; - ad.add_xpub(xtitle, text)?; - } - - let obj = - LayoutObj::new(SimplePage::horizontal(ad, theme::BG).with_swipe_right_to_go_back())?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_info_with_cancel(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 items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - let horizontal: bool = kwargs.get_or(Qstr::MP_QSTR_horizontal, false)?; - let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - - let mut paragraphs = ParagraphVecShort::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [key, value]: [Obj; 2] = util::iter_into_array(para)?; - let key: TString = key.try_into()?; - let value: TString = value.try_into()?; - paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, key).no_break()); - if chunkify { - paragraphs.add(Paragraph::new( - theme::get_chunkified_text_style(value.len()), - value, - )); - } else { - paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); - } - } - - let axis = match horizontal { - true => geometry::Axis::Horizontal, - _ => geometry::Axis::Vertical, - }; - - let obj = LayoutObj::new( - Frame::left_aligned( - theme::label_title(), - title, - SimplePage::new(paragraphs.into_paragraphs(), axis, theme::BG) - .with_swipe_right_to_go_back(), - ) - .with_cancel_button(), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_value(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 subtitle: Option = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into_option()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let value: Obj = kwargs.get(Qstr::MP_QSTR_value)?; - let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false)?; - - 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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; - let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; - - ConfirmBlobParams::new(title, value, description, verb, verb_cancel, hold) - .with_subtitle(subtitle) - .with_info_button(info_button) - .with_chunkify(chunkify) - .with_text_mono(text_mono) - .into_layout() - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let amount: TString = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?; - let amount_label: TString = kwargs.get(Qstr::MP_QSTR_amount_label)?.try_into()?; - let fee: TString = kwargs.get(Qstr::MP_QSTR_fee)?.try_into()?; - let fee_label: TString = kwargs.get(Qstr::MP_QSTR_fee_label)?.try_into()?; - let title: Option = kwargs - .get(Qstr::MP_QSTR_title) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let account_items: Option = kwargs - .get(Qstr::MP_QSTR_account_items) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let extra_items: Option = kwargs - .get(Qstr::MP_QSTR_extra_items) - .unwrap_or_else(|_| Obj::const_none()) - .try_into_option()?; - let _extra_title: Option = kwargs - .get(Qstr::MP_QSTR_extra_title) - .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 info_button: bool = account_items.is_some() || extra_items.is_some(); - let paragraphs = ParagraphVecShort::from_iter([ - Paragraph::new(&theme::TEXT_NORMAL, amount_label).no_break(), - Paragraph::new(&theme::TEXT_MONO, amount), - Paragraph::new(&theme::TEXT_NORMAL, fee_label).no_break(), - Paragraph::new(&theme::TEXT_MONO, fee), - ]); - - let mut page = ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) - .with_hold()? - .with_cancel_button(verb_cancel); - if info_button { - page = page.with_swipe_left(); - } - let mut frame = Frame::left_aligned( - theme::label_title(), - title.unwrap_or(TString::empty()), - page, - ); - if info_button { - frame = frame.with_info_button(); - } - let obj = LayoutObj::new(frame)?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; - let amount_change: TString = kwargs.get(Qstr::MP_QSTR_amount_change)?.try_into()?; - let amount_new: TString = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?; - - let description = if sign < 0 { - TR::modify_amount__decrease_amount - } else { - TR::modify_amount__increase_amount - }; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, description), - Paragraph::new(&theme::TEXT_MONO, amount_change), - Paragraph::new(&theme::TEXT_NORMAL, TR::modify_amount__new_amount), - Paragraph::new(&theme::TEXT_MONO, amount_new), - ]); - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - TR::modify_amount__title.into(), - ButtonPage::new(paragraphs, theme::BG) - .with_cancel_confirm(Some("^".into()), Some(TR::buttons__continue.into())), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_modify_fee(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 sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; - let user_fee_change: TString = kwargs.get(Qstr::MP_QSTR_user_fee_change)?.try_into()?; - let total_fee_new: TString = kwargs.get(Qstr::MP_QSTR_total_fee_new)?.try_into()?; - - let (description, change, total_label) = match sign { - s if s < 0 => ( - TR::modify_fee__decrease_fee, - user_fee_change, - TR::modify_fee__new_transaction_fee, - ), - s if s > 0 => ( - TR::modify_fee__increase_fee, - user_fee_change, - TR::modify_fee__new_transaction_fee, - ), - _ => ( - TR::modify_fee__no_change, - "".into(), - TR::modify_fee__transaction_fee, - ), - }; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, description), - Paragraph::new(&theme::TEXT_MONO, change), - Paragraph::new(&theme::TEXT_NORMAL, total_label), - Paragraph::new(&theme::TEXT_MONO, total_fee_new), - ]); - - let obj = LayoutObj::new( - Frame::left_aligned( - theme::label_title(), - title, - ButtonPage::new(paragraphs, theme::BG) - .with_hold()? - .with_swipe_left(), - ) - .with_info_button(), - )?; - Ok(obj.into()) - }; - 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()?; - let app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; - let icon: Option = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?; - let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; - - // Cache the page count so that we can move `accounts` into the closure. - let page_count = accounts.len(); - // Closure to lazy-load the information on given page index. - // Done like this to allow arbitrarily many pages without - // the need of any allocation here in Rust. - let get_page = move |page_index| { - let account = unwrap!(accounts.get(page_index)); - account.try_into().unwrap_or_else(|_| "".into()) - }; - - let controls = Button::cancel_confirm( - Button::with_icon(theme::ICON_CANCEL), - Button::with_text(TR::buttons__confirm.into()).styled(theme::button_confirm()), - true, - ); - - let fido_page = FidoConfirm::new(app_name, get_page, page_count, icon, controls); - - let obj = LayoutObj::new(Frame::centered(theme::label_title(), title, fido_page))?; - Ok(obj.into()) - }; - 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_mismatch(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 = TR::addr_mismatch__contact_support_at.into(); - let url: TString = TR::addr_mismatch__support_url.into(); - let button: TString = TR::buttons__quit.into(); - - let icon = BlendedImage::new( - theme::IMAGE_BG_OCTAGON, - theme::IMAGE_FG_WARN, - theme::WARN_COLOR, - theme::FG, - theme::BG, - ); - let obj = LayoutObj::new( - IconDialog::new( - icon, - title, - Button::cancel_confirm( - Button::with_icon(theme::ICON_BACK), - Button::with_text(button).styled(theme::button_reset()), - true, - ), - ) - .with_paragraph( - Paragraph::new(&theme::TEXT_NORMAL, description) - .centered() - .with_bottom_padding( - theme::TEXT_NORMAL.text_font.text_height() - - theme::TEXT_DEMIBOLD.text_font.text_height(), - ), - ) - .with_text(&theme::TEXT_DEMIBOLD, url), - )?; - - 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 title: Option = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?; - let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; - let button: TString = kwargs.get_or(Qstr::MP_QSTR_button, "".into())?; - - let obj = if let Some(t) = title { - LayoutObj::new(Frame::left_aligned( - theme::label_title(), - t, - Dialog::new( - Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]), - theme::button_bar(Button::with_text(button).map(|msg| { - (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) - })), - ), - ))? - .into() - } else if !button.is_empty() { - LayoutObj::new(Border::new( - theme::borders(), - Dialog::new( - Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]), - theme::button_bar(Button::with_text(button).map(|msg| { - (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) - })), - ), - ))? - .into() - } else { - LayoutObj::new(Border::new( - theme::borders(), - Dialog::new( - Paragraphs::new( - [Paragraph::new(&theme::TEXT_DEMIBOLD, description).centered()], - ), - Empty, - ), - ))? - .into() - }; - - Ok(obj) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_with_info(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 info_button: TString = kwargs.get(Qstr::MP_QSTR_info_button)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecShort::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [font, text]: [Obj; 2] = util::iter_into_array(para)?; - let style: &TextStyle = theme::textstyle_number(font.try_into()?); - let text: TString = text.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - if paragraphs.is_full() { - break; - } - } - - let buttons = Button::cancel_info_confirm(button, info_button); - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - Dialog::new(paragraphs.into_paragraphs(), buttons), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_more(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 button_style_confirm: bool = - kwargs.get_or(Qstr::MP_QSTR_button_style_confirm, false)?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - - for para in IterBuf::new().try_iterate(items)? { - let [font, text]: [Obj; 2] = util::iter_into_array(para)?; - let style: &TextStyle = theme::textstyle_number(font.try_into()?); - let text: TString = text.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) - .with_cancel_confirm(None, Some(button)) - .with_confirm_style(if button_style_confirm { - theme::button_confirm() - } else { - theme::button_default() - }) - .with_back_button(), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let max_rounds: TString = kwargs.get(Qstr::MP_QSTR_max_rounds)?.try_into()?; - let max_feerate: TString = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_rounds), - Paragraph::new(&theme::TEXT_MONO, max_rounds), - Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_mining_fee), - Paragraph::new(&theme::TEXT_MONO, max_feerate), - ]); - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - TR::coinjoin__title.into(), - ButtonPage::new(paragraphs, theme::BG).with_hold()?, - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_pin(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()?; - let subprompt: TString = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?; - let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?; - let warning: bool = kwargs.get_or(Qstr::MP_QSTR_wrong_pin, false)?; - let warning = if warning { - Some(TR::pin__wrong_pin.into()) - } else { - None - }; - let obj = LayoutObj::new(PinKeyboard::new(prompt, subprompt, warning, allow_cancel))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_passphrase(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()?; - let _max_len: u32 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?; - let obj = LayoutObj::new(PassphraseKeyboard::new())?; - Ok(obj.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()?; - let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; - let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; - let obj = LayoutObj::new(MnemonicKeyboard::new( - prefill_word.map(Bip39Input::prefilled_word), - prompt, - can_go_back, - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_slip39(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()?; - let prefill_word: TString = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?; - let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?; - let obj = LayoutObj::new(MnemonicKeyboard::new( - prefill_word.map(Slip39Input::prefilled_word), - prompt, - can_go_back, - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_select_word(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 words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?; - let words: [TString<'static>; 3] = util::iter_into_array(words_iterable)?; - - let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, description)]); - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - Dialog::new(paragraphs, Button::select_word(words)), - ))?; - Ok(obj.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()?; - let pages: Obj = kwargs.get(Qstr::MP_QSTR_pages)?; - - let mut paragraphs = ParagraphVecLong::new(); - for page in IterBuf::new().try_iterate(pages)? { - let text: TString = page.try_into()?; - paragraphs.add(Paragraph::new(&theme::TEXT_MONO, text).break_after()); - } - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) - .with_hold()? - .without_cancel(), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_request_number(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 min_count: u32 = kwargs.get(Qstr::MP_QSTR_min_count)?.try_into()?; - let max_count: u32 = kwargs.get(Qstr::MP_QSTR_max_count)?.try_into()?; - let count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?; - let description_callback: Obj = kwargs.get(Qstr::MP_QSTR_description)?; - assert!(description_callback != Obj::const_none()); - - let callback = move |i: u32| { - TString::try_from( - description_callback - .call_with_n_args(&[i.try_into().unwrap()]) - .unwrap(), - ) - .unwrap() - }; - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - NumberInputDialog::new(min_count, max_count, count, callback)?, - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_set_brightness(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let current: Option = kwargs.get(Qstr::MP_QSTR_current)?.try_into_option()?; - let obj = LayoutObj::new(Frame::centered( - theme::label_title(), - TR::brightness__title.into(), - SetBrightnessDialog::new(current), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_checklist(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 active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() { - let style = match i.cmp(&active) { - Ordering::Less => &theme::TEXT_CHECKLIST_DONE, - Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, - Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, - }; - let text: TString = item.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - Dialog::new( - Checklist::from_paragraphs( - theme::ICON_LIST_CURRENT, - theme::ICON_LIST_CHECK, - active, - paragraphs - .into_paragraphs() - .with_spacing(theme::CHECKLIST_SPACING), - ) - .with_check_width(theme::CHECKLIST_CHECK_WIDTH) - .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET) - .with_done_offset(theme::CHECKLIST_DONE_OFFSET), - theme::button_bar(Button::with_text(button).map(|msg| { - (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) - })), - ), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_confirm_recovery(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 button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; - let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false)?; - - let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_DEMIBOLD, title), - Paragraph::new(&theme::TEXT_NORMAL, description), - ]) - .with_spacing(theme::RECOVERY_SPACING); - - let notification = match recovery_type { - RecoveryType::DryRun => TR::recovery__title_dry_run.into(), - RecoveryType::UnlockRepeatedBackup => TR::recovery__title_dry_run.into(), - _ => TR::recovery__title.into(), - }; - - let obj = if info_button { - LayoutObj::new(Frame::left_aligned( - theme::label_title(), - notification, - Dialog::new( - paragraphs, - Button::cancel_info_confirm( - TR::buttons__continue.into(), - TR::buttons__more_info.into(), - ), - ), - ))? - } else { - LayoutObj::new(Frame::left_aligned( - theme::label_title(), - notification, - Dialog::new(paragraphs, Button::cancel_confirm_text(None, Some(button))), - ))? - }; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; - let title: TString = match recovery_type { - RecoveryType::DryRun => TR::recovery__title_dry_run.into(), - RecoveryType::UnlockRepeatedBackup => TR::recovery__title_dry_run.into(), - _ => TR::recovery__title.into(), - }; - - let paragraphs = Paragraphs::new(Paragraph::new( - &theme::TEXT_DEMIBOLD, - TR::recovery__num_of_words, - )); - - let content = if matches!(recovery_type, RecoveryType::UnlockRepeatedBackup) { - SelectWordCount::new_multishare() - } else { - SelectWordCount::new_all() - }; - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - Dialog::new(paragraphs, content), - ))?; - Ok(obj.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, - kwargs: *mut Map, -) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let lines_iterable: Obj = kwargs.get(Qstr::MP_QSTR_lines)?; - let lines: [TString; 4] = util::iter_into_array(lines_iterable)?; - - let obj = LayoutObj::new(IconDialog::new_shares( - lines, - theme::button_bar(Button::with_text(TR::buttons__continue.into()).map(|msg| { - (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) - })), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let pages_iterable: Obj = kwargs.get(Qstr::MP_QSTR_pages)?; - - let mut paragraphs = ParagraphVecLong::new(); - for page in IterBuf::new().try_iterate(pages_iterable)? { - let [title, description]: [TString; 2] = util::iter_into_array(page)?; - paragraphs - .add(Paragraph::new(&theme::TEXT_DEMIBOLD, title)) - .add(Paragraph::new(&theme::TEXT_NORMAL, description).break_after()); - } - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - TR::recovery__title_remaining_shares.into(), - ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) - .with_cancel_confirm(None, Some(TR::buttons__continue.into())) - .with_confirm_style(theme::button_default()) - .without_cancel(), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; - let title: Option = kwargs - .get(Qstr::MP_QSTR_title) - .and_then(Obj::try_into_option) - .unwrap_or(None); - - let (title, description) = if let Some(title) = title { - (title, description) - } else { - (description, "".into()) - }; - - let obj = LayoutObj::new(Progress::new(title, indeterminate, description))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_progress_coinjoin(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 indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; - let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; - let skip_first_paint: bool = kwargs.get_or(Qstr::MP_QSTR_skip_first_paint, false)?; - - // The second type parameter is actually not used in `new()` but we need to - // provide it. - let progress = CoinJoinProgress::::new(title, indeterminate)?; - let obj = if time_ms > 0 && indeterminate { - let timeout = Timeout::new(time_ms); - LayoutObj::new((timeout, progress.map(|_msg| None)))? - } else { - LayoutObj::new(progress)? - }; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let label: TString<'static> = kwargs - .get(Qstr::MP_QSTR_label)? - .try_into_option()? - .unwrap_or_else(|| model::FULL_NAME.into()); - let notification: Option> = - kwargs.get(Qstr::MP_QSTR_notification)?.try_into_option()?; - let notification_level: u8 = kwargs.get_or(Qstr::MP_QSTR_notification_level, 0)?; - let hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?; - let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - - let notification = notification.map(|w| (w, notification_level)); - let obj = LayoutObj::new(Homescreen::new(label, notification, hold))?; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let label: TString<'static> = kwargs - .get(Qstr::MP_QSTR_label)? - .try_into_option()? - .unwrap_or_else(|| model::FULL_NAME.into()); - let bootscreen: bool = kwargs.get(Qstr::MP_QSTR_bootscreen)?.try_into()?; - let coinjoin_authorized: bool = kwargs.get_or(Qstr::MP_QSTR_coinjoin_authorized, false)?; - let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - - let obj = LayoutObj::new(Lockscreen::new(label, bootscreen, coinjoin_authorized))?; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - -pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj { - let block = || { - let buffer = data.try_into()?; - Ok(check_homescreen_format(buffer, false).into()) - }; - - unsafe { util::try_or_raise(block) } -} - -#[no_mangle] -extern "C" fn new_confirm_firmware_update( - n_args: usize, - args: *const Obj, - kwargs: *mut Map, -) -> Obj { - use super::component::bl_confirm::{Confirm, ConfirmTitle}; - let block = move |_args: &[Obj], kwargs: &Map| { - let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let fingerprint: TString = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?; - - let title_str = TR::firmware_update__title.into(); - let title = Label::left_aligned(title_str, theme::TEXT_BOLD).vertically_centered(); - let msg = Label::left_aligned(description, theme::TEXT_NORMAL); - - let left = Button::with_text(TR::buttons__cancel.into()).styled(theme::button_default()); - let right = Button::with_text(TR::buttons__install.into()).styled(theme::button_confirm()); - - let obj = LayoutObj::new( - Confirm::new(theme::BG, left, right, ConfirmTitle::Text(title), msg).with_info( - TR::firmware_update__title_fingerprint.into(), - fingerprint, - theme::button_moreinfo(), - ), - )?; - Ok(obj.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()?; - let obj = LayoutObj::new(Connect::new(message, theme::FG, theme::BG))?; - Ok(obj.into()) - }; - - unsafe { util::try_or_raise(block) } -} - -#[no_mangle] -pub static mp_module_trezorui2: Module = obj_module! { - /// from trezor import utils - /// from trezorui_api import * - /// - /// def disable_animation(disable: bool) -> None: - /// """Disable animations, debug builds only.""" - Qstr::MP_QSTR_disable_animation => obj_fn_1!(upy_disable_animation).as_obj(), - - /// def check_homescreen_format(data: bytes) -> bool: - /// """Check homescreen format and dimensions.""" - Qstr::MP_QSTR_check_homescreen_format => obj_fn_1!(upy_check_homescreen_format).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 confirm_emphasized( - /// *, - /// title: str, - /// items: Iterable[str | tuple[bool, str]], - /// verb: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm formatted text that has been pre-split in python. For tuples - /// the first component is a bool indicating whether this part is emphasized.""" - Qstr::MP_QSTR_confirm_emphasized => obj_fn_kw!(0, new_confirm_emphasized).as_obj(), - - /// def confirm_homescreen( - /// *, - /// title: str, - /// image: bytes, - /// ) -> LayoutObj[UiResult]: - /// """Confirm homescreen.""" - Qstr::MP_QSTR_confirm_homescreen => obj_fn_kw!(0, new_confirm_homescreen).as_obj(), - - /// def confirm_blob( - /// *, - /// title: str, - /// data: str | bytes, - /// description: str | None, - /// text_mono: bool = True, - /// extra: str | None = None, - /// subtitle: str | None = None, - /// verb: str | None = None, - /// verb_cancel: str | None = None, - /// verb_info: str | None = None, - /// info: bool = True, - /// hold: bool = False, - /// chunkify: bool = False, - /// page_counter: bool = False, - /// prompt_screen: bool = False, - /// cancel: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm byte sequence data.""" - Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), - - /// def confirm_address( - /// *, - /// title: str, - /// data: str | bytes, - /// description: str | None, - /// verb: str | None = "CONFIRM", - /// extra: str | None, - /// chunkify: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm address. Similar to `confirm_blob` but has corner info button - /// and allows left swipe which does the same thing as the button.""" - Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(), - - /// def confirm_properties( - /// *, - /// title: str, - /// items: list[tuple[str | None, str | bytes | None, bool]], - /// hold: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Confirm list of key-value pairs. The third component in the tuple should be True if - /// the value is to be rendered as binary with monospace font, False otherwise.""" - Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(), - - /// def confirm_reset_device( - /// *, - /// title: str, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm TOS before device setup.""" - Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, new_confirm_reset_device).as_obj(), - - /// def show_address_details( - /// *, - /// qr_title: str, - /// address: str, - /// case_sensitive: bool, - /// details_title: str, - /// account: str | None, - /// path: str | None, - /// xpubs: list[tuple[str, str]], - /// ) -> LayoutObj[UiResult]: - /// """Show address details - QR code, account, path, cosigner xpubs.""" - Qstr::MP_QSTR_show_address_details => obj_fn_kw!(0, new_show_address_details).as_obj(), - - /// def show_info_with_cancel( - /// *, - /// title: str, - /// items: Iterable[Tuple[str, str]], - /// horizontal: bool = False, - /// chunkify: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Show metadata for outgoing transaction.""" - Qstr::MP_QSTR_show_info_with_cancel => obj_fn_kw!(0, new_show_info_with_cancel).as_obj(), - - /// def confirm_value( - /// *, - /// title: str, - /// value: str, - /// description: str | None, - /// subtitle: str | None, - /// verb: str | None = None, - /// verb_info: str | None = None, - /// verb_cancel: str | None = None, - /// info_button: bool = False, - /// hold: bool = False, - /// chunkify: bool = False, - /// text_mono: bool = True, - /// ) -> LayoutObj[UiResult]: - /// """Confirm value. Merge of confirm_total and confirm_output.""" - Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(), - - /// def confirm_summary( - /// *, - /// amount: str, - /// amount_label: str, - /// fee: str, - /// fee_label: str, - /// title: str | None = None, - /// account_items: Iterable[tuple[str, str]] | None = None, - /// extra_items: Iterable[tuple[str, str]] | None = None, - /// extra_title: str | None = None, - /// verb_cancel: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Confirm summary of a transaction.""" - Qstr::MP_QSTR_confirm_summary => obj_fn_kw!(0, new_confirm_summary).as_obj(), - - /// def confirm_modify_output( - /// *, - /// sign: int, - /// amount_change: str, - /// amount_new: str, - /// ) -> LayoutObj[UiResult]: - /// """Decrease or increase output amount.""" - Qstr::MP_QSTR_confirm_modify_output => obj_fn_kw!(0, new_confirm_modify_output).as_obj(), - - /// def confirm_modify_fee( - /// *, - /// title: str, - /// sign: int, - /// user_fee_change: str, - /// total_fee_new: str, - /// fee_rate_amount: str | None, # ignored - /// ) -> LayoutObj[UiResult]: - /// """Decrease or increase transaction fee.""" - Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(), - - /// def confirm_fido( - /// *, - /// title: str, - /// app_name: str, - /// icon_name: str | None, - /// accounts: list[str | None], - /// ) -> LayoutObj[int | UiResult]: - /// """FIDO confirmation. - /// - /// Returns page index in case of confirmation and CANCELLED otherwise. - /// """ - 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_mismatch(*, title: str) -> LayoutObj[UiResult]: - /// """Warning modal, receiving address mismatch.""" - Qstr::MP_QSTR_show_mismatch => obj_fn_kw!(0, new_show_mismatch).as_obj(), - - /// def show_simple( - /// *, - /// title: str | None, - /// description: str = "", - /// button: str = "", - /// ) -> LayoutObj[UiResult]: - /// """Simple dialog with text and one button.""" - Qstr::MP_QSTR_show_simple => obj_fn_kw!(0, new_show_simple).as_obj(), - - /// def confirm_with_info( - /// *, - /// title: str, - /// button: str, - /// info_button: str, - /// items: Iterable[tuple[int, str | bytes]], - /// ) -> LayoutObj[UiResult]: - /// """Confirm given items but with third button. Always single page - /// without scrolling.""" - Qstr::MP_QSTR_confirm_with_info => obj_fn_kw!(0, new_confirm_with_info).as_obj(), - - /// def confirm_more( - /// *, - /// title: str, - /// button: str, - /// button_style_confirm: bool = False, - /// items: Iterable[tuple[int, str | bytes]], - /// ) -> LayoutObj[UiResult]: - /// """Confirm long content with the possibility to go back from any page. - /// Meant to be used with confirm_with_info.""" - Qstr::MP_QSTR_confirm_more => obj_fn_kw!(0, new_confirm_more).as_obj(), - - /// def confirm_coinjoin( - /// *, - /// max_rounds: str, - /// max_feerate: str, - /// ) -> LayoutObj[UiResult]: - /// """Confirm coinjoin authorization.""" - Qstr::MP_QSTR_confirm_coinjoin => obj_fn_kw!(0, new_confirm_coinjoin).as_obj(), - - /// def request_pin( - /// *, - /// prompt: str, - /// subprompt: str, - /// allow_cancel: bool = True, - /// wrong_pin: bool = False, - /// ) -> LayoutObj[str | UiResult]: - /// """Request pin on device.""" - Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(), - - /// def request_passphrase( - /// *, - /// prompt: str, - /// max_len: int, - /// ) -> LayoutObj[str | UiResult]: - /// """Passphrase input keyboard.""" - Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(), - - /// def request_bip39( - /// *, - /// prompt: str, - /// prefill_word: str, - /// can_go_back: bool, - /// ) -> LayoutObj[str]: - /// """BIP39 word input keyboard.""" - Qstr::MP_QSTR_request_bip39 => obj_fn_kw!(0, new_request_bip39).as_obj(), - - /// def request_slip39( - /// *, - /// prompt: str, - /// prefill_word: str, - /// can_go_back: bool, - /// ) -> LayoutObj[str]: - /// """SLIP39 word input keyboard.""" - Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(), - - /// def select_word( - /// *, - /// title: str, - /// description: str, - /// words: Iterable[str], - /// ) -> LayoutObj[int]: - /// """Select mnemonic word from three possibilities - seed check after backup. The - /// iterable must be of exact size. Returns index in range `0..3`.""" - Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(), - - /// def show_share_words( - /// *, - /// title: str, - /// pages: Iterable[str], - /// ) -> LayoutObj[UiResult]: - /// """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" - Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), - - /// def request_number( - /// *, - /// title: str, - /// count: int, - /// min_count: int, - /// max_count: int, - /// description: Callable[[int], str] | None = None, - /// ) -> LayoutObj[tuple[UiResult, int]]: - /// """Number input with + and - buttons, description, and info button.""" - Qstr::MP_QSTR_request_number => obj_fn_kw!(0, new_request_number).as_obj(), - - /// def set_brightness( - /// *, - /// current: int | None = None - /// ) -> LayoutObj[UiResult]: - /// """Show the brightness configuration dialog.""" - Qstr::MP_QSTR_set_brightness => obj_fn_kw!(0, new_set_brightness).as_obj(), - - /// def show_checklist( - /// *, - /// title: str, - /// items: Iterable[str], - /// active: int, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Checklist of backup steps. Active index is highlighted, previous items have check - /// mark next to them.""" - Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), - - /// def confirm_recovery( - /// *, - /// title: str, - /// description: str, - /// button: str, - /// recovery_type: RecoveryType, - /// info_button: bool = False, - /// show_instructions: bool = False, # unused on TT - /// ) -> LayoutObj[UiResult]: - /// """Device recovery homescreen.""" - Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(), - - /// def select_word_count( - /// *, - /// recovery_type: RecoveryType, - /// ) -> LayoutObj[int | str]: # TT returns int - /// """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. - /// For unlocking a repeated backup, select from 20 or 33.""" - Qstr::MP_QSTR_select_word_count => obj_fn_kw!(0, new_select_word_count).as_obj(), - - /// def show_group_share_success( - /// *, - /// lines: Iterable[str] - /// ) -> LayoutObj[UiResult]: - /// """Shown after successfully finishing a group.""" - Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(), - - /// def show_remaining_shares( - /// *, - /// pages: Iterable[tuple[str, str]], - /// ) -> LayoutObj[UiResult]: - /// """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_progress( - /// *, - /// description: str, - /// indeterminate: bool = False, - /// title: str | None = None, - /// ) -> LayoutObj[UiResult]: - /// """Show progress loader. Please note that the number of lines reserved on screen for - /// description is determined at construction time. If you want multiline descriptions - /// make sure the initial description has at least that amount of lines.""" - Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(), - - /// def show_progress_coinjoin( - /// *, - /// title: str, - /// indeterminate: bool = False, - /// time_ms: int = 0, - /// skip_first_paint: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - /// time_ms timeout is passed.""" - Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(), - - /// def show_homescreen( - /// *, - /// label: str | None, - /// hold: bool, - /// notification: str | None, - /// notification_level: int = 0, - /// skip_first_paint: bool, - /// ) -> LayoutObj[UiResult]: - /// """Idle homescreen.""" - Qstr::MP_QSTR_show_homescreen => obj_fn_kw!(0, new_show_homescreen).as_obj(), - - /// def show_lockscreen( - /// *, - /// label: str | None, - /// bootscreen: bool, - /// skip_first_paint: bool, - /// coinjoin_authorized: bool = False, - /// ) -> LayoutObj[UiResult]: - /// """Homescreen for locked device.""" - Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), - - /// def confirm_firmware_update( - /// *, - /// description: str, - /// fingerprint: str, - /// ) -> LayoutObj[UiResult]: - /// """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" - Qstr::MP_QSTR_confirm_firmware_update => obj_fn_kw!(0, new_confirm_firmware_update).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(), -}; #[cfg(test)] mod tests { diff --git a/core/embed/rust/src/ui/model_tt/mod.rs b/core/embed/rust/src/ui/model_tt/mod.rs index e3570ac104..4554c422dc 100644 --- a/core/embed/rust/src/ui/model_tt/mod.rs +++ b/core/embed/rust/src/ui/model_tt/mod.rs @@ -19,6 +19,8 @@ use crate::ui::{ }; pub struct ModelTTFeatures; + +#[cfg(feature = "micropython")] pub mod ui_features_fw; impl UIFeaturesCommon for ModelTTFeatures { 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 129e207db3..1c23d897ee 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 @@ -1,20 +1,888 @@ +use core::cmp::Ordering; + use crate::{ - error::Error, - micropython::gc::Gc, + error::{value_error, Error}, + io::BinaryData, + micropython::{gc::Gc, iter::IterBuf, list::List, obj::Obj, util}, strutil::TString, + translations::TR, ui::{ - component::{image::BlendedImage, ComponentExt, Empty, Timeout}, - layout::obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, + component::{ + connect::Connect, + image::BlendedImage, + text::{ + op::OpTextLayout, + paragraphs::{ + Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, + Paragraphs, VecExt, + }, + TextStyle, + }, + Border, ComponentExt, Empty, FormattedText, Jpeg, Label, Never, Timeout, + }, + geometry, + layout::{ + obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, + util::{ConfirmBlob, PropsList, RecoveryType}, + }, ui_features_fw::UIFeaturesFirmware, }, }; use super::{ - component::{Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, IconDialog}, + component::{ + check_homescreen_format, AddressDetails, Bip39Input, Button, ButtonMsg, ButtonPage, + ButtonStyleSheet, CancelConfirmMsg, CoinJoinProgress, Dialog, FidoConfirm, Frame, + Homescreen, IconDialog, Lockscreen, MnemonicKeyboard, NumberInputDialog, + PassphraseKeyboard, PinKeyboard, Progress, SelectWordCount, SetBrightnessDialog, + ShareWords, SimplePage, Slip39Input, + }, theme, ModelTTFeatures, }; impl UIFeaturesFirmware for ModelTTFeatures { + fn confirm_action( + title: TString<'static>, + action: Option>, + description: Option>, + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + hold: bool, + hold_danger: bool, + reverse: bool, + prompt_screen: bool, + prompt_title: Option>, + ) -> Result { + 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 layout = RootComponent::new(Frame::left_aligned(theme::label_title(), title, page)); + Ok(layout) + } + + fn confirm_address( + title: TString<'static>, + data: Obj, + description: Option>, + extra: Option>, + verb: Option>, + chunkify: bool, + ) -> Result { + let verb = verb.unwrap_or(TR::buttons__confirm.into()); + let data_style = if chunkify { + let address: TString = data.try_into()?; + theme::get_chunkified_text_style(address.len()) + } else { + &theme::TEXT_MONO + }; + + let paragraphs = ConfirmBlob { + description: description.unwrap_or("".into()), + extra: extra.unwrap_or("".into()), + data: data.try_into()?, + description_font: &theme::TEXT_NORMAL, + extra_font: &theme::TEXT_DEMIBOLD, + data_font: data_style, + } + .into_paragraphs(); + + let layout = RootComponent::new( + Frame::left_aligned( + theme::label_title(), + title, + ButtonPage::new(paragraphs, theme::BG) + .with_swipe_left() + .with_cancel_confirm(None, Some(verb)), + ) + .with_info_button(), + ); + Ok(layout) + } + + fn confirm_blob( + title: TString<'static>, + data: Obj, + description: Option>, + text_mono: bool, + extra: Option>, + _subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + _verb_info: Option>, + info: bool, + hold: bool, + chunkify: bool, + _page_counter: bool, + _prompt_screen: bool, + _cancel: bool, + ) -> Result, Error> { + ConfirmBlobParams::new(title, data, description, verb, verb_cancel, hold) + .with_text_mono(text_mono) + .with_extra(extra) + .with_chunkify(chunkify) + .with_info_button(info) + .into_layout() + } + + fn confirm_blob_intro( + _title: TString<'static>, + _data: Obj, + _subtitle: Option>, + _verb: Option>, + _verb_cancel: Option>, + _chunkify: bool, + ) -> Result, Error> { + Err::, Error>(Error::ValueError(c"confirm_blob_intro not implemented")) + } + + fn confirm_homescreen( + title: TString<'static>, + mut image: BinaryData<'static>, + ) -> Result { + if image.is_empty() { + // Incoming data may be empty, meaning we should + // display default homescreen image. + image = theme::IMAGE_HOMESCREEN.into(); + } + + if !check_homescreen_format(image, false) { + return Err(value_error!(c"Invalid image.")); + }; + + let buttons = Button::cancel_confirm_text(None, Some(TR::buttons__change.into())); + let layout = RootComponent::new(Frame::centered( + theme::label_title(), + title, + Dialog::new(Jpeg::new(image, 1), buttons), + )); + Ok(layout) + } + + fn confirm_coinjoin( + max_rounds: TString<'static>, + max_feerate: TString<'static>, + ) -> Result { + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_rounds), + Paragraph::new(&theme::TEXT_MONO, max_rounds), + Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_mining_fee), + Paragraph::new(&theme::TEXT_MONO, max_feerate), + ]); + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + TR::coinjoin__title.into(), + ButtonPage::new(paragraphs, theme::BG).with_hold()?, + )); + Ok(layout) + } + + fn confirm_emphasized( + title: TString<'static>, + items: Obj, + verb: Option>, + ) -> Result { + let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); + for item in IterBuf::new().try_iterate(items)? { + if item.is_str() { + ops = ops.text_normal(TString::try_from(item)?) + } else { + let [emphasis, text]: [Obj; 2] = util::iter_into_array(item)?; + let text: TString = text.try_into()?; + if emphasis.try_into()? { + ops = ops.text_demibold(text); + } else { + ops = ops.text_normal(text); + } + } + } + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + ButtonPage::new(FormattedText::new(ops).vertically_centered(), theme::BG) + .with_cancel_confirm(None, verb), + )); + Ok(layout) + } + + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, + ) -> Result { + // Cache the page count so that we can move `accounts` into the closure. + let page_count = accounts.len(); + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let get_page = move |page_index| { + let account = unwrap!(accounts.get(page_index)); + account.try_into().unwrap_or_else(|_| "".into()) + }; + + let controls = Button::cancel_confirm( + Button::with_icon(theme::ICON_CANCEL), + Button::with_text(TR::buttons__confirm.into()).styled(theme::button_confirm()), + true, + ); + + let fido_page = FidoConfirm::new(app_name, get_page, page_count, icon, controls); + + let layout = RootComponent::new(Frame::centered(theme::label_title(), title, fido_page)); + Ok(layout) + } + + fn confirm_firmware_update( + description: TString<'static>, + fingerprint: TString<'static>, + ) -> Result { + use super::component::bl_confirm::{Confirm, ConfirmTitle}; + + let title_str = TR::firmware_update__title.into(); + let title = Label::left_aligned(title_str, theme::TEXT_BOLD).vertically_centered(); + let msg = Label::left_aligned(description, theme::TEXT_NORMAL); + + let left = Button::with_text(TR::buttons__cancel.into()).styled(theme::button_default()); + let right = Button::with_text(TR::buttons__install.into()).styled(theme::button_confirm()); + + let layout = RootComponent::new( + Confirm::new(theme::BG, left, right, ConfirmTitle::Text(title), msg).with_info( + TR::firmware_update__title_fingerprint.into(), + fingerprint, + theme::button_moreinfo(), + ), + ); + Ok(layout) + } + + fn confirm_modify_fee( + title: TString<'static>, + sign: i32, + user_fee_change: TString<'static>, + total_fee_new: TString<'static>, + fee_rate_amount: Option>, + ) -> Result { + let (description, change, total_label) = match sign { + s if s < 0 => ( + TR::modify_fee__decrease_fee, + user_fee_change, + TR::modify_fee__new_transaction_fee, + ), + s if s > 0 => ( + TR::modify_fee__increase_fee, + user_fee_change, + TR::modify_fee__new_transaction_fee, + ), + _ => ( + TR::modify_fee__no_change, + "".into(), + TR::modify_fee__transaction_fee, + ), + }; + + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_NORMAL, description), + Paragraph::new(&theme::TEXT_MONO, change), + Paragraph::new(&theme::TEXT_NORMAL, total_label), + Paragraph::new(&theme::TEXT_MONO, total_fee_new), + ]); + + let layout = RootComponent::new( + Frame::left_aligned( + theme::label_title(), + title, + ButtonPage::new(paragraphs, theme::BG) + .with_hold()? + .with_swipe_left(), + ) + .with_info_button(), + ); + Ok(layout) + } + + fn confirm_modify_output( + sign: i32, + amount_change: TString<'static>, + amount_new: TString<'static>, + ) -> Result { + let description = if sign < 0 { + TR::modify_amount__decrease_amount + } else { + TR::modify_amount__increase_amount + }; + + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_NORMAL, description), + Paragraph::new(&theme::TEXT_MONO, amount_change), + Paragraph::new(&theme::TEXT_NORMAL, TR::modify_amount__new_amount), + Paragraph::new(&theme::TEXT_MONO, amount_new), + ]); + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + TR::modify_amount__title.into(), + ButtonPage::new(paragraphs, theme::BG) + .with_cancel_confirm(Some("^".into()), Some(TR::buttons__continue.into())), + )); + Ok(layout) + } + + fn confirm_more( + title: TString<'static>, + button: TString<'static>, + button_style_confirm: bool, + items: Obj, + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [font, text]: [Obj; 2] = util::iter_into_array(para)?; + let style: &TextStyle = theme::textstyle_number(font.try_into()?); + let text: TString = text.try_into()?; + paragraphs.add(Paragraph::new(style, text)); + } + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) + .with_cancel_confirm(None, Some(button)) + .with_confirm_style(if button_style_confirm { + theme::button_confirm() + } else { + theme::button_default() + }) + .with_back_button(), + )); + Ok(layout) + } + + fn confirm_properties( + title: TString<'static>, + items: Obj, + hold: bool, + ) -> Result { + let paragraphs = PropsList::new( + items, + &theme::TEXT_NORMAL, + &theme::TEXT_MONO, + &theme::TEXT_MONO, + )?; + let page = if hold { + ButtonPage::new(paragraphs.into_paragraphs(), theme::BG).with_hold()? + } else { + ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) + .with_cancel_confirm(None, Some(TR::buttons__confirm.into())) + }; + let layout = RootComponent::new(Frame::left_aligned(theme::label_title(), title, page)); + Ok(layout) + } + + fn confirm_reset_device(recovery: bool) -> Result { + let (title, button) = if recovery { + ( + TR::recovery__title_recover.into(), + TR::reset__button_recover.into(), + ) + } else { + ( + TR::reset__title_create_wallet.into(), + TR::reset__button_create.into(), + ) + }; + let par_array: [Paragraph<'static>; 3] = [ + Paragraph::new(&theme::TEXT_NORMAL, TR::reset__by_continuing).with_bottom_padding(17), /* simulating a carriage return */ + Paragraph::new(&theme::TEXT_NORMAL, TR::reset__more_info_at), + Paragraph::new(&theme::TEXT_DEMIBOLD, TR::reset__tos_link), + ]; + let paragraphs = Paragraphs::new(par_array); + let buttons = Button::cancel_confirm( + Button::with_icon(theme::ICON_CANCEL), + Button::with_text(button).styled(theme::button_confirm()), + true, + ); + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + Dialog::new(paragraphs, buttons), + )); + Ok(layout) + } + + fn confirm_summary( + amount: TString<'static>, + amount_label: TString<'static>, + fee: TString<'static>, + fee_label: TString<'static>, + title: Option>, + account_items: Option, + extra_items: Option, + extra_title: Option>, + verb_cancel: Option>, + ) -> Result { + let info_button: bool = account_items.is_some() || extra_items.is_some(); + let paragraphs = ParagraphVecShort::from_iter([ + Paragraph::new(&theme::TEXT_NORMAL, amount_label).no_break(), + Paragraph::new(&theme::TEXT_MONO, amount), + Paragraph::new(&theme::TEXT_NORMAL, fee_label).no_break(), + Paragraph::new(&theme::TEXT_MONO, fee), + ]); + + let mut page = ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) + .with_hold()? + .with_cancel_button(verb_cancel); + if info_button { + page = page.with_swipe_left(); + } + let mut frame = Frame::left_aligned( + theme::label_title(), + title.unwrap_or(TString::empty()), + page, + ); + if info_button { + frame = frame.with_info_button(); + } + let layout = RootComponent::new(frame); + Ok(layout) + } + + fn confirm_value( + title: TString<'static>, + value: Obj, + description: Option>, + subtitle: Option>, + verb: Option>, + _verb_info: Option>, + verb_cancel: Option>, + info_button: bool, + hold: bool, + chunkify: bool, + text_mono: bool, + ) -> Result, Error> { + ConfirmBlobParams::new(title, value, description, verb, verb_cancel, hold) + .with_subtitle(subtitle) + .with_info_button(info_button) + .with_chunkify(chunkify) + .with_text_mono(text_mono) + .into_layout() + } + + fn confirm_with_info( + title: TString<'static>, + button: TString<'static>, + info_button: TString<'static>, + _verb_cancel: Option>, + items: Obj, + ) -> Result { + let mut paragraphs = ParagraphVecShort::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [font, text]: [Obj; 2] = util::iter_into_array(para)?; + let style: &TextStyle = theme::textstyle_number(font.try_into()?); + let text: TString = text.try_into()?; + paragraphs.add(Paragraph::new(style, text)); + if paragraphs.is_full() { + break; + } + } + + let buttons = Button::cancel_info_confirm(button, info_button); + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + Dialog::new(paragraphs.into_paragraphs(), buttons), + )); + Ok(layout) + } + + fn check_homescreen_format(image: BinaryData, accept_toif: bool) -> bool { + super::component::check_homescreen_format(image, false) + } + + fn continue_recovery_homepage( + text: TString<'static>, + subtext: Option>, + button: Option>, + recovery_type: RecoveryType, + _show_instructions: bool, + remaining_shares: Option, + ) -> Result, Error> { + let paragraphs = Paragraphs::new([ + Paragraph::new(&theme::TEXT_DEMIBOLD, text), + Paragraph::new(&theme::TEXT_NORMAL, subtext.unwrap_or(TString::empty())), + ]) + .with_spacing(theme::RECOVERY_SPACING); + + let notification = match recovery_type { + RecoveryType::DryRun => TR::recovery__title_dry_run.into(), + RecoveryType::UnlockRepeatedBackup => TR::recovery__title_dry_run.into(), + _ => TR::recovery__title.into(), + }; + + // Model T shows remaining shares info in a separate layout + let show_info_button = remaining_shares.is_some(); + if show_info_button { + LayoutObj::new(Frame::left_aligned( + theme::label_title(), + notification, + Dialog::new( + paragraphs, + Button::cancel_info_confirm( + TR::buttons__continue.into(), + TR::buttons__more_info.into(), + ), + ), + )) + } else { + LayoutObj::new(Frame::left_aligned( + theme::label_title(), + notification, + Dialog::new(paragraphs, Button::cancel_confirm_text(None, button)), + )) + } + } + + fn flow_confirm_output( + _title: Option>, + _subtitle: Option>, + _message: Obj, + _amount: Option, + _chunkify: bool, + _text_mono: bool, + _account: Option>, + _account_path: Option>, + _br_code: u16, + _br_name: TString<'static>, + _address: Option, + _address_title: Option>, + _summary_items: Option, + _fee_items: Option, + _summary_title: Option>, + _summary_br_code: Option, + _summary_br_name: Option>, + _cancel_text: Option>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"flow_confirm_output not supported", + )) + } + + fn flow_confirm_set_new_pin( + _title: TString<'static>, + _description: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"flow_confirm_set_new_pin not supported", + )) + } + + fn flow_get_address( + _address: Obj, + _title: TString<'static>, + _description: Option>, + _extra: Option>, + _chunkify: bool, + _address_qr: TString<'static>, + _case_sensitive: bool, + _account: Option>, + _path: Option>, + _xpubs: Obj, + _title_success: TString<'static>, + _br_code: u16, + _br_name: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"flow_get_address not supported", + )) + } + + fn multiple_pages_texts( + _title: TString<'static>, + _verb: TString<'static>, + _items: Gc, + ) -> Result { + Err::, Error>(Error::ValueError( + c"multiple_pages_texts not implemented", + )) + } + + fn prompt_backup() -> Result { + Err::, Error>(Error::ValueError( + c"prompt_backup not implemented", + )) + } + + fn request_bip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result { + let layout = RootComponent::new(MnemonicKeyboard::new( + prefill_word.map(Bip39Input::prefilled_word), + prompt, + can_go_back, + )); + Ok(layout) + } + + fn request_slip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result { + let layout = RootComponent::new(MnemonicKeyboard::new( + prefill_word.map(Slip39Input::prefilled_word), + prompt, + can_go_back, + )); + + Ok(layout) + } + + fn request_number( + title: TString<'static>, + count: u32, + min_count: u32, + max_count: u32, + _description: Option>, + more_info_callback: Option TString<'static> + 'static>, + ) -> Result { + debug_assert!(more_info_callback.is_some()); + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + NumberInputDialog::new(min_count, max_count, count, more_info_callback.unwrap())?, + )); + Ok(layout) + } + + fn request_pin( + prompt: TString<'static>, + subprompt: TString<'static>, + allow_cancel: bool, + warning: bool, + ) -> Result { + let warning = if warning { + Some(TR::pin__wrong_pin.into()) + } else { + None + }; + let layout = RootComponent::new(PinKeyboard::new(prompt, subprompt, warning, allow_cancel)); + Ok(layout) + } + + fn request_passphrase( + prompt: TString<'static>, + max_len: u32, + ) -> Result { + let layout = RootComponent::new(PassphraseKeyboard::new()); + Ok(layout) + } + + fn select_word( + title: TString<'static>, + description: TString<'static>, + words: [TString<'static>; 3], + ) -> Result { + let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, description)]); + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + Dialog::new(paragraphs, Button::select_word(words)), + )); + Ok(layout) + } + + fn select_word_count(recovery_type: RecoveryType) -> Result { + let title: TString = match recovery_type { + RecoveryType::DryRun => TR::recovery__title_dry_run.into(), + RecoveryType::UnlockRepeatedBackup => TR::recovery__title_dry_run.into(), + _ => TR::recovery__title.into(), + }; + + let paragraphs = Paragraphs::new(Paragraph::new( + &theme::TEXT_DEMIBOLD, + TR::recovery__num_of_words, + )); + + let content = if matches!(recovery_type, RecoveryType::UnlockRepeatedBackup) { + SelectWordCount::new_multishare() + } else { + SelectWordCount::new_all() + }; + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + Dialog::new(paragraphs, content), + )); + Ok(layout) + } + + fn set_brightness(current_brightness: Option) -> Result { + let layout = RootComponent::new(Frame::centered( + theme::label_title(), + TR::brightness__title.into(), + SetBrightnessDialog::new( + current_brightness.unwrap_or(theme::backlight::get_backlight_normal()), + ), + )); + + Ok(layout) + } + + fn show_address_details( + qr_title: TString<'static>, + address: TString<'static>, + case_sensitive: bool, + details_title: TString<'static>, + account: Option>, + path: Option>, + xpubs: Obj, + ) -> Result { + let mut ad = AddressDetails::new( + qr_title, + address, + case_sensitive, + details_title, + account, + path, + )?; + + for i in IterBuf::new().try_iterate(xpubs)? { + let [xtitle, text]: [TString; 2] = util::iter_into_array(i)?; + ad.add_xpub(xtitle, text)?; + } + + let layout = + RootComponent::new(SimplePage::horizontal(ad, theme::BG).with_swipe_right_to_go_back()); + Ok(layout) + } + + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + for (i, item) in items.into_iter().enumerate() { + let style = match i.cmp(&active) { + Ordering::Less => &theme::TEXT_CHECKLIST_DONE, + Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, + Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, + }; + paragraphs.add(Paragraph::new(style, item)); + } + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + Dialog::new( + Checklist::from_paragraphs( + theme::ICON_LIST_CURRENT, + theme::ICON_LIST_CHECK, + active, + paragraphs + .into_paragraphs() + .with_spacing(theme::CHECKLIST_SPACING), + ) + .with_check_width(theme::CHECKLIST_CHECK_WIDTH) + .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET) + .with_done_offset(theme::CHECKLIST_DONE_OFFSET), + theme::button_bar(Button::with_text(button).map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) + })), + ), + )); + Ok(layout) + } + + fn show_danger( + _title: TString<'static>, + _description: TString<'static>, + _value: TString<'static>, + _verb_cancel: Option>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"show_danger not supported", + )) + } + + 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 { + let layout = RootComponent::new(IconDialog::new_shares( + lines, + theme::button_bar(Button::with_text(TR::buttons__continue.into()).map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) + })), + )); + Ok(layout) + } + + fn show_homescreen( + label: TString<'static>, + hold: bool, + notification: Option>, + notification_level: u8, + ) -> Result { + let notification = notification.map(|w| (w, notification_level)); + let layout = RootComponent::new(Homescreen::new(label, notification, hold)); + Ok(layout) + } + fn show_info( title: TString<'static>, description: TString<'static>, @@ -33,17 +901,287 @@ impl UIFeaturesFirmware for ModelTTFeatures { theme::FG, theme::BG, ); - let res = new_show_modal( + new_show_modal( title, TString::empty(), description, - TString::empty(), + button, false, time_ms, icon, theme::button_info(), - )?; - Ok(res) + ) + } + + fn show_info_with_cancel( + title: TString<'static>, + items: Obj, + horizontal: bool, + chunkify: bool, + ) -> Result { + let mut paragraphs = ParagraphVecShort::new(); + + for para in IterBuf::new().try_iterate(items)? { + let [key, value]: [Obj; 2] = util::iter_into_array(para)?; + let key: TString = key.try_into()?; + let value: TString = value.try_into()?; + paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, key).no_break()); + if chunkify { + paragraphs.add(Paragraph::new( + theme::get_chunkified_text_style(value.len()), + value, + )); + } else { + paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); + } + } + + let axis = match horizontal { + true => geometry::Axis::Horizontal, + _ => geometry::Axis::Vertical, + }; + + let layout = RootComponent::new( + Frame::left_aligned( + theme::label_title(), + title, + SimplePage::new(paragraphs.into_paragraphs(), axis, theme::BG) + .with_swipe_right_to_go_back(), + ) + .with_cancel_button(), + ); + Ok(layout) + } + + fn show_lockscreen( + label: TString<'static>, + bootscreen: bool, + coinjoin_authorized: bool, + ) -> Result { + let layout = RootComponent::new(Lockscreen::new(label, bootscreen, coinjoin_authorized)); + Ok(layout) + } + + fn show_mismatch(title: TString<'static>) -> Result { + let description: TString = TR::addr_mismatch__contact_support_at.into(); + let url: TString = TR::addr_mismatch__support_url.into(); + let button: TString = TR::buttons__quit.into(); + + let icon = BlendedImage::new( + theme::IMAGE_BG_OCTAGON, + theme::IMAGE_FG_WARN, + theme::WARN_COLOR, + theme::FG, + theme::BG, + ); + let layout = RootComponent::new( + IconDialog::new( + icon, + title, + Button::cancel_confirm( + Button::with_icon(theme::ICON_BACK), + Button::with_text(button).styled(theme::button_reset()), + true, + ), + ) + .with_paragraph( + Paragraph::new(&theme::TEXT_NORMAL, description) + .centered() + .with_bottom_padding( + theme::TEXT_NORMAL.text_font.text_height() + - theme::TEXT_DEMIBOLD.text_font.text_height(), + ), + ) + .with_text(&theme::TEXT_DEMIBOLD, url), + ); + + Ok(layout) + } + + fn show_progress( + description: TString<'static>, + indeterminate: bool, + title: Option>, + ) -> Result { + let (title, description) = if let Some(title) = title { + (title, description) + } else { + (description, "".into()) + }; + + let layout = RootComponent::new(Progress::new(title, indeterminate, description)); + Ok(layout) + } + + fn show_progress_coinjoin( + title: TString<'static>, + indeterminate: bool, + time_ms: u32, + skip_first_paint: bool, + ) -> Result, Error> { + let progress = CoinJoinProgress::::new(title, indeterminate)?; + let obj = if time_ms > 0 && indeterminate { + let timeout = Timeout::new(time_ms); + LayoutObj::new((timeout, progress.map(|_msg| None)))? + } else { + LayoutObj::new(progress)? + }; + if skip_first_paint { + obj.skip_first_paint(); + } + Ok(obj) + } + + fn show_share_words( + words: heapless::Vec, 33>, + title: Option>, + ) -> Result { + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title.unwrap_or(TString::empty()), + ButtonPage::new(ShareWords::new(words), theme::BG) + .with_hold()? + .without_cancel(), + )); + Ok(layout) + } + + fn show_share_words_mercury( + _words: heapless::Vec, 33>, + _subtitle: Option>, + _instructions: crate::micropython::obj::Obj, + _text_footer: Option>, + _text_confirm: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError( + c"use show_share_words", + )) + } + + fn show_remaining_shares( + pages_iterable: crate::micropython::obj::Obj, // TODO: replace Obj + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + for page in crate::micropython::iter::IterBuf::new().try_iterate(pages_iterable)? { + let [title, description]: [TString; 2] = + crate::micropython::util::iter_into_array(page)?; + paragraphs + .add(Paragraph::new(&theme::TEXT_DEMIBOLD, title)) + .add(Paragraph::new(&theme::TEXT_NORMAL, description).break_after()); + } + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + TR::recovery__title_remaining_shares.into(), + ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) + .with_cancel_confirm(None, Some(TR::buttons__continue.into())) + .with_confirm_style(theme::button_default()) + .without_cancel(), + )); + Ok(layout) + } + + fn show_simple( + text: TString<'static>, + title: Option>, + button: Option>, + ) -> Result, Error> { + let button = button.unwrap_or(TString::empty()); + if let Some(t) = title { + LayoutObj::new(Frame::left_aligned( + theme::label_title(), + t, + Dialog::new( + Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, text)]), + theme::button_bar(Button::with_text(button).map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) + })), + ), + )) + } else if !button.is_empty() { + LayoutObj::new(Border::new( + theme::borders(), + Dialog::new( + Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, text)]), + theme::button_bar(Button::with_text(button).map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) + })), + ), + )) + } else { + LayoutObj::new(Border::new( + theme::borders(), + Dialog::new( + Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, text).centered()]), + Empty, + ), + )) + } + } + + 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", + )) } } @@ -109,3 +1247,104 @@ fn new_show_modal( Ok(obj) } + +// TODO: move to some util.rs? +struct ConfirmBlobParams { + title: TString<'static>, + subtitle: Option>, + data: Obj, + description: Option>, + extra: Option>, + verb: Option>, + verb_cancel: Option>, + info_button: bool, + hold: bool, + chunkify: bool, + text_mono: bool, +} + +impl ConfirmBlobParams { + fn new( + title: TString<'static>, + data: Obj, + description: Option>, + verb: Option>, + verb_cancel: Option>, + hold: bool, + ) -> Self { + Self { + title, + subtitle: None, + data, + description, + extra: None, + verb, + verb_cancel, + info_button: false, + hold, + chunkify: false, + text_mono: true, + } + } + + fn with_extra(mut self, extra: Option>) -> Self { + self.extra = extra; + self + } + + fn with_subtitle(mut self, subtitle: Option>) -> Self { + self.subtitle = subtitle; + self + } + + fn with_info_button(mut self, info_button: bool) -> Self { + self.info_button = info_button; + self + } + + fn with_chunkify(mut self, chunkify: bool) -> Self { + self.chunkify = chunkify; + self + } + + fn with_text_mono(mut self, text_mono: bool) -> Self { + self.text_mono = text_mono; + self + } + + fn into_layout(self) -> Result, Error> { + let paragraphs = ConfirmBlob { + description: self.description.unwrap_or("".into()), + extra: self.extra.unwrap_or("".into()), + data: self.data.try_into()?, + description_font: &theme::TEXT_NORMAL, + extra_font: &theme::TEXT_DEMIBOLD, + data_font: if self.chunkify { + let data: TString = self.data.try_into()?; + theme::get_chunkified_text_style(data.len()) + } else if self.text_mono { + &theme::TEXT_MONO + } else { + &theme::TEXT_NORMAL + }, + } + .into_paragraphs(); + + let mut page = ButtonPage::new(paragraphs, theme::BG); + if let Some(verb) = self.verb { + page = page.with_cancel_confirm(self.verb_cancel, Some(verb)) + } + if self.hold { + page = page.with_hold()? + } + let mut frame = Frame::left_aligned(theme::label_title(), self.title, page); + if let Some(subtitle) = self.subtitle { + frame = frame.with_subtitle(theme::label_subtitle(), subtitle); + } + + if self.info_button { + frame = frame.with_info_button(); + } + LayoutObj::new(frame) + } +} diff --git a/core/embed/rust/src/ui/ui_features_fw.rs b/core/embed/rust/src/ui/ui_features_fw.rs index f721394cb3..d124edcef9 100644 --- a/core/embed/rust/src/ui/ui_features_fw.rs +++ b/core/embed/rust/src/ui/ui_features_fw.rs @@ -1,12 +1,384 @@ -use crate::{error::Error, micropython::gc::Gc, strutil::TString}; +use crate::{ + error::Error, + io::BinaryData, + micropython::{gc::Gc, list::List, obj::Obj}, + strutil::TString, +}; +use heapless::Vec; -use super::layout::obj::{LayoutMaybeTrace, LayoutObj}; +use super::layout::{ + obj::{LayoutMaybeTrace, LayoutObj}, + util::RecoveryType, +}; pub trait UIFeaturesFirmware { + fn confirm_action( + title: TString<'static>, + action: Option>, + description: Option>, + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + hold: bool, + hold_danger: bool, + reverse: bool, + prompt_screen: bool, + prompt_title: Option>, + ) -> Result; + + fn confirm_address( + title: TString<'static>, + data: Obj, // TODO: replace Obj + description: Option>, + extra: Option>, + verb: Option>, + chunkify: bool, + ) -> Result; + + fn confirm_blob( + title: TString<'static>, + data: Obj, // TODO: replace Obj + description: Option>, + text_mono: bool, + extra: Option>, + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + verb_info: Option>, + info: bool, + hold: bool, + chunkify: bool, + page_counter: bool, + prompt_screen: bool, + cancel: bool, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + + fn confirm_blob_intro( + title: TString<'static>, + data: Obj, // TODO: replace Obj + subtitle: Option>, + verb: Option>, + verb_cancel: Option>, + chunkify: bool, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + + fn confirm_homescreen( + title: TString<'static>, + image: BinaryData<'static>, + ) -> Result; + + fn confirm_coinjoin( + max_rounds: TString<'static>, + max_feerate: TString<'static>, + ) -> Result; + + fn confirm_emphasized( + title: TString<'static>, + items: Obj, // TODO: replace Obj + verb: Option>, + ) -> Result; + + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, // TODO: replace Gc + ) -> Result; + + fn confirm_firmware_update( + description: TString<'static>, + fingerprint: TString<'static>, + ) -> Result; + + fn confirm_modify_fee( + title: TString<'static>, + sign: i32, + user_fee_change: TString<'static>, + total_fee_new: TString<'static>, + fee_rate_amount: Option>, + ) -> Result; + + fn confirm_modify_output( + sign: i32, + amount_change: TString<'static>, + amount_new: TString<'static>, + ) -> Result; + + fn confirm_more( + title: TString<'static>, + button: TString<'static>, + button_style_confirm: bool, + items: Obj, // TODO: replace Obj + ) -> Result; + + fn confirm_properties( + title: TString<'static>, + items: Obj, // TODO: replace Obj` + hold: bool, + ) -> Result; + + fn confirm_reset_device(recovery: bool) -> Result; + + fn confirm_summary( + amount: TString<'static>, + amount_label: TString<'static>, + fee: TString<'static>, + fee_label: TString<'static>, + title: Option>, + account_items: Option, // TODO: replace Obj + extra_items: Option, // TODO: replace Obj + extra_title: Option>, + verb_cancel: Option>, + ) -> Result; + + fn confirm_value( + title: TString<'static>, + value: Obj, // TODO: replace Obj + description: Option>, + subtitle: Option>, + verb: Option>, + verb_info: Option>, + verb_cancel: Option>, + info_button: bool, + hold: bool, + chunkify: bool, + text_mono: bool, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + + fn confirm_with_info( + title: TString<'static>, + button: TString<'static>, + info_button: TString<'static>, + verb_cancel: Option>, + items: Obj, // TODO: replace Obj + ) -> Result; + + fn continue_recovery_homepage( + text: TString<'static>, + subtext: Option>, + button: Option>, + recovery_type: RecoveryType, + show_instructions: bool, + remaining_shares: Option, // TODO: replace Obj + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + + fn check_homescreen_format(image: BinaryData, accept_toif: bool) -> bool; + + fn flow_confirm_output( + title: Option>, + subtitle: Option>, + message: Obj, // TODO: replace Obj + amount: Option, // TODO: replace Obj + chunkify: bool, + text_mono: bool, + account: Option>, + account_path: Option>, + br_code: u16, + br_name: TString<'static>, + address: Option, // TODO: replace Obj + address_title: Option>, + summary_items: Option, // TODO: replace Obj + fee_items: Option, // TODO: replace Obj + summary_title: Option>, + summary_br_code: Option, + summary_br_name: Option>, + cancel_text: Option>, + ) -> Result; + + fn flow_confirm_set_new_pin( + title: TString<'static>, + description: TString<'static>, + ) -> Result; + + fn flow_get_address( + address: Obj, // TODO: replace Obj + title: TString<'static>, + description: Option>, + extra: Option>, + chunkify: bool, + address_qr: TString<'static>, + case_sensitive: bool, + account: Option>, + path: Option>, + xpubs: Obj, // TODO: replace Obj + title_success: TString<'static>, + br_code: u16, + br_name: TString<'static>, + ) -> Result; + + // TODO: this is TR specific and used only in confirm_set_new_pin + fn multiple_pages_texts( + title: TString<'static>, + verb: TString<'static>, + items: Gc, + ) -> Result; + + fn prompt_backup() -> Result; + + fn request_bip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result; + + fn request_slip39( + prompt: TString<'static>, + prefill_word: TString<'static>, + can_go_back: bool, + ) -> Result; + + fn request_number( + title: TString<'static>, + count: u32, + min_count: u32, + max_count: u32, + description: Option>, + more_info_callback: Option TString<'static> + 'static>, + ) -> Result; + + fn request_pin( + prompt: TString<'static>, + subprompt: TString<'static>, + allow_cancel: bool, + warning: bool, + ) -> Result; + + fn request_passphrase( + prompt: TString<'static>, + max_len: u32, + ) -> Result; + + fn select_word( + title: TString<'static>, + description: TString<'static>, + words: [TString<'static>; 3], + ) -> Result; + + fn select_word_count(recovery_type: RecoveryType) -> Result; + + fn set_brightness(current_brightness: Option) -> Result; + + fn show_address_details( + qr_title: TString<'static>, + address: TString<'static>, + case_sensitive: bool, + details_title: TString<'static>, + account: Option>, + path: Option>, + xpubs: Obj, // TODO: replace Obj + ) -> Result; + + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result; + + fn show_danger( + title: TString<'static>, + description: TString<'static>, + value: TString<'static>, + verb_cancel: Option>, + ) -> 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; + + fn show_homescreen( + label: TString<'static>, + hold: bool, + notification: Option>, + notification_level: u8, + ) -> Result; + fn show_info( title: TString<'static>, description: TString<'static>, button: TString<'static>, time_ms: u32, - ) -> Result, Error>; + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + + fn show_info_with_cancel( + title: TString<'static>, + items: Obj, // TODO: replace Obj + horizontal: bool, + chunkify: bool, + ) -> Result; + + fn show_lockscreen( + label: TString<'static>, + bootscreen: bool, + coinjoin_authorized: bool, + ) -> Result; + + fn show_mismatch(title: TString<'static>) -> Result; + + fn show_progress( + description: TString<'static>, + indeterminate: bool, + title: Option>, + ) -> Result; + + fn show_progress_coinjoin( + title: TString<'static>, + indeterminate: bool, + time_ms: u32, + skip_first_paint: bool, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + + fn show_remaining_shares( + pages_iterable: Obj, // TODO: replace Obj + ) -> Result; + + fn show_share_words( + words: Vec, 33>, + title: Option>, + ) -> Result; + + // TODO: merge with `show_share_words` instead of having specific version for mercury + fn show_share_words_mercury( + words: Vec, 33>, + subtitle: Option>, + instructions: Obj, // TODO: replace Obj + text_footer: Option>, // footer description at instruction screen + text_confirm: TString<'static>, + ) -> Result; + + fn show_simple( + text: TString<'static>, + title: Option>, + button: Option>, + ) -> Result, Error>; // TODO: return LayoutMaybeTrace + + 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 f23bb51ee0..acfe35b970 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -1,1448 +1,7 @@ from typing import * from trezor import utils from trezorui_api import * - - -# rust/src/ui/model_mercury/layout.rs -def disable_animation(disable: bool) -> None: - """Disable animations, debug builds only.""" - - -# rust/src/ui/model_mercury/layout.rs -def check_homescreen_format(data: bytes) -> bool: - """Check homescreen format and dimensions.""" - - -# rust/src/ui/model_mercury/layout.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/model_mercury/layout.rs -def confirm_emphasized( - *, - title: str, - items: Iterable[str | tuple[bool, str]], - verb: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm formatted text that has been pre-split in python. For tuples - the first component is a bool indicating whether this part is emphasized.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_homescreen( - *, - title: str, - image: bytes, -) -> LayoutObj[UiResult]: - """Confirm homescreen.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_blob( - *, - title: str, - data: str | bytes, - description: str | None, - text_mono: bool = True, - extra: str | None = None, - subtitle: str | None = None, - verb: str | None = None, - verb_cancel: str | None = None, - verb_info: str | None = None, - info: bool = True, - hold: bool = False, - chunkify: bool = False, - page_counter: bool = False, - prompt_screen: bool = False, - cancel: bool = False, -) -> LayoutObj[UiResult]: - """Confirm byte sequence data.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_blob_intro( - *, - title: str, - data: str | bytes, - subtitle: str | None = None, - verb: str | None = None, - verb_cancel: str | None = None, - chunkify: bool = False, -) -> LayoutObj[UiResult]: - """Confirm byte sequence data by showing only the first page of the data - and instructing the user to access the menu in order to view all the data, - which can then be confirmed using confirm_blob.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_properties( - *, - title: str, - items: list[tuple[str | None, str | bytes | None, bool]], - hold: bool = False, -) -> LayoutObj[UiResult]: - """Confirm list of key-value pairs. The third component in the tuple should be True if - the value is to be rendered as binary with monospace font, False otherwise.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_confirm_reset(recovery: bool) -> LayoutObj[UiResult]: - """Confirm TOS before creating wallet creation or wallet recovery.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_confirm_set_new_pin( - *, - title: str, - description: str, -) -> LayoutObj[UiResult]: - """Confirm new PIN setup with an option to cancel action.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_info_with_cancel( - *, - title: str, - items: Iterable[Tuple[str, str]], - horizontal: bool = False, - chunkify: bool = False, -) -> LayoutObj[UiResult]: - """Show metadata for outgoing transaction.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_value( - *, - title: str, - value: str, - description: str | None, - subtitle: str | None, - verb: str | None = None, - verb_info: str | None = None, - verb_cancel: str | None = None, - info_button: bool = False, - hold: bool = False, - chunkify: bool = False, - text_mono: bool = True, -) -> LayoutObj[UiResult]: - """Confirm value. Merge of confirm_total and confirm_output.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_modify_output( - *, - sign: int, - amount_change: str, - amount_new: str, -) -> LayoutObj[UiResult]: - """Decrease or increase output amount.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_modify_fee( - *, - title: str, - sign: int, - user_fee_change: str, - total_fee_new: str, - fee_rate_amount: str | None, # ignored -) -> LayoutObj[UiResult]: - """Decrease or increase transaction fee.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_fido( - *, - title: str, - app_name: str, - icon_name: str | None, - accounts: list[str | None], -) -> LayoutObj[int | UiResult]: - """FIDO confirmation. - Returns page index in case of confirmation and CANCELLED otherwise. - """ - - -# 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_danger( - *, - title: str, - description: str, - value: str = "", - verb_cancel: str | None = None, -) -> LayoutObj[UiResult]: - """Warning modal that makes it easier to cancel than to continue.""" - - -# 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_mismatch(*, title: str) -> LayoutObj[UiResult]: - """Warning modal, receiving address mismatch.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_simple( - *, - title: str | None, - description: str = "", - button: str = "", -) -> LayoutObj[UiResult]: - """Simple dialog with text and one button.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_with_info( - *, - title: str, - button: str, - info_button: str, - items: Iterable[tuple[int, str]], -) -> LayoutObj[UiResult]: - """Confirm given items but with third button. In mercury, the button is placed in - context menu.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_coinjoin( - *, - max_rounds: str, - max_feerate: str, -) -> LayoutObj[UiResult]: - """Confirm coinjoin authorization.""" - - -# rust/src/ui/model_mercury/layout.rs -def request_pin( - *, - prompt: str, - subprompt: str, - allow_cancel: bool = True, - wrong_pin: bool = False, -) -> LayoutObj[str | UiResult]: - """Request pin on device.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_request_passphrase( - *, - prompt: str, - max_len: int, -) -> LayoutObj[str | UiResult]: - """Passphrase input keyboard.""" - - -# rust/src/ui/model_mercury/layout.rs -def request_bip39( - *, - prompt: str, - prefill_word: str, - can_go_back: bool, -) -> LayoutObj[str]: - """BIP39 word input keyboard.""" - - -# rust/src/ui/model_mercury/layout.rs -def request_slip39( - *, - prompt: str, - prefill_word: str, - can_go_back: bool, -) -> LayoutObj[str]: - """SLIP39 word input keyboard.""" - - -# rust/src/ui/model_mercury/layout.rs -def select_word( - *, - title: str, - description: str, - words: Iterable[str], -) -> LayoutObj[int]: - """Select mnemonic word from three possibilities - seed check after backup. The - iterable must be of exact size. Returns index in range `0..3`.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_prompt_backup() -> LayoutObj[UiResult]: - """Prompt a user to create backup with an option to skip.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_show_share_words( - *, - title: str, - subtitle: str, - words: Iterable[str], - description: str, - text_info: Iterable[str], - text_confirm: str, -) -> LayoutObj[UiResult]: - """Show wallet backup words preceded by an instruction screen and followed by - confirmation.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_request_number( - *, - title: str, - count: int, - min_count: int, - max_count: int, - description: str, - info: Callable[[int], str] | None = None, - br_code: ButtonRequestType, - br_name: str, -) -> LayoutObj[tuple[UiResult, int]]: - """Number input with + and - buttons, description, and context menu with cancel and - info.""" - - -# rust/src/ui/model_mercury/layout.rs -def set_brightness( - *, - current: int | None = None -) -> LayoutObj[UiResult]: - """Show the brightness configuration dialog.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_checklist( - *, - title: str, - items: Iterable[str], - active: int, - button: str, -) -> LayoutObj[UiResult]: - """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_continue_recovery( - *, - first_screen: bool, - recovery_type: RecoveryType, - text: str, - subtext: str | None = None, - pages: Iterable[tuple[str, str]] | None = None, -) -> LayoutObj[UiResult]: - """Device recovery homescreen.""" - - -# rust/src/ui/model_mercury/layout.rs -def select_word_count( - *, - recovery_type: RecoveryType, -) -> LayoutObj[int | str]: # merucry returns int - """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. - For unlocking a repeated backup, select from 20 or 33.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_group_share_success( - *, - lines: Iterable[str] -) -> LayoutObj[UiResult]: - """Shown after successfully finishing a group.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_progress( - *, - title: str, - indeterminate: bool = False, - description: str = "", -) -> LayoutObj[UiResult]: - """Show progress loader. Please note that the number of lines reserved on screen for - description is determined at construction time. If you want multiline descriptions - make sure the initial description has at least that amount of lines.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_progress_coinjoin( - *, - title: str, - indeterminate: bool = False, - time_ms: int = 0, - skip_first_paint: bool = False, -) -> LayoutObj[UiResult]: - """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - time_ms timeout is passed.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_homescreen( - *, - label: str | None, - hold: bool, - notification: str | None, - notification_level: int = 0, - skip_first_paint: bool, -) -> LayoutObj[UiResult]: - """Idle homescreen.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_lockscreen( - *, - label: str | None, - bootscreen: bool, - skip_first_paint: bool, - coinjoin_authorized: bool = False, -) -> LayoutObj[UiResult]: - """Homescreen for locked device.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_firmware_update( - *, - description: str, - fingerprint: str, -) -> LayoutObj[UiResult]: - """Ask whether to update firmware, optionally show fingerprint.""" - - -# rust/src/ui/model_mercury/layout.rs -def tutorial() -> LayoutObj[UiResult]: - """Show user how to interact with the device.""" - - -# rust/src/ui/model_mercury/layout.rs -def show_wait_text(message: str, /) -> LayoutObj[None]: - """Show single-line text in the middle of the screen.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_get_address( - *, - address: str | bytes, - title: str, - description: str | None, - extra: str | None, - chunkify: bool, - address_qr: str | None, - case_sensitive: bool, - account: str | None, - path: str | None, - xpubs: list[tuple[str, str]], - title_success: str, - br_code: ButtonRequestType, - br_name: str, -) -> LayoutObj[UiResult]: - """Get address / receive funds.""" - - -# rust/src/ui/model_mercury/layout.rs -def flow_confirm_output( - *, - title: str | None, - subtitle: str | None, - message: str, - amount: str | None, - chunkify: bool, - text_mono: bool, - account: str | None, - account_path: str | None, - br_code: ButtonRequestType, - br_name: str, - address: str | None, - address_title: str | None, - summary_items: Iterable[tuple[str, str]] | None = None, - fee_items: Iterable[tuple[str, str]] | None = None, - summary_title: str | None = None, - summary_br_code: ButtonRequestType | None = None, - summary_br_name: str | None = None, - cancel_text: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page.""" - - -# rust/src/ui/model_mercury/layout.rs -def confirm_summary( - *, - amount: str, - amount_label: str, - fee: str, - fee_label: str, - title: str | None = None, - account_items: Iterable[tuple[str, str]] | None = None, - extra_items: Iterable[tuple[str, str]] | None = None, - extra_title: str | None = None, - verb_cancel: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm summary of a transaction.""" from trezor import utils from trezorui_api import * - - -# rust/src/ui/model_tr/layout.rs -def disable_animation(disable: bool) -> None: - """Disable animations, debug builds only.""" - - -# rust/src/ui/model_tr/layout.rs -def check_homescreen_format(data: bytes) -> bool: - """Check homescreen format and dimensions.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_action( - *, - title: str, - action: str | None, - description: str | None, - subtitle: str | None = None, - verb: str = "CONFIRM", - verb_cancel: str | None = None, - hold: bool = False, - hold_danger: bool = False, # unused on TR - reverse: bool = False, - prompt_screen: bool = False, - prompt_title: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm action.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_homescreen( - *, - title: str, - image: bytes, -) -> object: - """Confirm homescreen.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_blob( - *, - title: str, - data: str | bytes, - description: str | None, - text_mono: bool = True, - extra: str | None = None, - subtitle: str | None = None, - verb: str = "CONFIRM", - verb_cancel: str | None = None, - verb_info: str | None = None, - info: bool = True, - hold: bool = False, - chunkify: bool = False, - page_counter: bool = False, - prompt_screen: bool = False, - cancel: bool = False, -) -> LayoutObj[UiResult]: - """Confirm byte sequence data.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_address( - *, - title: str, - data: str, - description: str | None, # unused on TR - extra: str | None, # unused on TR - verb: str = "CONFIRM", - chunkify: bool = False, -) -> LayoutObj[UiResult]: - """Confirm address.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_properties( - *, - title: str, - items: list[tuple[str | None, str | bytes | None, bool]], - hold: bool = False, -) -> LayoutObj[UiResult]: - """Confirm list of key-value pairs. The third component in the tuple should be True if - the value is to be rendered as binary with monospace font, False otherwise. - This only concerns the text style, you need to decode the value to UTF-8 in python.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_reset_device( - *, - title: str, - button: str, -) -> LayoutObj[UiResult]: - """Confirm TOS before device setup.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_backup() -> LayoutObj[UiResult]: - """Strongly recommend user to do backup.""" - - -# rust/src/ui/model_tr/layout.rs -def show_address_details( - *, - address: str, - case_sensitive: bool, - account: str | None, - path: str | None, - xpubs: list[tuple[str, str]], -) -> LayoutObj[UiResult]: - """Show address details - QR code, account, path, cosigner xpubs.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_value( - *, - title: str, - description: str, - value: str, - verb: str | None = None, - verb_info: str | None = None, - hold: bool = False, -) -> LayoutObj[UiResult]: - """Confirm value.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_joint_total( - *, - spending_amount: str, - total_amount: str, -) -> LayoutObj[UiResult]: - """Confirm total if there are external inputs.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_modify_output( - *, - sign: int, - amount_change: str, - amount_new: str, -) -> LayoutObj[UiResult]: - """Decrease or increase output amount.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_output_address( - *, - address: str, - address_label: str, - address_title: str, - chunkify: bool = False, -) -> LayoutObj[UiResult]: - """Confirm output address.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_output_amount( - *, - amount: str, - amount_title: str, -) -> LayoutObj[UiResult]: - """Confirm output amount.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_summary( - *, - amount: str, - amount_label: str, - fee: str, - fee_label: str, - title: str | None = None, - account_items: Iterable[tuple[str, str]] | None = None, - extra_items: Iterable[tuple[str, str]] | None = None, - extra_title: str | None = None, - verb_cancel: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm summary of a transaction.""" - - -# rust/src/ui/model_tr/layout.rs -def tutorial() -> LayoutObj[UiResult]: - """Show user how to interact with the device.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_modify_fee( - *, - title: str, # ignored - sign: int, - user_fee_change: str, - total_fee_new: str, - fee_rate_amount: str | None, -) -> LayoutObj[UiResult]: - """Decrease or increase transaction fee.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_fido( - *, - title: str, - app_name: str, - icon_name: str | None, # unused on TR - accounts: list[str | None], -) -> LayoutObj[int | UiResult]: - """FIDO confirmation. - Returns page index in case of confirmation and CANCELLED otherwise. - """ - - -# rust/src/ui/model_tr/layout.rs -def multiple_pages_texts( - *, - title: str, - verb: str, - items: list[str], -) -> LayoutObj[UiResult]: - """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.""" - - -# rust/src/ui/model_tr/layout.rs -def show_mismatch(*, title: str) -> LayoutObj[UiResult]: - """Warning modal, receiving address mismatch.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_with_info( - *, - title: str, - button: str, - info_button: str, # unused on TR - items: Iterable[Tuple[int, str | bytes]], - verb_cancel: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm given items but with third button. Always single page - without scrolling.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_more( - *, - title: str, - button: str, - items: Iterable[tuple[int, str | bytes]], -) -> object: - """Confirm long content with the possibility to go back from any page. - Meant to be used with confirm_with_info.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_coinjoin( - *, - max_rounds: str, - max_feerate: str, -) -> LayoutObj[UiResult]: - """Confirm coinjoin authorization.""" - - -# rust/src/ui/model_tr/layout.rs -def request_pin( - *, - prompt: str, - subprompt: str, - allow_cancel: bool = True, # unused on TR - wrong_pin: bool = False, # unused on TR -) -> LayoutObj[str | UiResult]: - """Request pin on device.""" - - -# rust/src/ui/model_tr/layout.rs -def request_passphrase( - *, - prompt: str, - max_len: int, # unused on TR -) -> LayoutObj[str | UiResult]: - """Get passphrase.""" - - -# rust/src/ui/model_tr/layout.rs -def request_bip39( - *, - prompt: str, - prefill_word: str, - can_go_back: bool, -) -> LayoutObj[str]: - """Get recovery word for BIP39.""" - - -# rust/src/ui/model_tr/layout.rs -def request_slip39( - *, - prompt: str, - prefill_word: str, - can_go_back: bool, -) -> LayoutObj[str]: - """SLIP39 word input keyboard.""" - - -# rust/src/ui/model_tr/layout.rs -def select_word( - *, - title: str, # unused on TR - description: str, - words: Iterable[str], -) -> LayoutObj[int]: - """Select mnemonic word from three possibilities - seed check after backup. The - iterable must be of exact size. Returns index in range `0..3`.""" - - -# rust/src/ui/model_tr/layout.rs -def show_share_words( - *, - share_words: Iterable[str], -) -> LayoutObj[UiResult]: - """Shows a backup seed.""" - - -# rust/src/ui/model_tr/layout.rs -def request_number( - *, - title: str, - count: int, - min_count: int, - max_count: int, - description: Callable[[int], str] | None = None, # unused on TR -) -> LayoutObj[tuple[UiResult, int]]: - """Number input with + and - buttons, description, and info button.""" - - -# rust/src/ui/model_tr/layout.rs -def show_checklist( - *, - title: str, # unused on TR - items: Iterable[str], - active: int, - button: str, -) -> LayoutObj[UiResult]: - """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_recovery( - *, - title: str, # unused on TR - description: str, - button: str, - recovery_type: RecoveryType, - info_button: bool, # unused on TR - show_instructions: bool, -) -> LayoutObj[UiResult]: - """Device recovery homescreen.""" - - -# rust/src/ui/model_tr/layout.rs -def select_word_count( - *, - recovery_type: RecoveryType, -) -> LayoutObj[int | str]: # TR returns str - """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. - For unlocking a repeated backup, select from 20 or 33.""" - - -# rust/src/ui/model_tr/layout.rs -def show_group_share_success( - *, - lines: Iterable[str], -) -> LayoutObj[int]: - """Shown after successfully finishing a group.""" - - -# rust/src/ui/model_tr/layout.rs -def show_progress( - *, - description: str, - indeterminate: bool = False, - title: str | None = None, -) -> LayoutObj[UiResult]: - """Show progress loader. Please note that the number of lines reserved on screen for - description is determined at construction time. If you want multiline descriptions - make sure the initial description has at least that amount of lines.""" - - -# rust/src/ui/model_tr/layout.rs -def show_progress_coinjoin( - *, - title: str, - indeterminate: bool = False, - time_ms: int = 0, - skip_first_paint: bool = False, -) -> LayoutObj[UiResult]: - """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - time_ms timeout is passed.""" - - -# rust/src/ui/model_tr/layout.rs -def show_homescreen( - *, - label: str | None, - hold: bool, # unused on TR - notification: str | None, - notification_level: int = 0, - skip_first_paint: bool, -) -> LayoutObj[UiResult]: - """Idle homescreen.""" - - -# rust/src/ui/model_tr/layout.rs -def show_lockscreen( - *, - label: str | None, - bootscreen: bool, - skip_first_paint: bool, - coinjoin_authorized: bool = False, -) -> LayoutObj[UiResult]: - """Homescreen for locked device.""" - - -# rust/src/ui/model_tr/layout.rs -def confirm_firmware_update( - *, - description: str, - fingerprint: str, -) -> LayoutObj[UiResult]: - """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" - - -# rust/src/ui/model_tr/layout.rs -def show_wait_text(message: str, /) -> None: - """Show single-line text in the middle of the screen.""" from trezor import utils from trezorui_api import * - - -# rust/src/ui/model_tt/layout.rs -def disable_animation(disable: bool) -> None: - """Disable animations, debug builds only.""" - - -# rust/src/ui/model_tt/layout.rs -def check_homescreen_format(data: bytes) -> bool: - """Check homescreen format and dimensions.""" - - -# rust/src/ui/model_tt/layout.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/model_tt/layout.rs -def confirm_emphasized( - *, - title: str, - items: Iterable[str | tuple[bool, str]], - verb: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm formatted text that has been pre-split in python. For tuples - the first component is a bool indicating whether this part is emphasized.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_homescreen( - *, - title: str, - image: bytes, -) -> LayoutObj[UiResult]: - """Confirm homescreen.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_blob( - *, - title: str, - data: str | bytes, - description: str | None, - text_mono: bool = True, - extra: str | None = None, - subtitle: str | None = None, - verb: str | None = None, - verb_cancel: str | None = None, - verb_info: str | None = None, - info: bool = True, - hold: bool = False, - chunkify: bool = False, - page_counter: bool = False, - prompt_screen: bool = False, - cancel: bool = False, -) -> LayoutObj[UiResult]: - """Confirm byte sequence data.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_address( - *, - title: str, - data: str | bytes, - description: str | None, - verb: str | None = "CONFIRM", - extra: str | None, - chunkify: bool = False, -) -> LayoutObj[UiResult]: - """Confirm address. Similar to `confirm_blob` but has corner info button - and allows left swipe which does the same thing as the button.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_properties( - *, - title: str, - items: list[tuple[str | None, str | bytes | None, bool]], - hold: bool = False, -) -> LayoutObj[UiResult]: - """Confirm list of key-value pairs. The third component in the tuple should be True if - the value is to be rendered as binary with monospace font, False otherwise.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_reset_device( - *, - title: str, - button: str, -) -> LayoutObj[UiResult]: - """Confirm TOS before device setup.""" - - -# rust/src/ui/model_tt/layout.rs -def show_address_details( - *, - qr_title: str, - address: str, - case_sensitive: bool, - details_title: str, - account: str | None, - path: str | None, - xpubs: list[tuple[str, str]], -) -> LayoutObj[UiResult]: - """Show address details - QR code, account, path, cosigner xpubs.""" - - -# rust/src/ui/model_tt/layout.rs -def show_info_with_cancel( - *, - title: str, - items: Iterable[Tuple[str, str]], - horizontal: bool = False, - chunkify: bool = False, -) -> LayoutObj[UiResult]: - """Show metadata for outgoing transaction.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_value( - *, - title: str, - value: str, - description: str | None, - subtitle: str | None, - verb: str | None = None, - verb_info: str | None = None, - verb_cancel: str | None = None, - info_button: bool = False, - hold: bool = False, - chunkify: bool = False, - text_mono: bool = True, -) -> LayoutObj[UiResult]: - """Confirm value. Merge of confirm_total and confirm_output.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_summary( - *, - amount: str, - amount_label: str, - fee: str, - fee_label: str, - title: str | None = None, - account_items: Iterable[tuple[str, str]] | None = None, - extra_items: Iterable[tuple[str, str]] | None = None, - extra_title: str | None = None, - verb_cancel: str | None = None, -) -> LayoutObj[UiResult]: - """Confirm summary of a transaction.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_modify_output( - *, - sign: int, - amount_change: str, - amount_new: str, -) -> LayoutObj[UiResult]: - """Decrease or increase output amount.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_modify_fee( - *, - title: str, - sign: int, - user_fee_change: str, - total_fee_new: str, - fee_rate_amount: str | None, # ignored -) -> LayoutObj[UiResult]: - """Decrease or increase transaction fee.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_fido( - *, - title: str, - app_name: str, - icon_name: str | None, - accounts: list[str | None], -) -> LayoutObj[int | UiResult]: - """FIDO confirmation. - Returns page index in case of confirmation and CANCELLED otherwise. - """ - - -# 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_mismatch(*, title: str) -> LayoutObj[UiResult]: - """Warning modal, receiving address mismatch.""" - - -# rust/src/ui/model_tt/layout.rs -def show_simple( - *, - title: str | None, - description: str = "", - button: str = "", -) -> LayoutObj[UiResult]: - """Simple dialog with text and one button.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_with_info( - *, - title: str, - button: str, - info_button: str, - items: Iterable[tuple[int, str | bytes]], -) -> LayoutObj[UiResult]: - """Confirm given items but with third button. Always single page - without scrolling.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_more( - *, - title: str, - button: str, - button_style_confirm: bool = False, - items: Iterable[tuple[int, str | bytes]], -) -> LayoutObj[UiResult]: - """Confirm long content with the possibility to go back from any page. - Meant to be used with confirm_with_info.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_coinjoin( - *, - max_rounds: str, - max_feerate: str, -) -> LayoutObj[UiResult]: - """Confirm coinjoin authorization.""" - - -# rust/src/ui/model_tt/layout.rs -def request_pin( - *, - prompt: str, - subprompt: str, - allow_cancel: bool = True, - wrong_pin: bool = False, -) -> LayoutObj[str | UiResult]: - """Request pin on device.""" - - -# rust/src/ui/model_tt/layout.rs -def request_passphrase( - *, - prompt: str, - max_len: int, -) -> LayoutObj[str | UiResult]: - """Passphrase input keyboard.""" - - -# rust/src/ui/model_tt/layout.rs -def request_bip39( - *, - prompt: str, - prefill_word: str, - can_go_back: bool, -) -> LayoutObj[str]: - """BIP39 word input keyboard.""" - - -# rust/src/ui/model_tt/layout.rs -def request_slip39( - *, - prompt: str, - prefill_word: str, - can_go_back: bool, -) -> LayoutObj[str]: - """SLIP39 word input keyboard.""" - - -# rust/src/ui/model_tt/layout.rs -def select_word( - *, - title: str, - description: str, - words: Iterable[str], -) -> LayoutObj[int]: - """Select mnemonic word from three possibilities - seed check after backup. The - iterable must be of exact size. Returns index in range `0..3`.""" - - -# rust/src/ui/model_tt/layout.rs -def show_share_words( - *, - title: str, - pages: Iterable[str], -) -> LayoutObj[UiResult]: - """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" - - -# rust/src/ui/model_tt/layout.rs -def request_number( - *, - title: str, - count: int, - min_count: int, - max_count: int, - description: Callable[[int], str] | None = None, -) -> LayoutObj[tuple[UiResult, int]]: - """Number input with + and - buttons, description, and info button.""" - - -# rust/src/ui/model_tt/layout.rs -def set_brightness( - *, - current: int | None = None -) -> LayoutObj[UiResult]: - """Show the brightness configuration dialog.""" - - -# rust/src/ui/model_tt/layout.rs -def show_checklist( - *, - title: str, - items: Iterable[str], - active: int, - button: str, -) -> LayoutObj[UiResult]: - """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_recovery( - *, - title: str, - description: str, - button: str, - recovery_type: RecoveryType, - info_button: bool = False, - show_instructions: bool = False, # unused on TT -) -> LayoutObj[UiResult]: - """Device recovery homescreen.""" - - -# rust/src/ui/model_tt/layout.rs -def select_word_count( - *, - recovery_type: RecoveryType, -) -> LayoutObj[int | str]: # TT returns int - """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. - For unlocking a repeated backup, select from 20 or 33.""" - - -# rust/src/ui/model_tt/layout.rs -def show_group_share_success( - *, - lines: Iterable[str] -) -> LayoutObj[UiResult]: - """Shown after successfully finishing a group.""" - - -# rust/src/ui/model_tt/layout.rs -def show_remaining_shares( - *, - pages: Iterable[tuple[str, str]], -) -> LayoutObj[UiResult]: - """Shows SLIP39 state after info button is pressed on `confirm_recovery`.""" - - -# rust/src/ui/model_tt/layout.rs -def show_progress( - *, - description: str, - indeterminate: bool = False, - title: str | None = None, -) -> LayoutObj[UiResult]: - """Show progress loader. Please note that the number of lines reserved on screen for - description is determined at construction time. If you want multiline descriptions - make sure the initial description has at least that amount of lines.""" - - -# rust/src/ui/model_tt/layout.rs -def show_progress_coinjoin( - *, - title: str, - indeterminate: bool = False, - time_ms: int = 0, - skip_first_paint: bool = False, -) -> LayoutObj[UiResult]: - """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - time_ms timeout is passed.""" - - -# rust/src/ui/model_tt/layout.rs -def show_homescreen( - *, - label: str | None, - hold: bool, - notification: str | None, - notification_level: int = 0, - skip_first_paint: bool, -) -> LayoutObj[UiResult]: - """Idle homescreen.""" - - -# rust/src/ui/model_tt/layout.rs -def show_lockscreen( - *, - label: str | None, - bootscreen: bool, - skip_first_paint: bool, - coinjoin_authorized: bool = False, -) -> LayoutObj[UiResult]: - """Homescreen for locked device.""" - - -# rust/src/ui/model_tt/layout.rs -def confirm_firmware_update( - *, - description: str, - fingerprint: str, -) -> LayoutObj[UiResult]: - """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" - - -# rust/src/ui/model_tt/layout.rs -def show_wait_text(message: str, /) -> LayoutObj[None]: - """Show single-line text in the middle of the screen.""" diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index 17f0c7f367..79facf4ece 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -68,6 +68,466 @@ CANCELLED: UiResult INFO: UiResult +# rust/src/ui/api/firmware_upy.rs +def check_homescreen_format(data: bytes) -> bool: + """Check homescreen format and dimensions.""" + + +# rust/src/ui/api/firmware_upy.rs +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 confirm_address( + *, + title: str, + data: str | bytes, + description: str | None, + extra: str | None, + verb: str | None = None, + chunkify: bool = False, +) -> LayoutObj[UiResult]: + """Confirm address.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_blob( + *, + title: str, + data: str | bytes, + description: str | None, + text_mono: bool = True, + extra: str | None = None, + subtitle: str | None = None, + verb: str | None = None, + verb_cancel: str | None = None, + verb_info: str | None = None, + info: bool = True, + hold: bool = False, + chunkify: bool = False, + page_counter: bool = False, + prompt_screen: bool = False, + cancel: bool = False, +) -> LayoutObj[UiResult]: + """Confirm byte sequence data.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_blob_intro( + *, + title: str, + data: str | bytes, + subtitle: str | None = None, + verb: str | None = None, + verb_cancel: str | None = None, + chunkify: bool = False, +) -> LayoutObj[UiResult]: + """Confirm byte sequence data by showing only the first page of the data + and instructing the user to access the menu in order to view all the data, + which can then be confirmed using confirm_blob.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_coinjoin( + *, + max_rounds: str, + max_feerate: str, +) -> LayoutObj[UiResult]: + """Confirm coinjoin authorization.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_emphasized( + *, + title: str, + items: Iterable[str | tuple[bool, str]], + verb: str | None = None, +) -> LayoutObj[UiResult]: + """Confirm formatted text that has been pre-split in python. For tuples + the first component is a bool indicating whether this part is emphasized.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_fido( + *, + title: str, + app_name: str, + icon_name: str | None, + accounts: list[str | None], +) -> LayoutObj[int | UiResult]: + """FIDO confirmation. + Returns page index in case of confirmation and CANCELLED otherwise. + """ + + +# rust/src/ui/api/firmware_upy.rs +def confirm_firmware_update( + *, + description: str, + fingerprint: str, +) -> LayoutObj[UiResult]: + """Ask whether to update firmware, optionally show fingerprint.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_homescreen( + *, + title: str, + image: bytes, +) -> LayoutObj[UiResult]: + """Confirm homescreen.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_modify_fee( + *, + title: str, + sign: int, + user_fee_change: str, + total_fee_new: str, + fee_rate_amount: str | None, +) -> LayoutObj[UiResult]: + """Decrease or increase transaction fee.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_modify_output( + *, + sign: int, + amount_change: str, + amount_new: str, +) -> LayoutObj[UiResult]: + """Decrease or increase output amount.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_more( + *, + title: str, + button: str, + button_style_confirm: bool = False, + items: Iterable[tuple[int, str | bytes]], +) -> LayoutObj[UiResult]: + """Confirm long content with the possibility to go back from any page. + Meant to be used with confirm_with_info on model TT and TR.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_properties( + *, + title: str, + items: list[tuple[str | None, str | bytes | None, bool]], + hold: bool = False, +) -> LayoutObj[UiResult]: + """Confirm list of key-value pairs. The third component in the tuple should be True if + the value is to be rendered as binary with monospace font, False otherwise.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_reset_device(recovery: bool) -> LayoutObj[UiResult]: + """Confirm TOS before creating wallet creation or wallet recovery.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_summary( + *, + amount: str, + amount_label: str, + fee: str, + fee_label: str, + title: str | None = None, + account_items: Iterable[tuple[str, str]] | None = None, + extra_items: Iterable[tuple[str, str]] | None = None, + extra_title: str | None = None, + verb_cancel: str | None = None, +) -> LayoutObj[UiResult]: + """Confirm summary of a transaction.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_value( + *, + title: str, + value: str, + description: str | None, + subtitle: str | None, + verb: str | None = None, + verb_info: str | None = None, + verb_cancel: str | None = None, + info_button: bool = False, + hold: bool = False, + chunkify: bool = False, + text_mono: bool = True, +) -> LayoutObj[UiResult]: + """Confirm value. Merge of confirm_total and confirm_output.""" + + +# rust/src/ui/api/firmware_upy.rs +def confirm_with_info( + *, + title: str, + button: str, + info_button: str, + verb_cancel: str | None = None, + items: Iterable[tuple[int, str | bytes]], +) -> LayoutObj[UiResult]: + """Confirm given items but with third button. Always single page + without scrolling. In mercury, the button is placed in + context menu.""" + + +# rust/src/ui/api/firmware_upy.rs +def continue_recovery_homepage( + *, + text: str, + subtext: str | None, + button: str | None, + recovery_type: RecoveryType, + show_instructions: bool = False, # unused on TT + remaining_shares: Iterable[tuple[str, str]] | None = None, +) -> LayoutObj[UiResult]: + """Device recovery homescreen.""" + + +# rust/src/ui/api/firmware_upy.rs +def flow_confirm_output( + *, + title: str | None, + subtitle: str | None, + message: str, + amount: str | None, + chunkify: bool, + text_mono: bool, + account: str | None, + account_path: str | None, + br_code: ButtonRequestType, + br_name: str, + address: str | None, + address_title: str | None, + summary_items: Iterable[tuple[str, str]] | None = None, + fee_items: Iterable[tuple[str, str]] | None = None, + summary_title: str | None = None, + summary_br_code: ButtonRequestType | None = None, + summary_br_name: str | None = None, + cancel_text: str | None = None, +) -> LayoutObj[UiResult]: + """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page.""" + + +# rust/src/ui/api/firmware_upy.rs +def flow_confirm_set_new_pin( + *, + title: str, + description: str, +) -> LayoutObj[UiResult]: + """Confirm new PIN setup with an option to cancel action.""" + + +# rust/src/ui/api/firmware_upy.rs +def flow_get_address( + *, + address: str | bytes, + title: str, + description: str | None, + extra: str | None, + chunkify: bool, + address_qr: str, + case_sensitive: bool, + account: str | None, + path: str | None, + xpubs: list[tuple[str, str]], + title_success: str, + br_code: ButtonRequestType, + br_name: str, +) -> LayoutObj[UiResult]: + """Get address / receive funds.""" + + +# rust/src/ui/api/firmware_upy.rs +def multiple_pages_texts( + *, + title: str, + verb: str, + items: list[str], +) -> LayoutObj[UiResult]: + """Show multiple texts, each on its own page. TR specific.""" + + +# rust/src/ui/api/firmware_upy.rs +def prompt_backup() -> LayoutObj[UiResult]: + """Strongly recommend user to do a backup.""" + + +# rust/src/ui/api/firmware_upy.rs +def request_bip39( + *, + prompt: str, + prefill_word: str, + can_go_back: bool, +) -> LayoutObj[str]: + """BIP39 word input keyboard.""" + + +# rust/src/ui/api/firmware_upy.rs +def request_slip39( + *, + prompt: str, + prefill_word: str, + can_go_back: bool, +) -> LayoutObj[str]: + """SLIP39 word input keyboard.""" + + +# rust/src/ui/api/firmware_upy.rs +def request_number( + *, + title: str, + count: int, + min_count: int, + max_count: int, + description: str | None = None, + more_info_callback: Callable[[int], str] | None = None, +) -> LayoutObj[tuple[UiResult, int]]: + """Number input with + and - buttons, optional static description and optional dynamic + description.""" + + +# rust/src/ui/api/firmware_upy.rs +def request_pin( + *, + prompt: str, + subprompt: str, + allow_cancel: bool = True, + wrong_pin: bool = False, +) -> LayoutObj[str | UiResult]: + """Request pin on device.""" + + +# rust/src/ui/api/firmware_upy.rs +def request_passphrase( + *, + prompt: str, + max_len: int, +) -> LayoutObj[str | UiResult]: + """Passphrase input keyboard.""" + + +# rust/src/ui/api/firmware_upy.rs +def select_word( + *, + title: str, + description: str, + words: Iterable[str], +) -> LayoutObj[int]: + """Select mnemonic word from three possibilities - seed check after backup. The + iterable must be of exact size. Returns index in range `0..3`.""" + + +# rust/src/ui/api/firmware_upy.rs +def select_word_count( + *, + recovery_type: RecoveryType, +) -> LayoutObj[int | str]: # TR returns str + """Select a mnemonic word count from the options: 12, 18, 20, 24, or 33. + For unlocking a repeated backup, select from 20 or 33.""" + + +# rust/src/ui/api/firmware_upy.rs +def set_brightness( + *, + current: int | None = None +) -> LayoutObj[UiResult]: + """Show the brightness configuration dialog.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_address_details( + *, + qr_title: str, + address: str, + case_sensitive: bool, + details_title: str, + account: str | None, + path: str | None, + xpubs: list[tuple[str, str]], +) -> LayoutObj[UiResult]: + """Show address details - QR code, account, path, cosigner xpubs.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_checklist( + *, + title: str, + items: Iterable[str], + active: int, + button: str, +) -> LayoutObj[UiResult]: + """Checklist of backup steps. Active index is highlighted, previous items have check + mark next to them. Limited to 3 items.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_danger( + *, + title: str, + description: str, + value: str = "", + verb_cancel: str | None = None, +) -> LayoutObj[UiResult]: + """Warning modal that makes it easier to cancel than to continue.""" + + +# 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( + *, + lines: Iterable[str], +) -> LayoutObj[UiResult]: + """Shown after successfully finishing a group.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_homescreen( + *, + label: str | None, + hold: bool, + notification: str | None, + notification_level: int = 0, + skip_first_paint: bool, +) -> LayoutObj[UiResult]: + """Idle homescreen.""" + + # rust/src/ui/api/firmware_upy.rs def show_info( *, @@ -79,6 +539,133 @@ def show_info( """Info screen.""" +# rust/src/ui/api/firmware_upy.rs +def show_info_with_cancel( + *, + title: str, + items: Iterable[Tuple[str, str]], + horizontal: bool = False, + chunkify: bool = False, +) -> LayoutObj[UiResult]: + """Show metadata for outgoing transaction.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_lockscreen( + *, + label: str | None, + bootscreen: bool, + skip_first_paint: bool, + coinjoin_authorized: bool = False, +) -> LayoutObj[UiResult]: + """Homescreen for locked device.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_mismatch(*, title: str) -> LayoutObj[UiResult]: + """Warning of receiving address mismatch.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_progress( + *, + description: str, + indeterminate: bool = False, + title: str | None = None, +) -> LayoutObj[UiResult]: + """Show progress loader. Please note that the number of lines reserved on screen for + description is determined at construction time. If you want multiline descriptions + make sure the initial description has at least that amount of lines.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_progress_coinjoin( + *, + title: str, + indeterminate: bool = False, + time_ms: int = 0, + skip_first_paint: bool = False, +) -> LayoutObj[UiResult]: + """Show progress loader for coinjoin. Returns CANCELLED after a specified time when + time_ms timeout is passed.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_remaining_shares( + *, + pages: Iterable[tuple[str, str]], +) -> LayoutObj[UiResult]: + """Shows SLIP39 state after info button is pressed on `confirm_recovery`.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_share_words( + *, + words: Iterable[str], + title: str | None = None, +) -> LayoutObj[UiResult]: + """Show mnemonic for backup.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_share_words_mercury( + *, + words: Iterable[str], + subtitle: str | None, + instructions: Iterable[str], + text_footer: str | None, + text_confirm: str, +) -> LayoutObj[UiResult]: + """Show mnemonic for wallet backup preceded by an instruction screen and followed by a + confirmation screen.""" + + +# rust/src/ui/api/firmware_upy.rs +def show_simple( + *, + text: str, + title: str | None = None, + button: str | None = None, +) -> LayoutObj[UiResult]: + """Simple dialog with text. TT: optional button.""" + + +# 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.""" + + # rust/src/ui/api/firmware_upy.rs class BacklightLevels: """Backlight levels. Values dynamically update based on user settings.""" diff --git a/core/src/apps/management/apply_settings.py b/core/src/apps/management/apply_settings.py index 2abf304994..54a2c63c38 100644 --- a/core/src/apps/management/apply_settings.py +++ b/core/src/apps/management/apply_settings.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING import storage.device as storage_device -import trezorui2 +import trezorui_api from trezor import TR, utils from trezor.enums import ButtonRequestType, DisplayRotation from trezor.ui.layouts import confirm_action @@ -25,7 +25,7 @@ def _validate_homescreen(homescreen: bytes) -> None: raise DataError( f"Homescreen is too large, maximum size is {storage_device.HOMESCREEN_MAXSIZE} bytes" ) - if not trezorui2.check_homescreen_format(homescreen): + if not trezorui_api.check_homescreen_format(homescreen): raise DataError("Wrong homescreen format") diff --git a/core/src/apps/management/recovery_device/__init__.py b/core/src/apps/management/recovery_device/__init__.py index 08eb02b412..10ca5f6377 100644 --- a/core/src/apps/management/recovery_device/__init__.py +++ b/core/src/apps/management/recovery_device/__init__.py @@ -67,7 +67,7 @@ async def recovery_device(msg: RecoveryDevice) -> Success: return await recovery_process() if recovery_type == RecoveryType.NormalRecovery: - await confirm_reset_device(TR.recovery__title_recover, recovery=True) + await confirm_reset_device(recovery=True) # wipe storage to make sure the device is in a clear state storage.reset() diff --git a/core/src/apps/management/reset_device/__init__.py b/core/src/apps/management/reset_device/__init__.py index 4e38bfcd8c..f7863675c7 100644 --- a/core/src/apps/management/reset_device/__init__.py +++ b/core/src/apps/management/reset_device/__init__.py @@ -55,7 +55,7 @@ async def reset_device(msg: ResetDevice) -> Success: _validate_reset_device(msg) # make sure user knows they're setting up a new wallet - await confirm_reset_device(TR.reset__title_create_wallet) + await confirm_reset_device() # Rendering empty loader so users do not feel a freezing screen render_empty_loader(config.StorageMessage.PROCESSING_MSG) diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py index 372fe6ce6a..5264b9d521 100644 --- a/core/src/trezor/ui/__init__.py +++ b/core/src/trezor/ui/__init__.py @@ -24,7 +24,9 @@ else: if __debug__: - trezorui2.disable_animation(utils.DISABLE_ANIMATION) + from trezorui_api import disable_animation + + disable_animation(utils.DISABLE_ANIMATION) # all rendering is done through a singleton of `Display` diff --git a/core/src/trezor/ui/layouts/homescreen.py b/core/src/trezor/ui/layouts/homescreen.py index f069908b0f..67adf918b6 100644 --- a/core/src/trezor/ui/layouts/homescreen.py +++ b/core/src/trezor/ui/layouts/homescreen.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING import storage.cache as storage_cache -import trezorui2 import trezorui_api from storage.cache_common import APP_COMMON_BUSY_DEADLINE_MS from trezor import TR, ui @@ -57,7 +56,7 @@ class Homescreen(HomescreenBase): level = 0 super().__init__( - layout=trezorui2.show_homescreen( + layout=trezorui_api.show_homescreen( label=label, notification=notification, notification_level=level, @@ -97,7 +96,7 @@ class Lockscreen(HomescreenBase): not bootscreen and storage_cache.homescreen_shown is self.RENDER_INDICATOR ) super().__init__( - layout=trezorui2.show_lockscreen( + layout=trezorui_api.show_lockscreen( label=label, bootscreen=bootscreen, skip_first_paint=skip, @@ -118,7 +117,7 @@ class Busyscreen(HomescreenBase): def __init__(self, delay_ms: int) -> None: super().__init__( - layout=trezorui2.show_progress_coinjoin( + layout=trezorui_api.show_progress_coinjoin( title=TR.coinjoin__waiting_for_others, indeterminate=True, time_ms=delay_ms, diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index a69b3ef0e8..94a035a1ee 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, @@ -74,12 +74,11 @@ def confirm_single( # Placeholders are coming from translations in form of {0} template_str = "{0}" - if template_str not in description: - template_str = "{}" + assert template_str in description begin, _separator, end = description.partition(template_str) return raise_if_not_confirmed( - trezorui2.confirm_emphasized( + trezorui_api.confirm_emphasized( title=title, items=(begin, (True, description_param), end), verb=verb, @@ -89,13 +88,15 @@ def confirm_single( ) -def confirm_reset_device(_title: str, recovery: bool = False) -> Awaitable[None]: - return raise_if_not_confirmed(trezorui2.flow_confirm_reset(recovery=recovery), None) +def confirm_reset_device(recovery: bool = False) -> Awaitable[None]: + return raise_if_not_confirmed( + trezorui_api.confirm_reset_device(recovery=recovery), 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, ) @@ -103,7 +104,7 @@ async def show_wallet_created_success() -> None: async def prompt_backup() -> bool: result = await interact( - trezorui2.flow_prompt_backup(), + trezorui_api.prompt_backup(), "backup_device", ButtonRequestType.ResetDevice, raise_on_cancel=None, @@ -140,7 +141,7 @@ def confirm_multisig_warning() -> Awaitable[None]: def confirm_multisig_different_paths_warning() -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.show_danger( + trezorui_api.show_danger( title=f"{TR.words__important}!", description="Using different paths for different XPUBs.", ), @@ -158,7 +159,7 @@ def confirm_homescreen( workflow.close_others() return raise_if_not_confirmed( - trezorui2.confirm_homescreen( + trezorui_api.confirm_homescreen( title=TR.homescreen__title_set, image=image, ), @@ -242,7 +243,7 @@ async def show_address( ) await raise_if_not_confirmed( - trezorui2.flow_get_address( + trezorui_api.flow_get_address( address=address, title=title or TR.address__title_receive_address, description=network or "", @@ -293,7 +294,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, @@ -315,7 +316,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, @@ -337,7 +338,7 @@ def show_danger( title = title or TR.words__warning verb_cancel = verb_cancel or TR.buttons__cancel return raise_if_not_confirmed( - trezorui2.show_danger( + trezorui_api.show_danger( title=title, description=content, value=(value or ""), @@ -355,8 +356,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, @@ -387,7 +389,7 @@ async def confirm_output( title = TR.send__title_sending_to await raise_if_not_confirmed( - trezorui2.flow_confirm_output( + trezorui_api.flow_confirm_output( title=TR.words__address, subtitle=title, message=address, @@ -422,7 +424,7 @@ async def should_show_payment_request_details( Raises ActionCancelled if the user cancels. """ result = await interact( - trezorui2.confirm_with_info( + trezorui_api.confirm_with_info( title=TR.send__title_sending, items=[(ui.NORMAL, f"{amount} to\n{recipient_name}")] + [(ui.NORMAL, memo) for memo in memos], @@ -459,7 +461,7 @@ async def should_show_more( confirm = TR.buttons__confirm result = await interact( - trezorui2.confirm_with_info( + trezorui_api.confirm_with_info( title=title, items=para, button=confirm, @@ -495,7 +497,7 @@ def confirm_blob( prompt_screen: bool = True, ) -> Awaitable[None]: if ask_pagination: - main_layout = trezorui2.confirm_blob_intro( + main_layout = trezorui_api.confirm_blob_intro( title=title, data=data, subtitle=description, @@ -503,7 +505,7 @@ def confirm_blob( verb_cancel=verb_cancel, chunkify=chunkify, ) - info_layout = trezorui2.confirm_blob( + info_layout = trezorui_api.confirm_blob( title=title, data=data, subtitle=description, @@ -527,7 +529,7 @@ def confirm_blob( info_layout_can_confirm=True, ) else: - layout = trezorui2.confirm_blob( + layout = trezorui_api.confirm_blob( title=title, data=data, description=description, @@ -626,14 +628,14 @@ def confirm_value( raise ValueError("Either verb or hold=True must be set") info_items = info_items or [] - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=info_title if info_title else TR.words__title_information, items=info_items, chunkify=chunkify_info, ) return with_info( - trezorui2.confirm_value( + trezorui_api.confirm_value( title=title, subtitle=subtitle, description=description, @@ -661,7 +663,7 @@ def confirm_properties( items = [(prop[0], prop[1], isinstance(prop[1], bytes)) for prop in props] return raise_if_not_confirmed( - trezorui2.confirm_properties( + trezorui_api.confirm_properties( title=title, items=items, hold=hold, @@ -697,7 +699,7 @@ def confirm_total( fee_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) return raise_if_not_confirmed( - trezorui2.confirm_summary( + trezorui_api.confirm_summary( amount=total_amount, amount_label=total_label, fee=fee_amount, @@ -727,7 +729,7 @@ def _confirm_summary( title = title or TR.words__title_summary # def_arg return raise_if_not_confirmed( - trezorui2.confirm_summary( + trezorui_api.confirm_summary( amount=amount, amount_label=amount_label, fee=fee, @@ -764,7 +766,7 @@ if not utils.BITCOIN_ONLY: chunkify: bool = False, ) -> None: await raise_if_not_confirmed( - trezorui2.flow_confirm_output( + trezorui_api.flow_confirm_output( title=TR.words__address, subtitle=( TR.words__recipient @@ -817,7 +819,7 @@ if not utils.BITCOIN_ONLY: (TR.send__maximum_fee, maximum_fee), ) await raise_if_not_confirmed( - trezorui2.flow_confirm_output( + trezorui_api.flow_confirm_output( title=verb, subtitle=None, message=intro_question, @@ -935,14 +937,14 @@ async def confirm_modify_output( amount_change: str, amount_new: str, ) -> None: - address_layout = trezorui2.confirm_blob( + address_layout = trezorui_api.confirm_blob( title=TR.modify_amount__title, data=address, verb=TR.buttons__continue, verb_cancel=None, description=f"{TR.words__address}:", ) - modify_layout = trezorui2.confirm_modify_output( + modify_layout = trezorui_api.confirm_modify_output( sign=sign, amount_change=amount_change, amount_new=amount_new, @@ -974,7 +976,7 @@ def confirm_modify_fee( total_fee_new: str, fee_rate_amount: str | None = None, ) -> Awaitable[None]: - fee_layout = trezorui2.confirm_modify_fee( + fee_layout = trezorui_api.confirm_modify_fee( title=title, sign=sign, user_fee_change=user_fee_change, @@ -984,7 +986,7 @@ def confirm_modify_fee( items: list[tuple[str, str]] = [] if fee_rate_amount: items.append((TR.bitcoin__new_fee_rate, fee_rate_amount)) - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=TR.confirm_total__title_fee, items=items, ) @@ -993,7 +995,7 @@ def confirm_modify_fee( def confirm_coinjoin(max_rounds: int, max_fee_per_vbyte: str) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_coinjoin( + trezorui_api.confirm_coinjoin( max_rounds=str(max_rounds), max_feerate=max_fee_per_vbyte, ), @@ -1030,7 +1032,7 @@ async def confirm_signverify( address_title = TR.sign_message__confirm_address br_name = "sign_message" - address_layout = trezorui2.confirm_value( + address_layout = trezorui_api.confirm_value( title=address_title, subtitle=None, description="", @@ -1053,17 +1055,18 @@ async def confirm_signverify( ) ) - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=TR.words__title_information, items=items, horizontal=True, ) - message_layout = trezorui2.confirm_blob( + message_layout = trezorui_api.confirm_blob( title=TR.sign_message__confirm_message, description=None, data=message, extra=None, + prompt_screen=True, hold=not verify, info=False, verb=TR.buttons__confirm if verify else None, @@ -1074,7 +1077,7 @@ async def confirm_signverify( await with_info(address_layout, info_layout, br_name, br_code=BR_CODE_OTHER) except ActionCancelled: result = await interact( - trezorui2.show_mismatch(title=TR.addr_mismatch__mismatch), + trezorui_api.show_mismatch(title=TR.addr_mismatch__mismatch), None, raise_on_cancel=None, ) @@ -1103,7 +1106,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, @@ -1114,20 +1117,20 @@ def error_popup( def request_passphrase_on_host() -> None: draw_simple( - trezorui2.show_simple( + trezorui_api.show_simple( title=None, - description=TR.passphrase__please_enter, + text=TR.passphrase__please_enter, ) ) def show_wait_text(message: str) -> None: - draw_simple(trezorui2.show_wait_text(message)) + draw_simple(trezorui_api.show_wait_text(message)) def request_passphrase_on_device(max_len: int) -> Awaitable[str]: result = interact( - trezorui2.flow_request_passphrase( + trezorui_api.request_passphrase( prompt=TR.passphrase__title_enter, max_len=max_len ), "passphrase_device", @@ -1153,7 +1156,7 @@ def request_pin_on_device( subprompt = f"{attempts_remaining} {TR.pin__tries_left}" result = interact( - trezorui2.request_pin( + trezorui_api.request_pin( prompt=prompt, subprompt=subprompt, allow_cancel=allow_cancel, @@ -1207,7 +1210,7 @@ def confirm_set_new_pin( br_code: ButtonRequestType = BR_CODE_OTHER, ) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.flow_confirm_set_new_pin(title=title, description=description), + trezorui_api.flow_confirm_set_new_pin(title=title, description=description), br_name, br_code, ) @@ -1215,7 +1218,7 @@ def confirm_set_new_pin( def confirm_firmware_update(description: str, fingerprint: str) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_firmware_update( + trezorui_api.confirm_firmware_update( description=description, fingerprint=fingerprint ), "firmware_update", @@ -1225,7 +1228,7 @@ def confirm_firmware_update(description: str, fingerprint: str) -> Awaitable[Non def set_brightness(current: int | None = None) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.set_brightness(current=current), + trezorui_api.set_brightness(current=current), "set_brightness", BR_CODE_OTHER, ) @@ -1234,7 +1237,7 @@ def set_brightness(current: int | None = None) -> Awaitable[None]: def tutorial(br_code: ButtonRequestType = BR_CODE_OTHER) -> Awaitable[None]: """Showing users how to interact with the device.""" return raise_if_not_confirmed( - trezorui2.tutorial(), + trezorui_api.tutorial(), "tutorial", br_code, ) diff --git a/core/src/trezor/ui/layouts/mercury/fido.py b/core/src/trezor/ui/layouts/mercury/fido.py index 6ab7aa784a..a4b54e0636 100644 --- a/core/src/trezor/ui/layouts/mercury/fido.py +++ b/core/src/trezor/ui/layouts/mercury/fido.py @@ -1,4 +1,3 @@ -import trezorui2 import trezorui_api from trezor import ui from trezor.enums import ButtonRequestType @@ -13,7 +12,7 @@ async def confirm_fido( accounts: list[str | None], ) -> int: """Webauthn confirmation for one or more credentials.""" - confirm = trezorui2.confirm_fido( + confirm = trezorui_api.confirm_fido( title=header, app_name=app_name, icon_name=icon_name, @@ -44,7 +43,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/mercury/recovery.py b/core/src/trezor/ui/layouts/mercury/recovery.py index 8f0b9c244b..21e1f1736b 100644 --- a/core/src/trezor/ui/layouts/mercury/recovery.py +++ b/core/src/trezor/ui/layouts/mercury/recovery.py @@ -17,9 +17,10 @@ if TYPE_CHECKING: async def request_word_count(recovery_type: RecoveryType) -> int: - selector = trezorui2.select_word_count(recovery_type=recovery_type) count = await interact( - selector, "recovery_word_count", ButtonRequestType.MnemonicWordCount + trezorui_api.select_word_count(recovery_type=recovery_type), + "recovery_word_count", + ButtonRequestType.MnemonicWordCount, ) return int(count) @@ -34,11 +35,11 @@ async def request_word( prompt = TR.recovery__word_x_of_y_template.format(word_index + 1, word_count) can_go_back = word_index > 0 if is_slip39: - keyboard = trezorui2.request_slip39( + keyboard = trezorui_api.request_slip39( prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back ) else: - keyboard = trezorui2.request_bip39( + keyboard = trezorui_api.request_bip39( prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back ) @@ -85,7 +86,7 @@ def format_remaining_shares_info( async def show_group_share_success(share_index: int, group_index: int) -> None: await raise_if_not_confirmed( - trezorui2.show_group_share_success( + trezorui_api.show_group_share_success( lines=[ TR.recovery__you_have_entered, TR.recovery__share_num_template.format(share_index + 1), @@ -99,7 +100,7 @@ async def show_group_share_success(share_index: int, group_index: int) -> None: async def continue_recovery( - button_label: str, # unused on mercury + _button_label: str, # unused on mercury text: str, subtext: str | None, recovery_type: RecoveryType, @@ -107,12 +108,13 @@ async def continue_recovery( remaining_shares_info: "RemainingSharesInfo | None" = None, ) -> bool: result = await interact( - trezorui2.flow_continue_recovery( - first_screen=show_instructions, - recovery_type=recovery_type, + trezorui_api.continue_recovery_homepage( text=text, subtext=subtext, - pages=( + button=None, + recovery_type=recovery_type, + show_instructions=show_instructions, + remaining_shares=( format_remaining_shares_info(remaining_shares_info) if remaining_shares_info else None @@ -134,7 +136,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 1e9d853a82..27b31bc399 100644 --- a/core/src/trezor/ui/layouts/mercury/reset.py +++ b/core/src/trezor/ui/layouts/mercury/reset.py @@ -1,6 +1,5 @@ from typing import Awaitable, Callable, Sequence -import trezorui2 import trezorui_api from trezor import TR, ui from trezor.enums import ButtonRequestType @@ -17,9 +16,8 @@ def show_share_words( share_index: int | None = None, group_index: int | None = None, ) -> Awaitable[None]: - title = TR.reset__recovery_wallet_backup_title if share_index is None: - subtitle = "" + subtitle = None elif group_index is None: subtitle = TR.reset__recovery_share_title_template.format(share_index + 1) else: @@ -27,24 +25,24 @@ def show_share_words( group_index + 1, share_index + 1 ) words_count = len(share_words) - description = "" - text_info = [TR.reset__write_down_words_template.format(words_count)] + description = None + instructions = [TR.reset__write_down_words_template.format(words_count)] if words_count == 20 and share_index is None: # 1-of-1 SLIP39: inform the user about repeated words - text_info.append(TR.reset__words_may_repeat) + instructions.append(TR.reset__words_may_repeat) if share_index == 0: # regular SLIP39, 1st share description = TR.instructions__shares_start_with_1 - text_info.append(TR.reset__repeat_for_all_shares) + instructions.append(TR.reset__repeat_for_all_shares) + assert len(instructions) < 3 text_confirm = TR.reset__words_written_down_template.format(words_count) return raise_if_not_confirmed( - trezorui2.flow_show_share_words( - title=title, - subtitle=subtitle, + trezorui_api.show_share_words_mercury( words=share_words, - description=description, - text_info=text_info, + subtitle=subtitle, + instructions=instructions, + text_footer=description, text_confirm=text_confirm, ), None, @@ -75,7 +73,7 @@ async def select_word( words.append(words[-1]) result = await interact( - trezorui2.select_word( + trezorui_api.select_word( title=title, description=TR.reset__select_word_x_of_y_template.format( checked_index + 1, count @@ -98,7 +96,7 @@ async def slip39_show_checklist( ) -> None: items = _slip_39_checklist_items(step, advanced, count, threshold) result = await interact( - trezorui2.show_checklist( + trezorui_api.show_checklist( title=TR.reset__title_shamir_backup, button=TR.buttons__continue, active=step, @@ -155,17 +153,17 @@ async def _prompt_number( br_name: str, ) -> int: result = await interact( - trezorui2.flow_request_number( + trezorui_api.request_number( title=title, - description=description, count=count, min_count=min_count, max_count=max_count, - info=info, - br_code=ButtonRequestType.ResetDevice, - br_name=br_name, + description=description, + more_info_callback=info, ), - None, + br_name, + ButtonRequestType.ResetDevice, + raise_on_cancel=None, ) if __debug__ and result is CONFIRMED: @@ -301,7 +299,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 +326,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/progress.py b/core/src/trezor/ui/layouts/progress.py index fae4c73a9f..da67b4f9a0 100644 --- a/core/src/trezor/ui/layouts/progress.py +++ b/core/src/trezor/ui/layouts/progress.py @@ -1,4 +1,4 @@ -import trezorui2 +import trezorui_api from trezor import TR, config, ui, utils @@ -30,7 +30,7 @@ def progress( description = TR.progress__please_wait # def_arg return ui.ProgressLayout( - layout=trezorui2.show_progress( + layout=trezorui_api.show_progress( description=description, title=title, indeterminate=indeterminate, @@ -44,7 +44,7 @@ def bitcoin_progress(message: str) -> ui.ProgressLayout: def coinjoin_progress(message: str) -> ui.ProgressLayout: return ui.ProgressLayout( - layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False) + layout=trezorui_api.show_progress_coinjoin(title=message, indeterminate=False) ) diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index 9c409d0b53..46ae05c742 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, @@ -109,19 +109,10 @@ def confirm_single( def confirm_reset_device( - title: str, recovery: bool = False, ) -> Awaitable[None]: - if recovery: - button = TR.reset__button_recover - else: - button = TR.reset__button_create - return raise_if_not_confirmed( - trezorui2.confirm_reset_device( - title=title, - button=button, - ), + trezorui_api.confirm_reset_device(recovery=recovery), "recover_device" if recovery else "setup_device", ButtonRequestType.ProtectCall if recovery else ButtonRequestType.ResetDevice, ) @@ -137,7 +128,7 @@ async def prompt_backup() -> bool: br_code = ButtonRequestType.ResetDevice result = await interact( - trezorui2.confirm_backup(), + trezorui_api.prompt_backup(), br_name, br_code, raise_on_cancel=None, @@ -146,7 +137,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, @@ -192,7 +183,7 @@ def confirm_multisig_different_paths_warning() -> Awaitable[ui.UiResult]: def confirm_homescreen(image: bytes) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_homescreen( + trezorui_api.confirm_homescreen( title=TR.homescreen__title_set, image=image, ), @@ -265,7 +256,7 @@ async def show_address( while True: result = await interact( - trezorui2.confirm_address( + trezorui_api.confirm_address( title=title, data=address, description="", # unused on TR @@ -296,7 +287,7 @@ async def show_address( return result result = await interact( - trezorui2.show_address_details( + trezorui_api.show_address_details( qr_title="", # unused on this model address=address if address_qr is None else address_qr, case_sensitive=case_sensitive, @@ -314,7 +305,7 @@ async def show_address( # User pressed left cancel button, show mismatch dialogue. else: result = await interact( - trezorui2.show_mismatch(title=mismatch_title), + trezorui_api.show_mismatch(title=mismatch_title), None, raise_on_cancel=None, ) @@ -410,9 +401,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, @@ -494,10 +486,13 @@ async def confirm_output( while True: await interact( - trezorui2.confirm_output_address( - address=address, - address_label=address_label or "", - address_title=address_title, + trezorui_api.confirm_blob( + title=address_title, + data=address, + description=address_label or "", + subtitle=None, + verb=TR.buttons__continue, + verb_cancel="", chunkify=chunkify, ), "confirm_output", @@ -506,9 +501,13 @@ async def confirm_output( try: await interact( - trezorui2.confirm_output_amount( - amount_title=amount_title, - amount=amount, + trezorui_api.confirm_blob( + title=amount_title, + data=amount, + description=None, + subtitle=None, + verb_cancel="^", + verb=TR.buttons__confirm, ), "confirm_output", br_code, @@ -522,7 +521,7 @@ async def confirm_output( def tutorial(br_code: ButtonRequestType = BR_CODE_OTHER) -> Awaitable[ui.UiResult]: """Showing users how to interact with the device.""" - return interact(trezorui2.tutorial(), "tutorial", br_code) + return interact(trezorui_api.tutorial(), "tutorial", br_code) async def should_show_payment_request_details( @@ -559,11 +558,11 @@ async def should_show_more( confirm = TR.buttons__confirm result = await interact( - trezorui2.confirm_with_info( + trezorui_api.confirm_with_info( title=title, items=para, button=confirm, - verb_cancel=verb_cancel, # type: ignore [No parameter named "verb_cancel"] + verb_cancel=verb_cancel, info_button=button_text, # unused on TR ), br_name, @@ -594,7 +593,7 @@ def confirm_blob( prompt_screen: bool = True, ) -> Awaitable[None]: verb = verb or TR.buttons__confirm # def_arg - layout = trezorui2.confirm_blob( + layout = trezorui_api.confirm_blob( title=title, description=description, data=data, @@ -624,7 +623,7 @@ async def _confirm_ask_pagination( data = hexlify(data).decode() - confirm_more_layout = trezorui2.confirm_more( + confirm_more_layout = trezorui_api.confirm_more( title=title, button=TR.buttons__confirm, items=[(ui.NORMAL, description), (ui.MONO, data)], @@ -720,7 +719,7 @@ def confirm_properties( return (key, value, bool(is_data)) return raise_if_not_confirmed( - trezorui2.confirm_properties( + trezorui_api.confirm_properties( title=title, items=map(handle_bytes, props), # type: ignore [cannot be assigned to parameter "items"] hold=hold, @@ -749,11 +748,12 @@ async def confirm_value( if info_items is None: return await raise_if_not_confirmed( - trezorui2.confirm_value( # type: ignore [Argument missing for parameter "subtitle"] + trezorui_api.confirm_value( # type: ignore [Argument missing for parameter "subtitle"] title=title, description=description, value=value, verb=verb or TR.buttons__hold_to_confirm, + verb_cancel="", hold=hold, ), br_name, @@ -768,7 +768,7 @@ async def confirm_value( send_button_request = True while True: result = await interact( - trezorui2.confirm_with_info( + trezorui_api.confirm_with_info( title=title, items=((ui.NORMAL, value),), button=verb or TR.buttons__confirm, @@ -784,7 +784,7 @@ async def confirm_value( elif result is INFO: info_title, info_value = info_items_list[0] await interact( - trezorui2.confirm_blob( + trezorui_api.confirm_blob( title=info_title, data=info_value, description=description, @@ -824,7 +824,7 @@ def confirm_total( account_info_items.append((TR.words__account_colon, source_account)) return raise_if_not_confirmed( - trezorui2.confirm_summary( + trezorui_api.confirm_summary( amount=total_amount, amount_label=total_label, fee=fee_amount, @@ -880,7 +880,7 @@ if not utils.BITCOIN_ONLY: amount_title = f"{TR.words__amount}:" amount_value = total_amount await raise_if_not_confirmed( - trezorui2.confirm_summary( + trezorui_api.confirm_summary( amount=amount_value, amount_label=amount_title, fee=maximum_fee, @@ -906,7 +906,7 @@ if not utils.BITCOIN_ONLY: ) # def_arg fee_title = fee_title or TR.words__fee # def_arg return raise_if_not_confirmed( - trezorui2.confirm_summary( + trezorui_api.confirm_summary( amount=amount, amount_label=amount_title, fee=fee, @@ -928,7 +928,7 @@ if not utils.BITCOIN_ONLY: fee_title = TR.send__including_fee return raise_if_not_confirmed( - trezorui2.confirm_summary( + trezorui_api.confirm_summary( amount=amount, amount_label=amount_title, fee=fee, @@ -951,7 +951,7 @@ if not utils.BITCOIN_ONLY: br_code: ButtonRequestType = ButtonRequestType.SignTx, chunkify: bool = False, ) -> None: - summary_layout = trezorui2.confirm_summary( + summary_layout = trezorui_api.confirm_summary( amount=total_amount, amount_label=f"{TR.words__amount}:", fee=maximum_fee, @@ -987,13 +987,15 @@ if not utils.BITCOIN_ONLY: def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]: - return raise_if_not_confirmed( - trezorui2.confirm_joint_total( - spending_amount=spending_amount, - total_amount=total_amount, - ), + return confirm_properties( "confirm_joint_total", - ButtonRequestType.SignTx, + TR.joint__title, + [ + (TR.joint__you_are_contributing, spending_amount), + (TR.joint__to_the_total_amount, total_amount), + ], + hold=True, + br_code=ButtonRequestType.SignTx, ) @@ -1031,14 +1033,14 @@ async def confirm_modify_output( amount_change: str, amount_new: str, ) -> None: - address_layout = trezorui2.confirm_blob( + address_layout = trezorui_api.confirm_blob( title=TR.modify_amount__title, data=address, verb=TR.buttons__continue, description=f"{TR.words__address}:", ) - modify_layout = trezorui2.confirm_modify_output( + modify_layout = trezorui_api.confirm_modify_output( sign=sign, amount_change=amount_change, amount_new=amount_new, @@ -1072,7 +1074,7 @@ def confirm_modify_fee( fee_rate_amount: str | None = None, ) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_modify_fee( + trezorui_api.confirm_modify_fee( title=title, sign=sign, user_fee_change=user_fee_change, @@ -1086,7 +1088,7 @@ def confirm_modify_fee( def confirm_coinjoin(max_rounds: int, max_fee_per_vbyte: str) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_coinjoin( + trezorui_api.confirm_coinjoin( max_rounds=str(max_rounds), max_feerate=max_fee_per_vbyte, ), @@ -1133,7 +1135,7 @@ async def confirm_signverify( ) try: await raise_if_not_confirmed( - trezorui2.confirm_blob( + trezorui_api.confirm_blob( title=TR.sign_message__confirm_message, description=None, data=message, @@ -1174,16 +1176,21 @@ def error_popup( def request_passphrase_on_host() -> None: - draw_simple(trezorui2.show_passphrase()) + draw_simple( + trezorui_api.show_simple( + title=None, + text=TR.passphrase__please_enter, + ) + ) def show_wait_text(message: str) -> None: - draw_simple(trezorui2.show_wait_text(message)) + draw_simple(trezorui_api.show_wait_text(message)) async def request_passphrase_on_device(max_len: int) -> str: result = await interact( - trezorui2.request_passphrase( + trezorui_api.request_passphrase( prompt=TR.passphrase__title_enter, max_len=max_len, ), @@ -1213,7 +1220,7 @@ async def request_pin_on_device( subprompt = f"{attempts_remaining} {TR.pin__tries_left}" result = await interact( - trezorui2.request_pin( + trezorui_api.request_pin( prompt=prompt, subprompt=subprompt, allow_cancel=allow_cancel, @@ -1252,7 +1259,7 @@ def _confirm_multiple_pages_texts( br_code: ButtonRequestType = BR_CODE_OTHER, ) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.multiple_pages_texts( + trezorui_api.multiple_pages_texts( title=title, verb=verb, items=items, @@ -1321,7 +1328,7 @@ async def confirm_set_new_pin( def confirm_firmware_update(description: str, fingerprint: str) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_firmware_update( + trezorui_api.confirm_firmware_update( description=description, fingerprint=fingerprint ), "firmware_update", diff --git a/core/src/trezor/ui/layouts/tr/fido.py b/core/src/trezor/ui/layouts/tr/fido.py index 4aaaad634d..18bff9df3a 100644 --- a/core/src/trezor/ui/layouts/tr/fido.py +++ b/core/src/trezor/ui/layouts/tr/fido.py @@ -1,4 +1,3 @@ -import trezorui2 import trezorui_api from trezor import ui from trezor.enums import ButtonRequestType @@ -9,13 +8,14 @@ from ..common import interact async def confirm_fido( header: str, app_name: str, - icon_name: str | None, + _icon_name: str | None, # unused on TR accounts: list[str | None], ) -> int: """Webauthn confirmation for one or more credentials.""" - confirm = trezorui2.confirm_fido( # type: ignore [Argument missing for parameter "icon_name"] + confirm = trezorui_api.confirm_fido( # type: ignore [Argument missing for parameter "icon_name"] title=header, app_name=app_name, + icon_name=None, accounts=accounts, ) result = await interact(confirm, "confirm_fido", ButtonRequestType.Other) @@ -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/tr/recovery.py b/core/src/trezor/ui/layouts/tr/recovery.py index 82786672c0..36496b914e 100644 --- a/core/src/trezor/ui/layouts/tr/recovery.py +++ b/core/src/trezor/ui/layouts/tr/recovery.py @@ -16,7 +16,7 @@ if TYPE_CHECKING: async def request_word_count(recovery_type: RecoveryType) -> int: count = await interact( - trezorui2.select_word_count(recovery_type=recovery_type), + trezorui_api.select_word_count(recovery_type=recovery_type), "recovery_word_count", ButtonRequestType.MnemonicWordCount, ) @@ -36,12 +36,12 @@ async def request_word( can_go_back = word_index > 0 if is_slip39: - keyboard = trezorui2.request_slip39( + keyboard = trezorui_api.request_slip39( prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back ) else: - keyboard = trezorui2.request_bip39( + keyboard = trezorui_api.request_bip39( prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back ) @@ -65,7 +65,7 @@ def show_group_share_success( share_index: int, group_index: int ) -> Awaitable[ui.UiResult]: return interact( - trezorui2.show_group_share_success( + trezorui_api.show_group_share_success( lines=[ TR.recovery__you_have_entered, TR.recovery__share_num_template.format(share_index + 1), @@ -123,13 +123,13 @@ async def continue_recovery( if subtext: text += f"\n\n{subtext}" - homepage = trezorui2.confirm_recovery( - title="", - description=text, + homepage = trezorui_api.continue_recovery_homepage( + text=text, + subtext=None, button=button_label, recovery_type=recovery_type, - info_button=False, show_instructions=show_instructions, + remaining_shares=None, ) while True: result = await interact( diff --git a/core/src/trezor/ui/layouts/tr/reset.py b/core/src/trezor/ui/layouts/tr/reset.py index 787940b817..1dd8d684bd 100644 --- a/core/src/trezor/ui/layouts/tr/reset.py +++ b/core/src/trezor/ui/layouts/tr/reset.py @@ -1,6 +1,5 @@ from typing import TYPE_CHECKING -import trezorui2 from trezor import TR from trezor.enums import ButtonRequestType import trezorui_api @@ -50,8 +49,9 @@ async def show_share_words( ) result = await interact( - trezorui2.show_share_words( # type: ignore [Arguments missing for parameters] - share_words=share_words, # type: ignore [No parameter named "share_words"] + trezorui_api.show_share_words( + words=share_words, + title=None, ), br_name, br_code, @@ -88,7 +88,7 @@ async def select_word( word_ordinal = format_ordinal(checked_index + 1) result = await interact( - trezorui2.select_word( + trezorui_api.select_word( title="", description=TR.reset__select_word_template.format(word_ordinal), words=(words[0].lower(), words[1].lower(), words[2].lower()), @@ -122,7 +122,7 @@ def slip39_show_checklist( ) return raise_if_not_confirmed( - trezorui2.show_checklist( + trezorui_api.show_checklist( title=TR.reset__slip39_checklist_title, button=TR.buttons__continue, active=step, @@ -140,7 +140,7 @@ async def _prompt_number( max_count: int, br_name: str, ) -> int: - num_input = trezorui2.request_number( + num_input = trezorui_api.request_number( title=title, count=count, min_count=min_count, diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index b0e670bf10..0b0b013ffc 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, @@ -74,7 +74,7 @@ def confirm_single( begin, _separator, end = description.partition(template_str) return raise_if_not_confirmed( - trezorui2.confirm_emphasized( + trezorui_api.confirm_emphasized( title=title, items=(begin, (True, description_param), end), verb=verb, @@ -84,17 +84,9 @@ def confirm_single( ) -def confirm_reset_device(title: str, recovery: bool = False) -> Awaitable[None]: - if recovery: - button = TR.reset__button_recover - else: - button = TR.reset__button_create - +def confirm_reset_device(recovery: bool = False) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_reset_device( - title=title, - button=button, - ), + trezorui_api.confirm_reset_device(recovery=recovery), "recover_device" if recovery else "setup_device", (ButtonRequestType.ProtectCall if recovery else ButtonRequestType.ResetDevice), ) @@ -108,7 +100,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 +115,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, @@ -144,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, @@ -173,7 +165,7 @@ def confirm_multisig_different_paths_warning() -> Awaitable[None]: def confirm_homescreen(image: bytes) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_homescreen( + trezorui_api.confirm_homescreen( title=TR.homescreen__title_set, image=image, ), @@ -250,7 +242,7 @@ async def show_address( while True: result = await interact( - trezorui2.confirm_address( + trezorui_api.confirm_address( title=title, data=address, description=network or "", @@ -281,7 +273,7 @@ async def show_address( return result result = await interact( - trezorui2.show_address_details( + trezorui_api.show_address_details( qr_title=title, address=address if address_qr is None else address_qr, case_sensitive=case_sensitive, @@ -297,7 +289,7 @@ async def show_address( else: result = await interact( - trezorui2.show_mismatch(title=mismatch_title), + trezorui_api.show_mismatch(title=mismatch_title), None, raise_on_cancel=None, ) @@ -339,7 +331,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, @@ -363,7 +355,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, @@ -397,7 +389,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, @@ -436,7 +428,7 @@ async def confirm_output( while True: # if the user cancels here, raise ActionCancelled (by default) await interact( - trezorui2.confirm_value( + trezorui_api.confirm_value( title=recipient_title, subtitle=address_label, description=None, @@ -452,7 +444,7 @@ async def confirm_output( try: await interact( - trezorui2.confirm_value( + trezorui_api.confirm_value( title=amount_title, subtitle=None, description=None, @@ -483,7 +475,7 @@ async def should_show_payment_request_details( Raises ActionCancelled if the user cancels. """ result = await interact( - trezorui2.confirm_with_info( + trezorui_api.confirm_with_info( title=TR.send__title_sending, items=[(ui.NORMAL, f"{amount} to\n{recipient_name}")] + [(ui.NORMAL, memo) for memo in memos], @@ -520,7 +512,7 @@ async def should_show_more( confirm = TR.buttons__confirm result = await interact( - trezorui2.confirm_with_info( + trezorui_api.confirm_with_info( title=title, items=para, button=confirm, @@ -551,7 +543,7 @@ async def _confirm_ask_pagination( data = hexlify(data).decode() - confirm_more_layout = trezorui2.confirm_more( + confirm_more_layout = trezorui_api.confirm_more( title=title, button=TR.buttons__confirm, button_style_confirm=True, @@ -592,7 +584,7 @@ def confirm_blob( prompt_screen: bool = True, ) -> Awaitable[None]: verb = verb or TR.buttons__confirm # def_arg - layout = trezorui2.confirm_blob( + layout = trezorui_api.confirm_blob( title=title, description=description, text_mono=text_mono, @@ -691,14 +683,14 @@ def confirm_value( raise ValueError("Either verb or hold=True must be set") info_items = info_items or [] - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=info_title if info_title else TR.words__title_information, items=info_items, chunkify=chunkify_info, ) return with_info( - trezorui2.confirm_value( + trezorui_api.confirm_value( title=title, subtitle=subtitle, description=description, @@ -725,7 +717,7 @@ def confirm_properties( items = [(prop[0], prop[1], isinstance(prop[1], bytes)) for prop in props] return raise_if_not_confirmed( - trezorui2.confirm_properties( + trezorui_api.confirm_properties( title=title, items=items, hold=hold, @@ -788,7 +780,7 @@ def _confirm_summary( ) -> Awaitable[None]: title = title or TR.words__title_summary # def_arg - total_layout = trezorui2.confirm_summary( + total_layout = trezorui_api.confirm_summary( amount=amount, amount_label=amount_label, fee=fee, @@ -804,7 +796,7 @@ def _confirm_summary( info_items.extend(account_items) if extra_items: info_items.extend(extra_items) - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=extra_title if extra_title else TR.words__title_information, items=info_items, ) @@ -831,7 +823,7 @@ if not utils.BITCOIN_ONLY: chunkify: bool = False, ) -> None: # NOTE: fee_info used so that info button is shown - total_layout = trezorui2.confirm_summary( + total_layout = trezorui_api.confirm_summary( amount=total_amount, amount_label=f"{TR.words__amount}:", fee=maximum_fee, @@ -841,7 +833,7 @@ if not utils.BITCOIN_ONLY: extra_title=TR.confirm_total__title_fee, verb_cancel="^", ) - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=TR.confirm_total__title_fee, items=[(f"{k}:", v) for (k, v) in fee_info_items], ) @@ -965,7 +957,7 @@ if not utils.BITCOIN_ONLY: def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]: return raise_if_not_confirmed( # FIXME: arguments for amount/fee are misused here - trezorui2.confirm_summary( + trezorui_api.confirm_summary( amount=spending_amount, amount_label=TR.send__you_are_contributing, fee=total_amount, @@ -1020,7 +1012,7 @@ async def confirm_modify_output( while True: # if the user cancels here, raise ActionCancelled (by default) await interact( - trezorui2.confirm_blob( + trezorui_api.confirm_blob( title="MODIFY AMOUNT", data=address, verb="CONTINUE", @@ -1033,7 +1025,7 @@ async def confirm_modify_output( try: await interact( - trezorui2.confirm_modify_output( + trezorui_api.confirm_modify_output( sign=sign, amount_change=amount_change, amount_new=amount_new, @@ -1056,7 +1048,7 @@ def confirm_modify_fee( total_fee_new: str, fee_rate_amount: str | None = None, ) -> Awaitable[None]: - fee_layout = trezorui2.confirm_modify_fee( + fee_layout = trezorui_api.confirm_modify_fee( title=title, sign=sign, user_fee_change=user_fee_change, @@ -1066,7 +1058,7 @@ def confirm_modify_fee( items: list[tuple[str, str]] = [] if fee_rate_amount: items.append((TR.bitcoin__new_fee_rate, fee_rate_amount)) - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=TR.confirm_total__title_fee, items=items, ) @@ -1075,7 +1067,7 @@ def confirm_modify_fee( def confirm_coinjoin(max_rounds: int, max_fee_per_vbyte: str) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_coinjoin( + trezorui_api.confirm_coinjoin( max_rounds=str(max_rounds), max_feerate=max_fee_per_vbyte, ), @@ -1112,7 +1104,7 @@ async def confirm_signverify( address_title = TR.sign_message__confirm_address br_name = "sign_message" - address_layout = trezorui2.confirm_address( + address_layout = trezorui_api.confirm_address( title=address_title, data=address, description="", @@ -1133,13 +1125,13 @@ async def confirm_signverify( ) ) - info_layout = trezorui2.show_info_with_cancel( + info_layout = trezorui_api.show_info_with_cancel( title=TR.words__title_information, items=items, horizontal=True, ) - message_layout = trezorui2.confirm_blob( + message_layout = trezorui_api.confirm_blob( title=TR.sign_message__confirm_message, description=None, data=message, @@ -1152,7 +1144,7 @@ async def confirm_signverify( await with_info(address_layout, info_layout, br_name, br_code=BR_CODE_OTHER) except ActionCancelled: result = await interact( - trezorui2.show_mismatch(title=TR.addr_mismatch__mismatch), + trezorui_api.show_mismatch(title=TR.addr_mismatch__mismatch), None, raise_on_cancel=None, ) @@ -1184,7 +1176,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, @@ -1196,20 +1188,22 @@ def error_popup( def request_passphrase_on_host() -> None: draw_simple( - trezorui2.show_simple( + trezorui_api.show_simple( title=None, - description=TR.passphrase__please_enter, + text=TR.passphrase__please_enter, ) ) def show_wait_text(message: str) -> None: - draw_simple(trezorui2.show_wait_text(message)) + draw_simple(trezorui_api.show_wait_text(message)) async def request_passphrase_on_device(max_len: int) -> str: result = await interact( - trezorui2.request_passphrase(prompt="Enter passphrase", max_len=max_len), + trezorui_api.request_passphrase( + prompt=TR.passphrase__title_enter, max_len=max_len + ), "passphrase_device", ButtonRequestType.PassphraseEntry, raise_on_cancel=ActionCancelled("Passphrase entry cancelled"), @@ -1234,7 +1228,7 @@ async def request_pin_on_device( subprompt = f"{attempts_remaining} {TR.pin__tries_left}" result = await interact( - trezorui2.request_pin( + trezorui_api.request_pin( prompt=prompt, subprompt=subprompt, allow_cancel=allow_cancel, @@ -1290,7 +1284,7 @@ def confirm_set_new_pin( br_code: ButtonRequestType = BR_CODE_OTHER, ) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_emphasized( + trezorui_api.confirm_emphasized( title=title, items=( (True, description + "\n\n"), @@ -1305,7 +1299,7 @@ def confirm_set_new_pin( def confirm_firmware_update(description: str, fingerprint: str) -> Awaitable[None]: return raise_if_not_confirmed( - trezorui2.confirm_firmware_update( + trezorui_api.confirm_firmware_update( description=description, fingerprint=fingerprint ), "firmware_update", @@ -1315,7 +1309,7 @@ def confirm_firmware_update(description: str, fingerprint: str) -> Awaitable[Non async def set_brightness(current: int | None = None) -> None: await interact( - trezorui2.set_brightness(current=current), + trezorui_api.set_brightness(current=current), "set_brightness", BR_CODE_OTHER, ) diff --git a/core/src/trezor/ui/layouts/tt/fido.py b/core/src/trezor/ui/layouts/tt/fido.py index 76d071ab69..64940470d7 100644 --- a/core/src/trezor/ui/layouts/tt/fido.py +++ b/core/src/trezor/ui/layouts/tt/fido.py @@ -1,4 +1,3 @@ -import trezorui2 import trezorui_api from trezor import ui from trezor.enums import ButtonRequestType @@ -13,7 +12,7 @@ async def confirm_fido( accounts: list[str | None], ) -> int: """Webauthn confirmation for one or more credentials.""" - confirm = trezorui2.confirm_fido( + confirm = trezorui_api.confirm_fido( title=header, app_name=app_name, icon_name=icon_name, @@ -53,7 +52,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/tt/recovery.py b/core/src/trezor/ui/layouts/tt/recovery.py index 7de8e3be45..8f19c1d49d 100644 --- a/core/src/trezor/ui/layouts/tt/recovery.py +++ b/core/src/trezor/ui/layouts/tt/recovery.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: async def request_word_count(recovery_type: RecoveryType) -> int: count = await interact( - trezorui2.select_word_count(recovery_type=recovery_type), + trezorui_api.select_word_count(recovery_type=recovery_type), "recovery_word_count", ButtonRequestType.MnemonicWordCount, ) @@ -34,12 +34,12 @@ async def request_word( prompt = TR.recovery__type_word_x_of_y_template.format(word_index + 1, word_count) can_go_back = word_index > 0 if is_slip39: - keyboard = trezorui2.request_slip39( + keyboard = trezorui_api.request_slip39( prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back ) else: - keyboard = trezorui2.request_bip39( + keyboard = trezorui_api.request_bip39( prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back ) @@ -51,14 +51,14 @@ async def request_word( return word -def show_remaining_shares( - groups: set[tuple[str, ...]], - shares_remaining: list[int], - group_threshold: int, -) -> Awaitable[trezorui_api.UiResult]: +def format_remaining_shares_info( + remaining_shares_info: "RemainingSharesInfo", +) -> list[tuple[str, str]]: from trezor import strings from trezor.crypto.slip39 import MAX_SHARE_COUNT + groups, shares_remaining, group_threshold = remaining_shares_info + pages: list[tuple[str, str]] = [] completed_groups = shares_remaining.count(0) @@ -81,8 +81,14 @@ def show_remaining_shares( words = "\n".join(group) pages.append((title, words)) + return pages + + +def show_remaining_shares( + pages: list[tuple[str, str]], +) -> Awaitable[trezorui_api.UiResult]: return interact( - trezorui2.show_remaining_shares(pages=pages), + trezorui_api.show_remaining_shares(pages=pages), "show_shares", ButtonRequestType.Other, ) @@ -92,7 +98,7 @@ def show_group_share_success( share_index: int, group_index: int ) -> Awaitable[ui.UiResult]: return interact( - trezorui2.show_group_share_success( + trezorui_api.show_group_share_success( lines=[ TR.recovery__you_have_entered, TR.recovery__share_num_template.format(share_index + 1), @@ -146,12 +152,17 @@ async def continue_recovery( else: description = subtext or "" - homepage = trezorui2.confirm_recovery( - title=text, - description=description, + remaining_shares = ( + format_remaining_shares_info(remaining_shares_info) + if remaining_shares_info + else None + ) + homepage = trezorui_api.continue_recovery_homepage( + text=text, + subtext=description, button=button_label, recovery_type=recovery_type, - info_button=remaining_shares_info is not None, + remaining_shares=remaining_shares, ) while True: @@ -164,8 +175,8 @@ async def continue_recovery( if result is trezorui_api.CONFIRMED: return True - elif result is trezorui_api.INFO and remaining_shares_info is not None: - await show_remaining_shares(*remaining_shares_info) + elif result is trezorui_api.INFO and remaining_shares is not None: + await show_remaining_shares(remaining_shares) else: try: await _confirm_abort(recovery_type != RecoveryType.NormalRecovery) @@ -185,7 +196,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 2aa2a7a70a..ec4e1675a0 100644 --- a/core/src/trezor/ui/layouts/tt/reset.py +++ b/core/src/trezor/ui/layouts/tt/reset.py @@ -1,6 +1,5 @@ from typing import Awaitable, Callable, Sequence -import trezorui2 import trezorui_api from trezor import TR from trezor.enums import ButtonRequestType @@ -10,30 +9,6 @@ from ..common import interact, raise_if_not_confirmed CONFIRMED = trezorui_api.CONFIRMED # global_import_cache -def _split_share_into_pages(share_words: Sequence[str], per_page: int = 4) -> list[str]: - pages: list[str] = [] - current = "" - fill = 2 - - for i, word in enumerate(share_words): - if i % per_page == 0: - if i != 0: - pages.append(current) - current = "" - - # Align numbers to the right. - lastnum = i + per_page + 1 - fill = 1 if lastnum < 10 else 2 - else: - current += "\n" - current += f"{i + 1:>{fill}}. {word}" - - if current: - pages.append(current) - - return pages - - def show_share_words( share_words: Sequence[str], share_index: int | None = None, @@ -48,12 +23,10 @@ def show_share_words( group_index + 1, share_index + 1 ) - pages = _split_share_into_pages(share_words) - return raise_if_not_confirmed( - trezorui2.show_share_words( + trezorui_api.show_share_words( + words=share_words, title=title, - pages=pages, ), "backup_words", ButtonRequestType.ResetDevice, @@ -84,7 +57,7 @@ async def select_word( words.append(words[-1]) result = await interact( - trezorui2.select_word( + trezorui_api.select_word( title=title, description=TR.reset__select_word_x_of_y_template.format( checked_index + 1, count @@ -120,7 +93,7 @@ def slip39_show_checklist( ) return raise_if_not_confirmed( - trezorui2.show_checklist( + trezorui_api.show_checklist( title=TR.reset__slip39_checklist_title, button=TR.buttons__continue, active=step, @@ -140,12 +113,13 @@ async def _prompt_number( max_count: int, br_name: str, ) -> int: - num_input = trezorui2.request_number( + num_input = trezorui_api.request_number( title=title, - description=description, count=count, min_count=min_count, max_count=max_count, + description=None, + more_info_callback=description, ) while True: @@ -167,9 +141,9 @@ async def _prompt_number( return value await interact( - trezorui2.show_simple( + trezorui_api.show_simple( title=None, - description=info(value), + text=info(value), button=TR.buttons__ok_i_understand, ), None, @@ -354,7 +328,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,