feat(core/ui): T3T1 confirm backup flow

obrusvit/ui-t3t1-reset-device-apple-hotfix
obrusvit 3 weeks ago
parent 0344871761
commit 6f2d471860

@ -149,6 +149,7 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_action;
MP_QSTR_confirm_address;
MP_QSTR_confirm_backup;
MP_QSTR_confirm_backup_written_down;
MP_QSTR_confirm_blob;
MP_QSTR_confirm_coinjoin;
MP_QSTR_confirm_emphasized;
@ -172,6 +173,7 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_value;
MP_QSTR_confirm_with_info;
MP_QSTR_count;
MP_QSTR_create_backup_flow;
MP_QSTR_data;
MP_QSTR_data_hash;
MP_QSTR_data_len;
@ -620,6 +622,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__error;
MP_QSTR_words__fee;
MP_QSTR_words__from;
MP_QSTR_words__important;
MP_QSTR_words__keep_it_safe;
MP_QSTR_words__know_what_your_doing;
MP_QSTR_words__my_trezor;

@ -1240,6 +1240,7 @@ pub enum TranslatedString {
instructions__swipe_up = 845, // "Swipe up"
instructions__tap_to_confirm = 846, // "Tap to confirm"
instructions__hold_to_confirm = 847, // "Hold to confirm"
words__important = 848, // "Important"
}
impl TranslatedString {
@ -2475,6 +2476,7 @@ impl TranslatedString {
Self::instructions__swipe_up => "Swipe up",
Self::instructions__tap_to_confirm => "Tap to confirm",
Self::instructions__hold_to_confirm => "Hold to confirm",
Self::words__important => "Important",
}
}
@ -3711,6 +3713,7 @@ impl TranslatedString {
Qstr::MP_QSTR_instructions__swipe_up => Some(Self::instructions__swipe_up),
Qstr::MP_QSTR_instructions__tap_to_confirm => Some(Self::instructions__tap_to_confirm),
Qstr::MP_QSTR_instructions__hold_to_confirm => Some(Self::instructions__hold_to_confirm),
Qstr::MP_QSTR_words__important => Some(Self::words__important),
_ => None,
}
}

@ -0,0 +1,146 @@
use crate::{
error,
micropython::qstr::Qstr,
strutil::TString,
translations::TR,
ui::{
component::{
text::paragraphs::{Paragraph, Paragraphs},
PageMsg,
},
flow::{
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeDirection, SwipeFlow,
SwipePage,
},
},
};
use heapless::Vec;
use super::super::{
component::{
CancelInfoConfirmMsg, Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg,
},
theme,
};
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum CreateBackup {
Intro,
Menu,
SkipBackupIntro,
SkipBackupConfirm,
}
impl FlowState for CreateBackup {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(CreateBackup::Intro, SwipeDirection::Left) => {
Decision::Goto(CreateBackup::Menu, direction)
}
(CreateBackup::SkipBackupIntro, SwipeDirection::Up) => {
Decision::Goto(CreateBackup::SkipBackupConfirm, direction)
}
(CreateBackup::SkipBackupConfirm, SwipeDirection::Down) => {
Decision::Goto(CreateBackup::SkipBackupIntro, direction)
}
(CreateBackup::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(CreateBackup::Intro, FlowMsg::Info) => {
Decision::Goto(CreateBackup::Menu, SwipeDirection::Left)
}
(CreateBackup::Menu, FlowMsg::Choice(0)) => {
Decision::Goto(CreateBackup::SkipBackupIntro, SwipeDirection::Left)
}
(CreateBackup::Menu, FlowMsg::Cancelled) => {
Decision::Goto(CreateBackup::Intro, SwipeDirection::Right)
}
(CreateBackup::SkipBackupIntro, FlowMsg::Cancelled) => {
Decision::Goto(CreateBackup::Menu, SwipeDirection::Right)
}
(CreateBackup::SkipBackupConfirm, FlowMsg::Cancelled) => {
Decision::Goto(CreateBackup::SkipBackupIntro, SwipeDirection::Right)
}
(CreateBackup::SkipBackupConfirm, FlowMsg::Confirmed) => {
Decision::Return(FlowMsg::Cancelled)
}
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::layout::obj::LayoutObj,
};
pub extern "C" fn new_create_backup(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, CreateBackup::new) }
}
impl CreateBackup {
fn new(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = TR::backup__title_backup_wallet.into();
let par_array: [Paragraph<'static>; 1] = [Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
TString::from_str("Your wallet backup contains X words in a specific order."),
)];
let paragraphs = Paragraphs::new(par_array);
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None);
let content_menu = Frame::left_aligned(
"".into(),
VerticalMenu::context_menu(unwrap!(Vec::from_slice(&[(
"Skip backup", // FIXME: use TString
theme::ICON_CANCEL
)]))),
)
.with_cancel_button();
let par_array_skip_intro: [Paragraph<'static>; 2] = [
Paragraph::new(&theme::TEXT_WARNING, TString::from_str("Not recommended!")),
Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
TString::from_str("Create a backup to avoid losing access to your funds"),
),
];
let paragraphs_skip_intro = Paragraphs::new(par_array_skip_intro);
let content_skip_intro =
Frame::left_aligned(TR::backup__title_skip.into(), SwipePage::vertical(paragraphs_skip_intro))
.with_cancel_button()
.with_footer(TR::instructions__swipe_up.into(), Some(TR::words__continue_anyway.into()));
let content_skip_confirm = Frame::left_aligned(
TR::backup__title_skip.into(),
PromptScreen::new_tap_to_cancel(),
)
.with_footer(TR::instructions__tap_to_confirm.into(), None);
let store = flow_store()
.add(content_intro, |msg| {
matches!(msg, FrameMsg::Button(CancelInfoConfirmMsg::Info)).then_some(FlowMsg::Info)
})?
.add(content_menu, |msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
FrameMsg::Button(_) => None,
})?
.add(content_skip_intro, |msg| match msg {
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
_ => None,
})?
.add(content_skip_confirm, |msg| match msg {
FrameMsg::Content(PageMsg::Confirmed) => Some(FlowMsg::Confirmed),
FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled),
_ => None,
})?;
let res = SwipeFlow::new(CreateBackup::Intro, store)?;
Ok(LayoutObj::new(res)?.into())
}
}

