feat(core/ui): T3T1 reset wallet screen

The first screen in wallet reset (onboarding or recovery) implemented as
a simple SwipeFlow to handle moving between Menu and back.
obrusvit/ui-t3t1-reset-device-apple-hotfix
obrusvit 4 weeks ago
parent 8843b25734
commit b30ad5c824

@ -231,6 +231,7 @@ static void _librust_qstrs(void) {
MP_QSTR_inputs__return;
MP_QSTR_inputs__show;
MP_QSTR_inputs__space;
MP_QSTR_instructions__swipe_up;
MP_QSTR_is_type_of;
MP_QSTR_items;
MP_QSTR_joint__title;

@ -1237,6 +1237,7 @@ pub enum TranslatedString {
storage_msg__starting = 842, // "STARTING UP"
storage_msg__verifying_pin = 843, // "VERIFYING PIN"
storage_msg__wrong_pin = 844, // "WRONG PIN"
instructions__swipe_up = 845, // "Swipe up"
}
impl TranslatedString {
@ -2469,6 +2470,7 @@ impl TranslatedString {
Self::storage_msg__starting => "STARTING UP",
Self::storage_msg__verifying_pin => "VERIFYING PIN",
Self::storage_msg__wrong_pin => "WRONG PIN",
Self::instructions__swipe_up => "Swipe up",
}
}
@ -3702,6 +3704,7 @@ impl TranslatedString {
Qstr::MP_QSTR_storage_msg__starting => Some(Self::storage_msg__starting),
Qstr::MP_QSTR_storage_msg__verifying_pin => Some(Self::storage_msg__verifying_pin),
Qstr::MP_QSTR_storage_msg__wrong_pin => Some(Self::storage_msg__wrong_pin),
Qstr::MP_QSTR_instructions__swipe_up => Some(Self::instructions__swipe_up),
_ => None,
}
}

@ -14,6 +14,7 @@ use crate::{
/// height must be 18px (only instruction) or 37px (both description and
/// instruction). The content and style of both description and instruction is
/// configurable separatedly.
#[derive(Clone)]
pub struct Footer<'a> {
area: Rect,
text_instruction: TString<'a>,
@ -33,8 +34,8 @@ impl<'a> Footer<'a> {
area: Rect::zero(),
text_instruction: instruction.into(),
text_description: None,
style_instruction: &theme::TEXT_SUB,
style_description: &theme::TEXT_SUB,
style_instruction: &theme::TEXT_SUB_GREY,
style_description: &theme::TEXT_SUB_GREY_LIGHT,
}
}
@ -64,6 +65,14 @@ impl<'a> Footer<'a> {
self.style_description = style;
ctx.request_paint();
}
pub fn get_necessary_height(&self) -> i16 {
if self.text_description.is_some() {
Footer::HEIGHT_DEFAULT
} else {
Footer::HEIGHT_SIMPLE
}
}
}
impl<'a> Component for Footer<'a> {
@ -130,3 +139,14 @@ impl<'a> Component for Footer<'a> {
sink(self.area);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Footer<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Footer");
if let Some(description) = self.text_description {
t.string("description", description);
}
t.string("instruction", self.text_instruction);
}
}

@ -66,7 +66,7 @@ where
}
pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self {
let style = theme::TEXT_SUB;
let style = theme::TEXT_SUB_GREY_LIGHT;
self.title = Child::new(self.title.into_inner().top_aligned());
self.subtitle = Some(Child::new(Label::new(
subtitle,

@ -105,10 +105,10 @@ impl<'a> Component for ShareWords<'a> {
// the ordinal number of the current word
let ordinal_val = self.page_index as u8 + 1;
let ordinal_pos = self.area_word.top_left()
+ Offset::y(theme::TEXT_SUB.text_font.visible_text_height("1"));
+ Offset::y(theme::TEXT_SUB_GREY_LIGHT.text_font.visible_text_height("1"));
let ordinal = build_string!(3, inttostr!(ordinal_val), ".");
shape::Text::new(ordinal_pos, &ordinal)
.with_font(theme::TEXT_SUB.text_font)
.with_font(theme::TEXT_SUB_GREY_LIGHT.text_font)
.with_fg(theme::GREY)
.render(target);

@ -59,13 +59,13 @@ impl VerticalMenu {
Self::new(buttons_vec)
}
pub fn context_menu(options: [(&'static str, Icon); 3]) -> Self {
// TODO: this is just POC
pub fn context_menu(options: Vec<(&'static str, Icon), N_ITEMS>) -> Self {
// FIXME: args should be TString when IconText has TString
let mut buttons_vec = VerticalMenuButtons::new();
for opt in options {
let button_theme;
match opt.1 {
// FIXME: might not be applicable everywhere
theme::ICON_CANCEL => {
button_theme = theme::button_vertical_menu_orange();
}
@ -93,11 +93,12 @@ impl Component for VerticalMenu {
self.area = bounds;
self.areas_sep.clear();
let mut remaining = bounds;
for i in 0..N_ITEMS {
let n_seps = self.buttons.len() - 1;
for (i, button) in self.buttons.iter_mut().enumerate() {
let (area_button, new_remaining) = remaining.split_top(MENU_BUTTON_HEIGHT);
self.buttons[i].place(area_button);
button.place(area_button);
remaining = new_remaining;
if i < N_SEPS {
if i < n_seps {
let (area_sep, new_remaining) = remaining.split_top(MENU_SEP_HEIGHT);
unwrap!(self.areas_sep.push(area_sep));
remaining = new_remaining;

@ -0,0 +1,126 @@
use crate::{
error,
micropython::qstr::Qstr,
strutil::TString,
translations::TR,
ui::{
component::text::paragraphs::{Paragraph, Paragraphs},
flow::{
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeDirection, SwipeFlow,
SwipePage,
},
},
};
use heapless::Vec;
use super::super::{
component::{Frame, FrameMsg, VerticalMenu, VerticalMenuChoiceMsg},
theme,
};
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmResetDevice {
Intro,
Menu,
}
impl FlowState for ConfirmResetDevice {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmResetDevice::Intro, SwipeDirection::Left) => {
Decision::Goto(ConfirmResetDevice::Menu, direction)
}
(ConfirmResetDevice::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmResetDevice::Intro, direction)
}
(ConfirmResetDevice::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(ConfirmResetDevice::Intro, FlowMsg::Info) => {
Decision::Goto(ConfirmResetDevice::Menu, SwipeDirection::Left)
}
(ConfirmResetDevice::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmResetDevice::Intro, SwipeDirection::Right)
}
(ConfirmResetDevice::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{buffer::StrBuffer, map::Map, obj::Obj, util},
ui::layout::obj::LayoutObj,
};
pub extern "C" fn new_confirm_reset_device(
n_args: usize,
args: *const Obj,
kwargs: *mut Map,
) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmResetDevice::new) }
}
impl ConfirmResetDevice {
fn new(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let par_array: [Paragraph<'static>; 3] = [
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, TR::reset__by_continuing)
.with_bottom_padding(17),
Paragraph::new(&theme::TEXT_SUB_GREY, TR::reset__more_info_at),
Paragraph::new(&theme::TEXT_SUB_GREY_LIGHT, TR::reset__tos_link),
];
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(&[(
"Cancel", // FIXME: use TString
theme::ICON_CANCEL
)]))),
)
.with_cancel_button();
let content_confirm = Frame::left_aligned(
TR::reset__title_create_wallet.into(),
PromptScreen::new_hold_to_confirm(),
)
.with_footer(TR::instructions__hold_to_confirm.into(), None);
let store = flow_store()
// Intro,
.add(
Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_info_button()
.with_footer(TR::instructions__swipe_up.into(), None),
|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info),
)?
// Menu,
.add(
Frame::left_aligned(
"".into(),
VerticalMenu::context_menu(unwrap!(Vec::from_slice(&[(
"Cancel", // FIXME: use TString
theme::ICON_CANCEL
)]))),
)
.with_cancel_button(),
|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => {
Some(FlowMsg::Choice(i))
}
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
},
)?;
let res = SwipeFlow::new(ConfirmResetDevice::Intro, store)?;
Ok(LayoutObj::new(res)?.into())
}
}

@ -12,6 +12,7 @@ use crate::{
},
},
};
use heapless::Vec;
use super::super::{
component::{Frame, FrameMsg, IconDialog, VerticalMenu, VerticalMenuChoiceMsg},
@ -120,11 +121,11 @@ impl GetAddress {
.add(
Frame::left_aligned(
"".into(),
VerticalMenu::context_menu([
VerticalMenu::context_menu(unwrap!(Vec::from_slice(&[
("Address QR code", theme::ICON_QR_CODE),
("Account info", theme::ICON_CHEVRON_RIGHT),
("Cancel trans.", theme::ICON_CANCEL),
]),
]))),
)
.with_cancel_button(),
|msg| match msg {

@ -1,3 +1,5 @@
pub mod get_address;
pub mod confirm_reset_device;
pub use get_address::GetAddress;
pub use confirm_reset_device::ConfirmResetDevice;

@ -663,28 +663,6 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
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(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 = kwargs.get(Qstr::MP_QSTR_qr_title)?.try_into()?;
@ -1292,12 +1270,11 @@ extern "C" fn new_show_tx_context_menu(n_args: usize, args: *const Obj, kwargs:
let block = move |_args: &[Obj], _kwargs: &Map| {
// TODO: this is just POC
let title: TString = "".into();
let options: [(&'static str, Icon); 3] = [
let options = unwrap!(Vec::from_slice(&[
("Address QR code", theme::ICON_QR_CODE),
("Fee info", theme::ICON_CHEVRON_RIGHT),
("Cancel transaction", theme::ICON_CANCEL),
];
]));
let content = VerticalMenu::context_menu(options);
let frame_with_menu = Frame::left_aligned(title, content).with_cancel_button();
let obj = LayoutObj::new(frame_with_menu)?;
@ -1801,7 +1778,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// 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(),
Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, flow::confirm_reset_device::new_confirm_reset_device).as_obj(),
/// def show_address_details(
/// *,

@ -707,8 +707,12 @@ pub const fn loader_lock_icon() -> LoaderStyleSheet {
}
pub const TEXT_SUPER: TextStyle = TextStyle::new(Font::BIG, GREY_EXTRA_LIGHT, BG, GREY, GREY);
pub const TEXT_MAIN: TextStyle = TextStyle::new(Font::NORMAL, GREY_EXTRA_LIGHT, BG, GREY, GREY);
pub const TEXT_SUB: TextStyle = TextStyle::new(Font::SUB, GREY, BG, GREY, GREY);
pub const TEXT_MAIN_GREY_EXTRA_LIGHT: TextStyle =
TextStyle::new(Font::NORMAL, GREY_EXTRA_LIGHT, BG, GREY, GREY);
pub const TEXT_MAIN_GREY_LIGHT: TextStyle =
TextStyle::new(Font::NORMAL, GREY_LIGHT, BG, GREY, GREY);
pub const TEXT_SUB_GREY_LIGHT: TextStyle = TextStyle::new(Font::SUB, GREY_LIGHT, BG, GREY, GREY);
pub const TEXT_SUB_GREY: TextStyle = TextStyle::new(Font::SUB, GREY, BG, GREY, GREY);
pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, GREY_EXTRA_LIGHT, BG, GREY, GREY)
.with_line_breaking(LineBreaking::BreakWordsNoHyphen)
.with_page_breaking(PageBreaking::CutAndInsertEllipsisBoth)

@ -349,6 +349,7 @@ class TR:
inputs__return: str = "RETURN"
inputs__show: str = "SHOW"
inputs__space: str = "SPACE"
instructions__swipe_up: str = "Swipe up"
joint__title: str = "JOINT TRANSACTION"
joint__to_the_total_amount: str = "To the total amount:"
joint__you_are_contributing: str = "You are contributing:"

@ -322,17 +322,12 @@ async def confirm_single(
async def confirm_reset_device(title: str, recovery: bool = False) -> None:
if recovery:
button = TR.reset__button_recover
else:
button = TR.reset__button_create
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.confirm_reset_device(
title=title.upper(),
button=button,
title=title,
button="", # not used
)
),
"recover_device" if recovery else "setup_device",

@ -377,6 +377,7 @@
"inputs__previous": "PŘEDCHOZÍ",
"inputs__show": "ZOBRAZIT",
"inputs__space": "ROZDĚLENÍ",
"instructions__swipe_up": "Swipe up",
"joint__title": "SPOLEČNÁ TRANSAKCE",
"joint__to_the_total_amount": "Do celkové částky:",
"joint__you_are_contributing": "Přispíváte:",

@ -377,6 +377,7 @@
"inputs__return": "ZURÜCK",
"inputs__show": "ANZEIGEN",
"inputs__space": "LEER",
"instructions__swipe_up": "Swipe up",
"joint__title": "GEMEINS. TRANSAKT.",
"joint__to_the_total_amount": "Gesamtbetrag:",
"joint__you_are_contributing": "Dein Anteil:",

@ -351,6 +351,7 @@
"inputs__previous": "PREVIOUS",
"inputs__show": "SHOW",
"inputs__space": "SPACE",
"instructions__swipe_up": "Swipe up",
"joint__title": "JOINT TRANSACTION",
"joint__to_the_total_amount": "To the total amount:",
"joint__you_are_contributing": "You are contributing:",

@ -377,6 +377,7 @@
"inputs__return": "VOLVER",
"inputs__show": "MOSTRAR",
"inputs__space": "ESPACIO",
"instructions__swipe_up": "Swipe up",
"joint__title": "TRANSACC. CONJUNTA",
"joint__to_the_total_amount": "Al importe total:",
"joint__you_are_contributing": "Estás aportando:",

@ -377,6 +377,7 @@
"inputs__return": "RETOUR",
"inputs__show": "AFFICHER",
"inputs__space": "ESPACE",
"instructions__swipe_up": "Swipe up",
"joint__title": "TRANS. COMMUNE",
"joint__to_the_total_amount": "Au montant total :",
"joint__you_are_contributing": "Votre contribution :",

@ -843,5 +843,6 @@
"841": "ethereum__staking_unstake_intro",
"842": "storage_msg__starting",
"843": "storage_msg__verifying_pin",
"844": "storage_msg__wrong_pin"
"844": "storage_msg__wrong_pin",
"845": "instructions__swipe_up"
}

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "bb6eb3feee5ec334a310bb083a1c4a84910bbaabc6808dec0ed0ab5831bab390",
"datetime": "2024-04-11T20:31:39.003670",
"commit": "fd71de0c950dfad73d0f783a6cbcb138f711d32c"
"merkle_root": "2f1c1b3de98b6be084e1c1d51f64d7d8d11ecce193563db41065da56ba8d6fba",
"datetime": "2024-04-16T08:55:40.825962",
"commit": "dbba304f5cfd54a0a08e2fa339149dec71ab2299"
},
"history": [
{

Loading…
Cancel
Save