mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-03 08:46:05 +00:00
feat(core/ui): show success screens between shares
This commit also adds minor improvements to reset flow. [no changelog]
This commit is contained in:
parent
72bdea6754
commit
5bd6996ae4
@ -97,9 +97,11 @@ impl ConfirmResetCreate {
|
||||
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
|
||||
.one_button_request(ButtonRequestCode::ResetDevice.with_type("setup_device"));
|
||||
|
||||
// FIXME: TR::reset__cancel_create_wallet should be used but Button text on
|
||||
// multiple lines not supported yet
|
||||
let content_menu = Frame::left_aligned(
|
||||
"".into(),
|
||||
VerticalMenu::empty().danger(theme::ICON_CANCEL, "Cancel".into()), // TODO: use TR
|
||||
VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()),
|
||||
)
|
||||
.with_cancel_button()
|
||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||
|
@ -112,13 +112,12 @@ impl ShowShareWords {
|
||||
.map(|_| Some(FlowMsg::Confirmed));
|
||||
|
||||
let content_check_backup_intro = Frame::left_aligned(
|
||||
TR::reset__check_backup_title.into(),
|
||||
TR::reset__check_wallet_backup_title.into(),
|
||||
SwipeContent::new(Paragraphs::new(Paragraph::new(
|
||||
&theme::TEXT_MAIN_GREY_LIGHT,
|
||||
TR::reset__check_backup_instructions,
|
||||
))),
|
||||
)
|
||||
.with_subtitle(TR::words__instructions.into())
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||
.map(|_| Some(FlowMsg::Confirmed));
|
||||
|
@ -788,10 +788,15 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
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<TString> = 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();
|
||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
||||
Frame::left_aligned(title, SwipeContent::new(content).with_normal_attach(None))
|
||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
||||
.with_footer(TR::instructions__swipe_up.into(), description)
|
||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
@ -1572,7 +1577,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Success modal. No buttons shown when `button` is empty string."""
|
||||
/// """Success screen. Description is used in the footer."""
|
||||
Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(),
|
||||
|
||||
/// def show_info(
|
||||
|
@ -278,7 +278,7 @@ def show_success(
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Success modal. No buttons shown when `button` is empty string."""
|
||||
"""Success screen. Description is used in the footer."""
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
|
@ -69,17 +69,20 @@ async def _share_words_confirmed(
|
||||
|
||||
Return true if the words are confirmed successfully.
|
||||
"""
|
||||
# TODO: confirm_action("Select the words bla bla")
|
||||
from trezor.ui.layouts.reset import (
|
||||
show_share_confirmation_success,
|
||||
show_share_confirmation_failure,
|
||||
)
|
||||
|
||||
if await _do_confirm_share_words(share_index, share_words, group_index):
|
||||
await _show_confirmation_success(
|
||||
await show_share_confirmation_success(
|
||||
share_index,
|
||||
num_of_shares,
|
||||
group_index,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
await _show_confirmation_failure()
|
||||
await show_share_confirmation_failure()
|
||||
|
||||
return False
|
||||
|
||||
@ -105,52 +108,6 @@ async def _do_confirm_share_words(
|
||||
return True
|
||||
|
||||
|
||||
async def _show_confirmation_success(
|
||||
share_index: int | None = None,
|
||||
num_of_shares: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> None:
|
||||
if share_index is None or num_of_shares is None:
|
||||
# it is a BIP39 or a 1-of-1 SLIP39 backup
|
||||
subheader = TR.reset__finished_verifying_wallet_backup
|
||||
text = ""
|
||||
|
||||
elif share_index == num_of_shares - 1:
|
||||
if group_index is None:
|
||||
subheader = TR.reset__finished_verifying_shares
|
||||
else:
|
||||
subheader = TR.reset__finished_verifying_group_template.format(
|
||||
group_index + 1
|
||||
)
|
||||
text = ""
|
||||
|
||||
else:
|
||||
if group_index is None:
|
||||
subheader = TR.reset__share_checked_successfully_template.format(
|
||||
share_index + 1
|
||||
)
|
||||
text = TR.reset__continue_with_share_template.format(share_index + 2)
|
||||
else:
|
||||
subheader = TR.reset__group_share_checked_successfully_template.format(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
text = TR.reset__continue_with_next_share
|
||||
|
||||
return await show_success("success_recovery", text, subheader)
|
||||
|
||||
|
||||
async def _show_confirmation_failure() -> None:
|
||||
from trezor.ui.layouts.reset import show_reset_warning
|
||||
|
||||
await show_reset_warning(
|
||||
"warning_backup_check",
|
||||
TR.words__please_check_again,
|
||||
TR.reset__wrong_word_selected,
|
||||
TR.buttons__check_again,
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
|
||||
async def show_backup_warning() -> None:
|
||||
from trezor.ui.layouts.reset import show_warning_backup
|
||||
|
||||
|
@ -369,7 +369,11 @@ async def confirm_reset_device(_title: str, recovery: bool = False) -> None:
|
||||
async def prompt_backup() -> bool:
|
||||
# TODO: should we move this to `flow_prompt_backup`?
|
||||
await interact(
|
||||
RustLayout(trezorui2.show_success(title=TR.backup__new_wallet_created)),
|
||||
RustLayout(
|
||||
trezorui2.show_success(
|
||||
title=TR.backup__new_wallet_created, description=None
|
||||
)
|
||||
),
|
||||
"backup_device",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
@ -562,7 +566,7 @@ async def show_success(
|
||||
RustLayout(
|
||||
trezorui2.show_success(
|
||||
title=content,
|
||||
description="",
|
||||
description=subheader,
|
||||
)
|
||||
),
|
||||
br_type,
|
||||
|
@ -123,7 +123,7 @@ async def continue_recovery(
|
||||
|
||||
if show_info:
|
||||
# Show this just one-time
|
||||
description = TR.recovery__only_first_n_letters
|
||||
description = TR.recovery__enter_each_word
|
||||
else:
|
||||
description = subtext or ""
|
||||
|
||||
|
@ -6,7 +6,7 @@ from trezor.enums import ButtonRequestType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout, raise_if_not_confirmed
|
||||
from . import RustLayout, raise_if_not_confirmed, show_success
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
@ -57,11 +57,11 @@ async def select_word(
|
||||
group_index: int | None = None,
|
||||
) -> str:
|
||||
if share_index is None:
|
||||
description: str = TR.reset__check_wallet_backup_title
|
||||
title: str = TR.reset__check_wallet_backup_title
|
||||
elif group_index is None:
|
||||
description: str = TR.reset__check_share_title_template.format(share_index + 1)
|
||||
title: str = TR.reset__check_share_title_template.format(share_index + 1)
|
||||
else:
|
||||
description: str = TR.reset__check_group_share_title_template.format(
|
||||
title: str = TR.reset__check_group_share_title_template.format(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
|
||||
@ -74,10 +74,10 @@ async def select_word(
|
||||
|
||||
result = await RustLayout(
|
||||
trezorui2.select_word(
|
||||
title=TR.reset__select_word_x_of_y_template.format(
|
||||
title=title,
|
||||
description=TR.reset__select_word_x_of_y_template.format(
|
||||
checked_index + 1, count
|
||||
),
|
||||
description=description,
|
||||
words=(words[0], words[1], words[2]),
|
||||
)
|
||||
)
|
||||
@ -316,12 +316,9 @@ async def show_warning_backup() -> None:
|
||||
|
||||
|
||||
async def show_success_backup() -> None:
|
||||
from . import show_success
|
||||
|
||||
await show_success(
|
||||
"success_backup",
|
||||
TR.reset__use_your_backup,
|
||||
TR.reset__your_backup_is_done,
|
||||
TR.backup__title_backup_completed,
|
||||
)
|
||||
|
||||
|
||||
@ -332,15 +329,14 @@ async def show_reset_warning(
|
||||
button: str | None = None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
||||
) -> None:
|
||||
button = button or TR.buttons__try_again # def_arg
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.show_warning(
|
||||
title=content or TR.words__warning,
|
||||
description="",
|
||||
value=subheader or "",
|
||||
button=button,
|
||||
title=subheader or "",
|
||||
description=content,
|
||||
value="",
|
||||
button="",
|
||||
allow_cancel=False,
|
||||
)
|
||||
),
|
||||
@ -348,3 +344,49 @@ async def show_reset_warning(
|
||||
br_code,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def show_share_confirmation_success(
|
||||
share_index: int | None = None,
|
||||
num_of_shares: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> None:
|
||||
if share_index is None or num_of_shares is None:
|
||||
# it is a BIP39 or a 1-of-1 SLIP39 backup
|
||||
# mercury UI shows only final wallet backup confirmation screen later
|
||||
return
|
||||
|
||||
# TODO: super-shamir copy not done
|
||||
if share_index == num_of_shares - 1:
|
||||
title = TR.reset__share_completed_template.format(share_index + 1)
|
||||
if group_index is None:
|
||||
footer_description = ""
|
||||
else:
|
||||
footer_description = TR.reset__finished_verifying_group_template.format(
|
||||
group_index + 1
|
||||
)
|
||||
else:
|
||||
if group_index is None:
|
||||
title = TR.reset__share_completed_template.format(share_index + 1)
|
||||
footer_description = (
|
||||
TR.instructions__shares_continue_with_x_template.format(share_index + 2)
|
||||
)
|
||||
else:
|
||||
title = TR.reset__continue_with_next_share
|
||||
footer_description = (
|
||||
TR.reset__group_share_checked_successfully_template.format(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
)
|
||||
|
||||
return await show_success("success_recovery", title, subheader=footer_description)
|
||||
|
||||
|
||||
async def show_share_confirmation_failure() -> None:
|
||||
await show_reset_warning(
|
||||
"warning_backup_check",
|
||||
TR.words__try_again,
|
||||
TR.reset__incorrect_word_selected,
|
||||
"",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ from trezor.enums import ButtonRequestType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout, confirm_action, show_warning
|
||||
from . import RustLayout, confirm_action, show_warning, show_success
|
||||
|
||||
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
||||
|
||||
@ -291,3 +291,47 @@ async def show_reset_warning(
|
||||
button,
|
||||
br_code=br_code,
|
||||
)
|
||||
|
||||
|
||||
async def show_share_confirmation_success(
|
||||
share_index: int | None = None,
|
||||
num_of_shares: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> None:
|
||||
if share_index is None or num_of_shares is None:
|
||||
# it is a BIP39 or a 1-of-1 SLIP39 backup
|
||||
subheader = TR.reset__finished_verifying_wallet_backup
|
||||
text = ""
|
||||
|
||||
elif share_index == num_of_shares - 1:
|
||||
if group_index is None:
|
||||
subheader = TR.reset__finished_verifying_shares
|
||||
else:
|
||||
subheader = TR.reset__finished_verifying_group_template.format(
|
||||
group_index + 1
|
||||
)
|
||||
text = ""
|
||||
|
||||
else:
|
||||
if group_index is None:
|
||||
subheader = TR.reset__share_checked_successfully_template.format(
|
||||
share_index + 1
|
||||
)
|
||||
text = TR.reset__continue_with_share_template.format(share_index + 2)
|
||||
else:
|
||||
subheader = TR.reset__group_share_checked_successfully_template.format(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
text = TR.reset__continue_with_next_share
|
||||
|
||||
return await show_success("success_recovery", text, subheader)
|
||||
|
||||
|
||||
async def show_share_confirmation_failure() -> None:
|
||||
await show_reset_warning(
|
||||
"warning_backup_check",
|
||||
TR.words__please_check_again,
|
||||
TR.reset__wrong_word_selected,
|
||||
TR.buttons__check_again,
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ from trezor.enums import ButtonRequestType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout, raise_if_not_confirmed
|
||||
from . import RustLayout, raise_if_not_confirmed, show_success
|
||||
|
||||
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
||||
|
||||
@ -326,8 +326,6 @@ async def show_warning_backup() -> None:
|
||||
|
||||
|
||||
async def show_success_backup() -> None:
|
||||
from . import show_success
|
||||
|
||||
await show_success(
|
||||
"success_backup",
|
||||
TR.reset__use_your_backup,
|
||||
@ -357,3 +355,47 @@ async def show_reset_warning(
|
||||
br_code,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def show_share_confirmation_success(
|
||||
share_index: int | None = None,
|
||||
num_of_shares: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> None:
|
||||
if share_index is None or num_of_shares is None:
|
||||
# it is a BIP39 or a 1-of-1 SLIP39 backup
|
||||
subheader = TR.reset__finished_verifying_wallet_backup
|
||||
text = ""
|
||||
|
||||
elif share_index == num_of_shares - 1:
|
||||
if group_index is None:
|
||||
subheader = TR.reset__finished_verifying_shares
|
||||
else:
|
||||
subheader = TR.reset__finished_verifying_group_template.format(
|
||||
group_index + 1
|
||||
)
|
||||
text = ""
|
||||
|
||||
else:
|
||||
if group_index is None:
|
||||
subheader = TR.reset__share_checked_successfully_template.format(
|
||||
share_index + 1
|
||||
)
|
||||
text = TR.reset__continue_with_share_template.format(share_index + 2)
|
||||
else:
|
||||
subheader = TR.reset__group_share_checked_successfully_template.format(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
text = TR.reset__continue_with_next_share
|
||||
|
||||
return await show_success("success_recovery", text, subheader)
|
||||
|
||||
|
||||
async def show_share_confirmation_failure() -> None:
|
||||
await show_reset_warning(
|
||||
"warning_backup_check",
|
||||
TR.words__please_check_again,
|
||||
TR.reset__wrong_word_selected,
|
||||
TR.buttons__check_again,
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"current": {
|
||||
"merkle_root": "57561824d2bed19054d5570b0181b5907c7ae5546e4d6c2e589a727898a69237",
|
||||
"datetime": "2024-05-31T09:09:13.124759",
|
||||
"commit": "6000eaf8280116d0cc947aa8d2aceb568dca56f8"
|
||||
"merkle_root": "7a115e582a5f5f09b1850946030762360f55e516a60cf960dffc9ba174c2e4d2",
|
||||
"datetime": "2024-06-02T11:07:12.183601",
|
||||
"commit": "66496206ccbe9583203fbfebf8c9222e8c6379b8"
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
|
@ -171,27 +171,32 @@ class LayoutContent(UnstructuredJSONReader):
|
||||
|
||||
return visible
|
||||
|
||||
def _get_str_or_dict_text(self, key: str) -> str:
|
||||
value = self.find_unique_value_by_key(key, "")
|
||||
if isinstance(value, dict):
|
||||
return value["text"]
|
||||
return value
|
||||
|
||||
def title(self) -> str:
|
||||
"""Getting text that is displayed as a title."""
|
||||
"""Getting text that is displayed as a title and potentially subtitle."""
|
||||
# There could be possibly subtitle as well
|
||||
title_parts: List[str] = []
|
||||
|
||||
def _get_str_or_dict_text(key: str) -> str:
|
||||
value = self.find_unique_value_by_key(key, "")
|
||||
if isinstance(value, dict):
|
||||
return value["text"]
|
||||
return value
|
||||
|
||||
title = _get_str_or_dict_text("title")
|
||||
title = self._get_str_or_dict_text("title")
|
||||
if title:
|
||||
title_parts.append(title)
|
||||
|
||||
subtitle = _get_str_or_dict_text("subtitle")
|
||||
subtitle = self.subtitle()
|
||||
if subtitle:
|
||||
title_parts.append(subtitle)
|
||||
|
||||
return "\n".join(title_parts)
|
||||
|
||||
def subtitle(self) -> str:
|
||||
"""Getting text that is displayed as a subtitle."""
|
||||
subtitle = self._get_str_or_dict_text("subtitle")
|
||||
return subtitle
|
||||
|
||||
def text_content(self) -> str:
|
||||
"""What is on the screen, in one long string, so content can be
|
||||
asserted regardless of newlines. Also getting rid of possible ellipsis.
|
||||
|
@ -277,13 +277,21 @@ def check_share(
|
||||
Given the mnemonic word list, proceed with the backup check:
|
||||
three rounds of `Select word X of Y` choices.
|
||||
"""
|
||||
re_num_of_word = r"\d+"
|
||||
for _ in range(3):
|
||||
if debug.model is models.T2B1:
|
||||
# T2B1 has the instruction in the title
|
||||
word_pos_match = re.search(r"\d+", debug.wait_layout().title())
|
||||
if debug.model is models.T2T1:
|
||||
# T2T1 has position as the first number in the text
|
||||
word_pos_match = re.search(
|
||||
re_num_of_word, debug.wait_layout().text_content()
|
||||
)
|
||||
elif debug.model is models.T2B1:
|
||||
# other models have the instruction in the title/subtitle
|
||||
word_pos_match = re.search(re_num_of_word, debug.wait_layout().title())
|
||||
elif debug.model is models.T3T1:
|
||||
word_pos_match = re.search(re_num_of_word, debug.wait_layout().subtitle())
|
||||
else:
|
||||
# Other models has position as the first number in the text
|
||||
word_pos_match = re.search(r"\d+", debug.wait_layout().text_content())
|
||||
word_pos_match = None
|
||||
|
||||
assert word_pos_match is not None
|
||||
word_pos = int(word_pos_match.group(0))
|
||||
|
||||
|
@ -1255,9 +1255,11 @@ def get_mnemonic_and_confirm_success(
|
||||
# mnemonic phrases
|
||||
mnemonic = yield from read_and_confirm_mnemonic(debug)
|
||||
|
||||
br = yield # confirm recovery seed check
|
||||
assert br.code == B.Success
|
||||
debug.press_yes()
|
||||
is_slip39 = len(mnemonic.split()) in (20, 33)
|
||||
if debug.model in (models.T2T1, models.T2B1) or is_slip39:
|
||||
br = yield # confirm recovery share check
|
||||
assert br.code == B.Success
|
||||
debug.press_yes()
|
||||
|
||||
br = yield # confirm success
|
||||
assert br.code == B.Success
|
||||
|
@ -93,7 +93,7 @@ class RecoveryFlow:
|
||||
def enter_your_backup(self) -> BRGeneratorType:
|
||||
yield
|
||||
if self.debug.model is models.T3T1:
|
||||
TR.assert_in(self._text_content(), "recovery__only_first_n_letters")
|
||||
TR.assert_in(self._text_content(), "recovery__enter_each_word")
|
||||
else:
|
||||
TR.assert_in(self._text_content(), "recovery__enter_backup")
|
||||
is_dry_run = any(
|
||||
@ -110,7 +110,7 @@ class RecoveryFlow:
|
||||
yield
|
||||
TR.assert_in_multiple(
|
||||
self._text_content(),
|
||||
["recovery__enter_any_share", "recovery__only_first_n_letters"],
|
||||
["recovery__enter_any_share", "recovery__enter_each_word"],
|
||||
)
|
||||
is_dry_run = any(
|
||||
title in self.debug.wait_layout().title().lower()
|
||||
@ -127,7 +127,7 @@ class RecoveryFlow:
|
||||
if self.client.model is models.T2B1:
|
||||
TR.assert_in(self._text_content(), "recovery__num_of_words")
|
||||
elif self.client.model is models.T3T1:
|
||||
TR.assert_in(self._text_content(), "recovery__only_first_n_letters")
|
||||
TR.assert_in(self._text_content(), "recovery__enter_each_word")
|
||||
else:
|
||||
TR.assert_in(self._text_content(), "recovery__enter_any_share")
|
||||
self.debug.press_no()
|
||||
|
Loading…
Reference in New Issue
Block a user