@ -1,5 +1,7 @@
pub mod get_address;
pub mod confirm_reset_device;
pub mod create_backup;
pub use get_address::GetAddress;
pub use confirm_reset_device::ConfirmResetDevice;
pub use create_backup::CreateBackup;

@ -1020,42 +1020,50 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
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())
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?;
let content = SwipeUpScreen::new(Paragraphs::new([Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
value,
)]));
let obj = LayoutObj::new(
Frame::left_aligned(title, content)
.with_warning_button()
.button_styled(theme::button_warning_low())
.with_footer(TR::instructions__swipe_up.into(), None),
)?;
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 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())
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let content = StatusScreen::new_success();
let obj = LayoutObj::new(
Frame::left_aligned(title, content)
.with_footer(TR::instructions__swipe_up.into(), None),
)?;
Ok(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 icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_INFO,
theme::INFO_COLOR,
theme::FG,
theme::BG,
);
new_show_modal(kwargs, icon, theme::button_info())
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let content = SwipeUpScreen::new(Paragraphs::new([Paragraph::new(
&theme::TEXT_MAIN_GREY_LIGHT,
description,
)]));
let obj = LayoutObj::new(
Frame::left_aligned(title, content)
.with_footer(TR::instructions__swipe_up.into(), None),
)?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
@ -1327,6 +1335,22 @@ extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_backup_written_down(
n_args: usize,
args: *const Obj,
kwargs: *mut Map,
) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let content = PromptScreen::new_hold_to_confirm();
let frame_with_hold_to_confirm =
Frame::left_aligned("I wrote down all words in order.".into(), content)
.with_footer(TR::instructions__hold_to_confirm.into(), None);
let obj = LayoutObj::new(frame_with_hold_to_confirm)?;
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()?;
@ -2030,6 +2054,11 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// information, 3) Cancel transaction"""
Qstr::MP_QSTR_show_tx_context_menu => obj_fn_kw!(0, new_show_tx_context_menu).as_obj(),
// TODO: This is just POC
/// def create_backup_flow() -> LayoutObj[UiResult]
/// """Start create backup or skip flow."""
Qstr::MP_QSTR_create_backup_flow => obj_fn_kw!(0, flow::create_backup::new_create_backup).as_obj(),
/// def show_share_words(
/// *,
/// title: str,
@ -2038,6 +2067,11 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Show mnemonic for backup."""
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
// TODO: This is just POC
/// def confirm_backup_written_down() -> LayoutObj[UiResult]
/// """Confirm with the user that backup words are written down."""
Qstr::MP_QSTR_confirm_backup_written_down => obj_fn_kw!(0, new_confirm_backup_written_down).as_obj(),
/// def request_number(
/// *,
/// title: str,

@ -292,6 +292,41 @@ pub const fn button_warning_high() -> ButtonStyleSheet {
}
}
pub const fn button_warning_low() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: GREY_LIGHT,
button_color: BG,
icon_color: GREEN_LIME,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
active: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: GREY_LIGHT,
button_color: BG,
icon_color: GREEN_LIME,
background_color: BG,
border_color: FG,
border_radius: RADIUS,
border_width: 0,
},
disabled: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: GREY_LIGHT,
button_color: BG,
icon_color: GREEN_LIME,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
}
}
// TODO: delete
pub const fn button_confirm() -> ButtonStyleSheet {
ButtonStyleSheet {

@ -395,6 +395,11 @@ def show_tx_context_menu() -> LayoutObj[int]:
information, 3) Cancel transaction"""
# rust/src/ui/model_mercury/layout.rs
def create_backup_flow() -> LayoutObj[UiResult]
"""Start create backup or skip flow."""
# rust/src/ui/model_mercury/layout.rs
def show_share_words(
*,
@ -404,6 +409,11 @@ def show_share_words(
"""Show mnemonic for backup."""
# rust/src/ui/model_mercury/layout.rs
def confirm_backup_written_down() -> LayoutObj[UiResult]
"""Confirm with the user that backup words are written down."""
# rust/src/ui/model_mercury/layout.rs
def request_number(
*,

@ -826,6 +826,7 @@ class TR:
words__error: str = "Error"
words__fee: str = "Fee"
words__from: str = "from"
words__important: str = "Important"
words__keep_it_safe: str = "Keep it safe!"
words__know_what_your_doing: str = "Continue only if you know what you are doing!"
words__my_trezor: str = "My Trezor"

@ -340,37 +340,21 @@ async def confirm_reset_device(title: str, recovery: bool = False) -> None:
)
# TODO cleanup @ redesign
async def prompt_backup() -> bool:
result = await interact(
RustLayout(
trezorui2.confirm_action(
title=TR.words__title_success,
action=TR.backup__new_wallet_successfully_created,
description=TR.backup__it_should_be_backed_up,
verb=TR.buttons__back_up,
verb_cancel=TR.buttons__skip,
)
),
# result = await interact(RustLayout(trezorui2.))
await interact(
RustLayout(trezorui2.show_success(title=TR.backup__new_wallet_created)),
"backup_device",
ButtonRequestType.ResetDevice,
)
if result is CONFIRMED:
return True
result = await interact(
RustLayout(
trezorui2.confirm_action(
title=TR.words__warning.upper(),
action=TR.backup__want_to_skip,
description=TR.backup__can_back_up_anytime,
verb=TR.buttons__back_up,
verb_cancel=TR.buttons__skip,
)
),
# TODO: this is just POC
RustLayout(trezorui2.create_backup_flow()),
"backup_device",
ButtonRequestType.ResetDevice,
)
return result is CONFIRMED
@ -586,8 +570,8 @@ async def show_success(
interact(
RustLayout(
trezorui2.show_success(
title=content,
description=subheader or "",
title=subheader or "",
description="",
button=button.upper(),
allow_cancel=False,
)

@ -32,9 +32,16 @@ async def show_share_words(
group_index + 1, share_index + 1
)
await RustLayout(
trezorui2.show_share_words(
title=title,
pages=share_words,
),
)
result = await interact(
RustLayout(
trezorui2.show_share_words(
trezorui2.confirm_backup_written_down(
title=title,
pages=share_words,
),
@ -42,6 +49,13 @@ async def show_share_words(
"backup_words",
ButtonRequestType.ResetDevice,
)
result = await RustLayout(
trezorui2.show_info(
title="Check wallet backup",
description="Let's do a quick check of your backup.",
)
)
if result != CONFIRMED:
raise ActionCancelled
@ -296,15 +310,22 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
async def show_warning_backup(slip39: bool) -> None:
result = await interact(
RustLayout(
trezorui2.show_info(
title=TR.reset__never_make_digital_copy,
button=TR.buttons__ok_i_understand,
trezorui2.show_warning(
title=TR.words__important,
value=TR.reset__never_make_digital_copy,
button="",
allow_cancel=False,
)
),
"backup_warning",
ButtonRequestType.ResetDevice,
)
result = await RustLayout(
trezorui2.show_info(
title="Wallet backup",
description="Write the following words in order on your wallet backup card.",
)
)
if result != CONFIRMED:
raise ActionCancelled
@ -332,8 +353,9 @@ async def show_reset_warning(
RustLayout(
trezorui2.show_warning(
title=subheader or "",
description=content,
button=button.upper(),
description="",
value=content,
button="",
allow_cancel=False,
)
),

@ -828,6 +828,7 @@
"words__error": "Error",
"words__fee": "Fee",
"words__from": "from",
"words__important": "Important",
"words__keep_it_safe": "Keep it safe!",
"words__know_what_your_doing": "Continue only if you know what you are doing!",
"words__my_trezor": "My Trezor",

@ -847,4 +847,5 @@
"845": "instructions__swipe_up",
"846": "instructions__tap_to_confirm",
"847": "instructions__hold_to_confirm",
"848": "words__important"
}

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "5a83288c2b984cb98dfed78216ab2b24a9f01bc6d49b1e9484d7140241d7be0c",
"datetime": "2024-04-18T10:27:35.547426",
"commit": "c09789f9dd2390b79e25be884e96ede021ab8151"
"merkle_root": "4a9791a69489039202914aee7c3ee810e205d13cfa4fa3e072420f380f577d0f",
"datetime": "2024-04-21T12:56:00.193900",
"commit": "224f031725c91f1a06715a038555916374a61706"
},
"history": [
{

Loading…
Cancel
Save