diff --git a/core/assets/info.png b/core/assets/info.png new file mode 100644 index 0000000000..af15fbf3d9 Binary files /dev/null and b/core/assets/info.png differ diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 81c22d3c66..1b0dec8883 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -26,9 +26,11 @@ static void _librust_qstrs(void) { MP_QSTR_confirm_payment_request; MP_QSTR_confirm_text; MP_QSTR_confirm_total; + MP_QSTR_show_error; MP_QSTR_show_qr; MP_QSTR_show_success; MP_QSTR_show_warning; + MP_QSTR_show_info; MP_QSTR_request_pin; MP_QSTR_request_passphrase; MP_QSTR_request_bip39; diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 6d77d8c73d..cc84838901 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -432,42 +432,71 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +fn new_show_modal( + kwargs: &Map, + icon: &'static [u8], + button_style: ButtonStyleSheet, +) -> Result { + let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; + let description: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?; + let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; + let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?; + + let obj = if allow_cancel { + LayoutObj::new( + IconDialog::new( + icon, + title, + Button::cancel_confirm( + Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()), + Button::with_text(button).styled(button_style), + 2, + ), + ) + .with_description(description), + )? + .into() + } else { + LayoutObj::new( + IconDialog::new( + icon, + title, + Button::with_text(button).styled(button_style).map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) + }), + ) + .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| { + new_show_modal(kwargs, theme::IMAGE_ERROR, theme::button_default()) + }; + 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: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let description: StrBuffer = - kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?; - - let buttons = Button::cancel_confirm( - Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()), - Button::with_text("CONTINUE").styled(theme::button_reset()), - 2, - ); - - let obj = LayoutObj::new( - IconDialog::new(theme::IMAGE_WARN, title, buttons).with_description(description), - )?; - Ok(obj.into()) + new_show_modal(kwargs, theme::IMAGE_WARN, 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 title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let description: StrBuffer = - kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?; - let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; + new_show_modal(kwargs, theme::IMAGE_SUCCESS, theme::button_confirm()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} - let buttons = component::Map::new( - Button::with_text(button).styled(theme::button_confirm()), - |msg| (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed), - ); - - let obj = LayoutObj::new( - IconDialog::new(theme::IMAGE_SUCCESS, title, buttons).with_description(description), - )?; - Ok(obj.into()) +extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + new_show_modal(kwargs, theme::IMAGE_INFO, theme::button_info()) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } @@ -667,10 +696,22 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Decrease or increase transaction fee.""" Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(), + /// def show_error( + /// *, + /// title: str, + /// button: str, + /// description: str = "", + /// allow_cancel: bool = False, + /// ) -> object: + /// """Error modal.""" + Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(), + /// def show_warning( /// *, /// title: str, + /// button: str, /// description: str = "", + /// allow_cancel: bool = False, /// ) -> object: /// """Warning modal.""" Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(), @@ -680,10 +721,21 @@ pub static mp_module_trezorui2: Module = obj_module! { /// title: str, /// button: str, /// description: str = "", + /// allow_cancel: bool = False, /// ) -> object: /// """Success modal.""" Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(), + /// def show_info( + /// *, + /// title: str, + /// button: str, + /// description: str = "", + /// allow_cancel: bool = False, + /// ) -> object: + /// """Info modal.""" + Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(), + /// def confirm_payment_request( /// *, /// description: str, diff --git a/core/embed/rust/src/ui/model_tt/res/info.toif b/core/embed/rust/src/ui/model_tt/res/info.toif new file mode 100644 index 0000000000..3456470bff Binary files /dev/null and b/core/embed/rust/src/ui/model_tt/res/info.toif differ diff --git a/core/embed/rust/src/ui/model_tt/theme.rs b/core/embed/rust/src/ui/model_tt/theme.rs index 957992e815..f0459320bd 100644 --- a/core/embed/rust/src/ui/model_tt/theme.rs +++ b/core/embed/rust/src/ui/model_tt/theme.rs @@ -34,6 +34,7 @@ pub const YELLOW_DARK: Color = Color::rgb(154, 115, 6); // FIXME pub const GREEN: Color = Color::rgb(57, 168, 20); // grass-green pub const GREEN_DARK: Color = Color::rgb(48, 147, 15); pub const BLUE: Color = Color::rgb(0, 86, 190); // blue +pub const BLUE_DARK: Color = Color::rgb(0, 68, 152); // FIXME pub const OFF_WHITE: Color = Color::rgb(222, 222, 222); // very light grey pub const GREY_LIGHT: Color = Color::rgb(168, 168, 168); // greyish pub const GREY_MEDIUM: Color = Color::rgb(100, 100, 100); @@ -60,6 +61,7 @@ pub const ICON_NEXT: &[u8] = include_res!("model_tt/res/next.toif"); pub const IMAGE_WARN: &[u8] = include_res!("model_tt/res/warn.toif"); pub const IMAGE_SUCCESS: &[u8] = include_res!("model_tt/res/success.toif"); pub const IMAGE_ERROR: &[u8] = include_res!("model_tt/res/error.toif"); +pub const IMAGE_INFO: &[u8] = include_res!("model_tt/res/info.toif"); // Scrollbar/PIN dots. pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif"); @@ -250,6 +252,38 @@ pub fn button_reset() -> ButtonStyleSheet { } } +pub fn button_info() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: FONT_BOLD, + text_color: FG, + button_color: BLUE, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: FONT_BOLD, + text_color: FG, + button_color: BLUE_DARK, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: FONT_BOLD, + text_color: GREY_LIGHT, + button_color: BLUE, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + pub fn button_pin() -> ButtonStyleSheet { ButtonStyleSheet { normal: &ButtonStyle { diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 1acc9b7d57..5cbc0f6654 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -146,11 +146,24 @@ def confirm_modify_fee( """Decrease or increase transaction fee.""" +# rust/src/ui/model_tt/layout.rs +def show_error( + *, + title: str, + button: str, + description: str = "", + allow_cancel: bool = False, +) -> object: + """Error modal.""" + + # rust/src/ui/model_tt/layout.rs def show_warning( *, title: str, + button: str, description: str = "", + allow_cancel: bool = False, ) -> object: """Warning modal.""" @@ -161,10 +174,22 @@ def show_success( title: str, button: str, description: str = "", + allow_cancel: bool = False, ) -> object: """Success modal.""" +# rust/src/ui/model_tt/layout.rs +def show_info( + *, + title: str, + button: str, + description: str = "", + allow_cancel: bool = False, +) -> object: + """Info modal.""" + + # rust/src/ui/model_tt/layout.rs def confirm_payment_request( *, diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index f32d7e2635..11dda96d5e 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -208,6 +208,7 @@ async def confirm_path_warning( trezorui2.show_warning( title="Unknown path", description=path, + button="CONTINUE", ) ), "path_warning", @@ -317,71 +318,58 @@ def show_pubkey( ) -async def _show_modal( - ctx: wire.GenericContext, - br_type: str, - br_code: ButtonRequestType, - header: str, - subheader: str | None, - content: str, - button_confirm: str | None, - button_cancel: str | None, - icon: str, - icon_color: int, - exc: ExceptionType = wire.ActionCancelled, -) -> None: - raise NotImplementedError - - async def show_error_and_raise( ctx: wire.GenericContext, br_type: str, content: str, header: str = "Error", subheader: str | None = None, - button: str = "Close", + button: str = "CLOSE", red: bool = False, exc: ExceptionType = wire.ActionCancelled, ) -> NoReturn: - await _show_modal( + await interact( ctx, - br_type=br_type, - br_code=ButtonRequestType.Other, - header=header, - subheader=subheader, - content=content, - button_confirm=None, - button_cancel=button, - icon=ui.ICON_WRONG, - icon_color=ui.RED if red else ui.ORANGE_ICON, - exc=exc, + _RustLayout( + trezorui2.show_error( + title=content.replace("\n", " "), + description=subheader or "", + button=button.upper(), + allow_cancel=False, + ) + ), + br_type, + ButtonRequestType.Other, ) raise exc -def show_warning( +async def show_warning( ctx: wire.GenericContext, br_type: str, content: str, header: str = "Warning", subheader: str | None = None, - button: str = "Try again", + button: str = "TRY AGAIN", br_code: ButtonRequestType = ButtonRequestType.Warning, icon: str = ui.ICON_WRONG, icon_color: int = ui.RED, -) -> Awaitable[None]: - return _show_modal( +) -> None: + result = await interact( ctx, - br_type=br_type, - br_code=br_code, - header=header, - subheader=subheader, - content=content, - button_confirm=button, - button_cancel=None, - icon=icon, - icon_color=icon_color, + _RustLayout( + trezorui2.show_warning( + title=content.replace("\n", " "), + description=subheader or "", + button=button.upper(), + allow_cancel=False, + ) + ), + br_type, + br_code, ) + if result is not trezorui2.CONFIRMED: + raise wire.ActionCancelled async def show_success( @@ -389,15 +377,16 @@ async def show_success( br_type: str, content: str, subheader: str | None = None, - button: str = "Continue", + button: str = "CONTINUE", ) -> None: result = await interact( ctx, _RustLayout( trezorui2.show_success( - title=content, + title=content.replace("\n", " "), description=subheader or "", button=button.upper(), + allow_cancel=False, ) ), br_type, @@ -666,11 +655,13 @@ async def confirm_metadata( layout = trezorui2.show_warning( title="Unusually high fee", description=param or "", + button="CONTINUE", ) elif br_type == "change_count_over_threshold": layout = trezorui2.show_warning( title="A lot of change-outputs", description=f"{param} outputs" if param is not None else "", + button="CONTINUE", ) else: if param is not None: