Skip to content

Commit

Permalink
feat(core/ui): T3T1 instruction screens between shares
Browse files Browse the repository at this point in the history
Changes the visual appearance of the screens between shares during
multi-share (shamir) recovery.

[no changelog]
  • Loading branch information
obrusvit committed Jul 28, 2024
1 parent 53799cd commit 63602b6
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 152 deletions.
4 changes: 4 additions & 0 deletions core/embed/rust/librust_qstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,13 @@ 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_create;
MP_QSTR_flow_confirm_reset_recover;
MP_QSTR_flow_confirm_set_new_pin;
MP_QSTR_flow_confirm_summary;
MP_QSTR_flow_continue_recovery;
MP_QSTR_flow_get_address;
MP_QSTR_flow_prompt_backup;
MP_QSTR_flow_request_number;
Expand Down Expand Up @@ -659,7 +661,9 @@ static void _librust_qstrs(void) {
MP_QSTR_storage_msg__verifying_pin;
MP_QSTR_storage_msg__wrong_pin;
MP_QSTR_subprompt;
MP_QSTR_subtext;
MP_QSTR_subtitle;
MP_QSTR_text;
MP_QSTR_text_confirm;
MP_QSTR_text_info;
MP_QSTR_text_mono;
Expand Down
134 changes: 134 additions & 0 deletions core/embed/rust/src/ui/model_mercury/flow/continue_recovery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use crate::{
error,
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString,
translations::TR,
ui::{
component::{
swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
ComponentExt, SwipeDirection,
},
flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};

use super::super::{
component::{Frame, FrameMsg, VerticalMenu, VerticalMenuChoiceMsg},
theme,
};

const RECOVERY_TYPE_DRY_RUN: u32 = 1;
const RECOVERY_TYPE_UNLOCK_REPEATED_BACKUP: u32 = 2;

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ContinueRecovery {
Main,
Menu,
}

impl FlowState for ContinueRecovery {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}

fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) {
(Self::Main, SwipeDirection::Left) => Self::Menu.swipe(direction),
(Self::Menu, SwipeDirection::Right) => Self::Main.swipe(direction),
(Self::Main, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
_ => self.do_nothing(),
}
}

fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) {
(Self::Main, FlowMsg::Info) => Self::Menu.transit(),
(Self::Menu, FlowMsg::Cancelled) => Self::Main.swipe_right(),
(Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
_ => self.do_nothing(),
}
}
}

#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_continue_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ContinueRecovery::new_obj) }
}

impl ContinueRecovery {
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let first_screen: bool = kwargs.get(Qstr::MP_QSTR_first_screen)?.try_into()?;
let recovery_type: u32 = 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<TString> = kwargs.get(Qstr::MP_QSTR_subtext)?.try_into_option()?; // #shares remaining

let (title, cancel_btn) = match recovery_type {
RECOVERY_TYPE_DRY_RUN => (
TR::recovery__title_dry_run.into(),
TR::recovery__cancel_dry_run.into(),
),
RECOVERY_TYPE_UNLOCK_REPEATED_BACKUP => (
TR::recovery__title_dry_run.into(),
TR::recovery__cancel_dry_run.into(),
),
_ => (
TR::recovery__title.into(),
TR::recovery__title_cancel_recovery.into(),
),
};

let mut pars = ParagraphVecShort::new();
if first_screen {
pars.add(Paragraph::new(
&theme::TEXT_MAIN_GREY_EXTRA_LIGHT,
TR::recovery__enter_each_word,
));
} else {
pars.add(Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, text));
if let Some(sub) = subtext {
pars.add(Paragraph::new(&theme::TEXT_SUB_GREY, sub));
}
}

let (footer_instruction, footer_description) = if first_screen {
(TR::instructions__swipe_up.into(), None)
} else {
(
TR::instructions__swipe_up.into(),
Some(TR::instructions__enter_next_share.into()),
)
};

let paragraphs = Paragraphs::new(pars);
let content_main = Frame::left_aligned(title, SwipeContent::new(paragraphs))
.with_subtitle(TR::words__instructions.into())
.with_menu_button()
.with_footer(footer_instruction, footer_description)
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));

let content_menu = Frame::left_aligned(
TString::empty(),
VerticalMenu::empty().danger(theme::ICON_CANCEL, cancel_btn),
)
.with_cancel_button()
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
.map(|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});

let res = SwipeFlow::new(&ContinueRecovery::Main)?
.with_page(&ContinueRecovery::Main, content_main)?
.with_page(&ContinueRecovery::Menu, content_menu)?;
Ok(LayoutObj::new(res)?.into())
}
}
2 changes: 2 additions & 0 deletions core/embed/rust/src/ui/model_mercury/flow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod confirm_reset_create;
pub mod confirm_reset_recover;
pub mod confirm_set_new_pin;
pub mod confirm_summary;
pub mod continue_recovery;
pub mod get_address;
pub mod prompt_backup;
pub mod request_number;
Expand All @@ -20,6 +21,7 @@ pub use confirm_reset_create::ConfirmResetCreate;
pub use confirm_reset_recover::ConfirmResetRecover;
pub use confirm_set_new_pin::SetNewPin;
pub use confirm_summary::new_confirm_summary;
pub use continue_recovery::new_continue_recovery;
pub use get_address::GetAddress;
pub use prompt_backup::PromptBackup;
pub use request_number::RequestNumber;
Expand Down
42 changes: 5 additions & 37 deletions core/embed/rust/src/ui/model_mercury/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,6 @@ impl ConfirmBlobParams {
}
}

const RECOVERY_TYPE_DRY_RUN: u32 = 1;
const RECOVERY_TYPE_UNLOCK_REPEATED_BACKUP: u32 = 2;

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()?;
Expand Down Expand Up @@ -1062,34 +1059,6 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}

extern "C" fn new_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: u32 = 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_NORMAL, description));

let notification = match recovery_type {
RECOVERY_TYPE_DRY_RUN => TR::recovery__title_dry_run.into(),
RECOVERY_TYPE_UNLOCK_REPEATED_BACKUP => TR::recovery__title_dry_run.into(),
_ => TR::recovery__title.into(),
};

let obj = LayoutObj::new(SwipeUpScreen::new(
Frame::left_aligned(notification, SwipeContent::new(paragraphs))
.with_cancel_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.with_subtitle(TR::words__instructions.into())
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
))?;
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 obj = LayoutObj::new(Frame::left_aligned(
Expand Down Expand Up @@ -1731,16 +1700,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// mark next to them."""
Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(),

/// def confirm_recovery(
/// def flow_continue_recovery(
/// *,
/// title: str,
/// description: str,
/// button: str,
/// first_screen: bool,
/// recovery_type: RecoveryType,
/// info_button: bool = False,
/// text: str,
/// subtext: str | None = None,
/// ) -> LayoutObj[UiResult]:
/// """Device recovery homescreen."""
Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(),
Qstr::MP_QSTR_flow_continue_recovery => obj_fn_kw!(0, flow::continue_recovery::new_continue_recovery).as_obj(),

/// def select_word_count(
/// *,
Expand Down
9 changes: 4 additions & 5 deletions core/mocks/generated/trezorui2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,12 @@ def show_checklist(


# rust/src/ui/model_mercury/layout.rs
def confirm_recovery(
def flow_continue_recovery(
*,
title: str,
description: str,
button: str,
first_screen: bool,
recovery_type: RecoveryType,
info_button: bool = False,
text: str,
subtext: str | None = None,
) -> LayoutObj[UiResult]:
"""Device recovery homescreen."""

Expand Down
43 changes: 4 additions & 39 deletions core/src/apps/management/recovery_device/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from trezor import TR
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import confirm_action
from trezor.ui.layouts.recovery import ( # noqa: F401
request_word_count,
show_group_share_success,
Expand All @@ -18,28 +17,6 @@
from trezor.enums import BackupType


async def _confirm_abort(dry_run: bool = False) -> None:
if dry_run:
await confirm_action(
"abort_recovery",
TR.recovery__title_cancel_dry_run,
TR.recovery__cancel_dry_run,
description=TR.recovery__wanna_cancel_dry_run,
verb=TR.buttons__cancel,
br_code=ButtonRequestType.ProtectCall,
)
else:
await confirm_action(
"abort_recovery",
TR.recovery__title_cancel_recovery,
TR.recovery__progress_will_be_lost,
TR.recovery__wanna_cancel_recovery,
verb=TR.buttons__cancel,
reverse=True,
br_code=ButtonRequestType.ProtectCall,
)


async def request_mnemonic(
word_count: int, backup_type: BackupType | None
) -> str | None:
Expand Down Expand Up @@ -149,24 +126,12 @@ async def homescreen_dialog(
show_info: bool = False,
) -> None:
import storage.recovery as storage_recovery
from trezor.enums import RecoveryType
from trezor.ui.layouts.recovery import continue_recovery
from trezor.wire import ActionCancelled

from .recover import RecoveryAborted

recovery_type = storage_recovery.get_type()

while True:
if await continue_recovery(
button_label, text, subtext, info_func, recovery_type, show_info
):
# go forward in the recovery process
break
# user has chosen to abort, confirm the choice
try:
await _confirm_abort(recovery_type != RecoveryType.NormalRecovery)
except ActionCancelled:
pass
else:
raise RecoveryAborted
if not await continue_recovery(
button_label, text, subtext, info_func, recovery_type, show_info
):
raise RecoveryAborted
Loading

0 comments on commit 63602b6

Please sign in to comment.