1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-26 01:18:28 +00:00

refactor(core): move tutorial to UiFeatures

- unsupported on model_t
This commit is contained in:
obrusvit 2024-11-01 21:22:14 +01:00
parent d4fa752709
commit 36f4c46389
11 changed files with 122 additions and 119 deletions

View File

@ -358,6 +358,14 @@ extern "C" fn new_show_wait_text(message: Obj) -> Obj {
unsafe { util::try_or_raise(block) } unsafe { util::try_or_raise(block) }
} }
extern "C" fn new_tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], _kwargs: &Map| {
let layout = ModelUI::tutorial()?;
Ok(LayoutObj::new_root(layout)?.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj { pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj {
let block = || { let block = || {
let buffer = data.try_into()?; let buffer = data.try_into()?;
@ -654,6 +662,10 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// """Show single-line text in the middle of the screen.""" /// """Show single-line text in the middle of the screen."""
Qstr::MP_QSTR_show_wait_text => obj_fn_1!(new_show_wait_text).as_obj(), Qstr::MP_QSTR_show_wait_text => obj_fn_1!(new_show_wait_text).as_obj(),
/// def tutorial() -> LayoutObj[UiResult]:
/// """Show user how to interact with the device."""
Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, new_tutorial).as_obj(),
/// class BacklightLevels: /// class BacklightLevels:
/// """Backlight levels. Values dynamically update based on user settings.""" /// """Backlight levels. Values dynamically update based on user settings."""
/// MAX: ClassVar[int] /// MAX: ClassVar[int]

View File

@ -1015,15 +1015,6 @@ extern "C" fn new_prompt_backup() -> Obj {
unsafe { util::try_or_raise(block) } unsafe { util::try_or_raise(block) }
} }
extern "C" fn new_show_tutorial() -> Obj {
let block = || {
let flow = flow::show_tutorial::new_show_tutorial()?;
let obj = LayoutObj::new_root(flow)?;
Ok(obj.into())
};
unsafe { util::try_or_raise(block) }
}
extern "C" fn new_show_group_share_success( extern "C" fn new_show_group_share_success(
n_args: usize, n_args: usize,
args: *const Obj, args: *const Obj,
@ -1323,10 +1314,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Shown after successfully finishing a group.""" /// """Shown after successfully finishing a group."""
Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(), Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(),
/// def tutorial() -> LayoutObj[UiResult]:
/// """Show user how to interact with the device."""
Qstr::MP_QSTR_tutorial => obj_fn_0!(new_show_tutorial).as_obj(),
/// def flow_get_address( /// def flow_get_address(
/// *, /// *,
/// address: str | bytes, /// address: str | bytes,

View File

@ -356,4 +356,9 @@ impl UIFeaturesFirmware for ModelMercuryFeatures {
let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG)); let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG));
Ok(layout) Ok(layout)
} }
fn tutorial() -> Result<impl LayoutMaybeTrace, Error> {
let flow = flow::show_tutorial::new_show_tutorial()?;
Ok(flow)
}
} }

View File

@ -753,95 +753,6 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
/// General pattern of most tutorial screens.
/// (title, text, btn_layout, btn_actions, text_y_offset)
fn tutorial_screen(
title: TString<'static>,
text: TR,
btn_layout: ButtonLayout,
btn_actions: ButtonActions,
) -> Page {
let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(title)
}
extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], _kwargs: &Map| {
const PAGE_COUNT: usize = 7;
let get_page = move |page_index| {
// Lazy-loaded list of screens to show, with custom content,
// buttons and actions triggered by these buttons.
// Cancelling the first screen will point to the last one,
// which asks for confirmation whether user wants to
// really cancel the tutorial.
match page_index {
// title, text, btn_layout, btn_actions
0 => tutorial_screen(
TR::tutorial__title_hello.into(),
TR::tutorial__welcome_press_right,
ButtonLayout::cancel_none_arrow(),
ButtonActions::last_none_next(),
),
1 => tutorial_screen(
"".into(),
TR::tutorial__use_trezor,
ButtonLayout::arrow_none_arrow(),
ButtonActions::prev_none_next(),
),
2 => tutorial_screen(
TR::buttons__hold_to_confirm.into(),
TR::tutorial__press_and_hold,
ButtonLayout::arrow_none_htc(TR::buttons__hold_to_confirm.into()),
ButtonActions::prev_none_next(),
),
3 => tutorial_screen(
TR::tutorial__title_screen_scroll.into(),
TR::tutorial__scroll_down,
ButtonLayout::arrow_none_text(TR::buttons__continue.into()),
ButtonActions::prev_none_next(),
),
4 => tutorial_screen(
TR::buttons__confirm.into(),
TR::tutorial__middle_click,
ButtonLayout::none_armed_none(TR::buttons__confirm.into()),
ButtonActions::none_next_none(),
),
5 => tutorial_screen(
TR::tutorial__title_tutorial_complete.into(),
TR::tutorial__ready_to_use,
ButtonLayout::text_none_text(
TR::buttons__again.into(),
TR::buttons__continue.into(),
),
ButtonActions::beginning_none_confirm(),
),
6 => tutorial_screen(
TR::tutorial__title_skip.into(),
TR::tutorial__sure_you_want_skip,
ButtonLayout::arrow_none_text(TR::buttons__skip.into()),
ButtonActions::beginning_none_cancel(),
),
_ => unreachable!(),
}
};
let pages = FlowPages::new(get_page, PAGE_COUNT);
// Setting the ignore-second-button to mimic all the Choice pages, to teach user
// that they should really press both buttons at the same time to achieve
// middle-click.
let obj = LayoutObj::new(
Flow::new(pages)
.with_scrollbar(false)
.with_ignore_second_button_ms(constant::IGNORE_OTHER_BTN_MS),
)?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
@ -1364,10 +1275,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Confirm details about altcoin transaction.""" /// """Confirm details about altcoin transaction."""
Qstr::MP_QSTR_altcoin_tx_summary => obj_fn_kw!(0, new_altcoin_tx_summary).as_obj(), Qstr::MP_QSTR_altcoin_tx_summary => obj_fn_kw!(0, new_altcoin_tx_summary).as_obj(),
/// def tutorial() -> LayoutObj[UiResult]:
/// """Show user how to interact with the device."""
Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(),
/// def confirm_modify_fee( /// def confirm_modify_fee(
/// *, /// *,
/// title: str, # ignored /// title: str, # ignored

View File

@ -23,7 +23,10 @@ use crate::{
obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, obj::{LayoutMaybeTrace, LayoutObj, RootComponent},
util::RecoveryType, util::RecoveryType,
}, },
model_tr::component::{ButtonActions, ButtonLayout, Page}, model_tr::{
component::{ButtonActions, ButtonLayout, Page},
constant,
},
ui_features_fw::UIFeaturesFirmware, ui_features_fw::UIFeaturesFirmware,
}, },
}; };
@ -389,6 +392,79 @@ impl UIFeaturesFirmware for ModelTRFeatures {
let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG)); let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG));
Ok(layout) Ok(layout)
} }
fn tutorial() -> Result<impl LayoutMaybeTrace, Error> {
const PAGE_COUNT: usize = 7;
let get_page = move |page_index| {
// Lazy-loaded list of screens to show, with custom content,
// buttons and actions triggered by these buttons.
// Cancelling the first screen will point to the last one,
// which asks for confirmation whether user wants to
// really cancel the tutorial.
match page_index {
// title, text, btn_layout, btn_actions
0 => tutorial_screen(
TR::tutorial__title_hello.into(),
TR::tutorial__welcome_press_right,
ButtonLayout::cancel_none_arrow(),
ButtonActions::last_none_next(),
),
1 => tutorial_screen(
"".into(),
TR::tutorial__use_trezor,
ButtonLayout::arrow_none_arrow(),
ButtonActions::prev_none_next(),
),
2 => tutorial_screen(
TR::buttons__hold_to_confirm.into(),
TR::tutorial__press_and_hold,
ButtonLayout::arrow_none_htc(TR::buttons__hold_to_confirm.into()),
ButtonActions::prev_none_next(),
),
3 => tutorial_screen(
TR::tutorial__title_screen_scroll.into(),
TR::tutorial__scroll_down,
ButtonLayout::arrow_none_text(TR::buttons__continue.into()),
ButtonActions::prev_none_next(),
),
4 => tutorial_screen(
TR::buttons__confirm.into(),
TR::tutorial__middle_click,
ButtonLayout::none_armed_none(TR::buttons__confirm.into()),
ButtonActions::none_next_none(),
),
5 => tutorial_screen(
TR::tutorial__title_tutorial_complete.into(),
TR::tutorial__ready_to_use,
ButtonLayout::text_none_text(
TR::buttons__again.into(),
TR::buttons__continue.into(),
),
ButtonActions::beginning_none_confirm(),
),
6 => tutorial_screen(
TR::tutorial__title_skip.into(),
TR::tutorial__sure_you_want_skip,
ButtonLayout::arrow_none_text(TR::buttons__skip.into()),
ButtonActions::beginning_none_cancel(),
),
_ => unreachable!(),
}
};
let pages = FlowPages::new(get_page, PAGE_COUNT);
// Setting the ignore-second-button to mimic all the Choice pages, to teach user
// that they should really press both buttons at the same time to achieve
// middle-click.
let layout = RootComponent::new(
Flow::new(pages)
.with_scrollbar(false)
.with_ignore_second_button_ms(constant::IGNORE_OTHER_BTN_MS),
);
Ok(layout)
}
} }
/// Function to create and call a `ButtonPage` dialog based on paginable content /// Function to create and call a `ButtonPage` dialog based on paginable content
@ -427,3 +503,16 @@ fn content_in_button_page<T: Component + Paginate + MaybeTrace + 'static>(
Ok(RootComponent::new(frame)) Ok(RootComponent::new(frame))
} }
/// General pattern of most tutorial screens.
/// (title, text, btn_layout, btn_actions, text_y_offset)
fn tutorial_screen(
title: TString<'static>,
text: TR,
btn_layout: ButtonLayout,
btn_actions: ButtonActions,
) -> Page {
let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(title)
}

View File

@ -440,6 +440,12 @@ impl UIFeaturesFirmware for ModelTTFeatures {
let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG)); let layout = RootComponent::new(Connect::new(text, theme::FG, theme::BG));
Ok(layout) Ok(layout)
} }
fn tutorial() -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelTTFeatures>, Error>(Error::ValueError(
c"tutorial not supported",
))
}
} }
fn new_show_modal( fn new_show_modal(

View File

@ -120,4 +120,6 @@ pub trait UIFeaturesFirmware {
) -> Result<Gc<LayoutObj>, Error>; // TODO: return LayoutMaybeTrace ) -> Result<Gc<LayoutObj>, Error>; // TODO: return LayoutMaybeTrace
fn show_wait_text(text: TString<'static>) -> Result<impl LayoutMaybeTrace, Error>; fn show_wait_text(text: TString<'static>) -> Result<impl LayoutMaybeTrace, Error>;
fn tutorial() -> Result<impl LayoutMaybeTrace, Error>;
} }

View File

@ -262,11 +262,6 @@ def show_group_share_success(
"""Shown after successfully finishing a group.""" """Shown after successfully finishing a group."""
# rust/src/ui/model_mercury/layout.rs
def tutorial() -> LayoutObj[UiResult]:
"""Show user how to interact with the device."""
# rust/src/ui/model_mercury/layout.rs # rust/src/ui/model_mercury/layout.rs
def flow_get_address( def flow_get_address(
*, *,
@ -481,11 +476,6 @@ def altcoin_tx_summary(
"""Confirm details about altcoin transaction.""" """Confirm details about altcoin transaction."""
# rust/src/ui/model_tr/layout.rs
def tutorial() -> LayoutObj[UiResult]:
"""Show user how to interact with the device."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_modify_fee( def confirm_modify_fee(
*, *,

View File

@ -281,6 +281,11 @@ def show_wait_text(message: str, /) -> LayoutObj[None]:
"""Show single-line text in the middle of the screen.""" """Show single-line text in the middle of the screen."""
# rust/src/ui/api/firmware_upy.rs
def tutorial() -> LayoutObj[UiResult]:
"""Show user how to interact with the device."""
# rust/src/ui/api/firmware_upy.rs # rust/src/ui/api/firmware_upy.rs
class BacklightLevels: class BacklightLevels:
"""Backlight levels. Values dynamically update based on user settings.""" """Backlight levels. Values dynamically update based on user settings."""

View File

@ -1204,7 +1204,7 @@ def set_brightness(current: int | None = None) -> Awaitable[None]:
def tutorial(br_code: ButtonRequestType = BR_CODE_OTHER) -> Awaitable[None]: def tutorial(br_code: ButtonRequestType = BR_CODE_OTHER) -> Awaitable[None]:
"""Showing users how to interact with the device.""" """Showing users how to interact with the device."""
return raise_if_not_confirmed( return raise_if_not_confirmed(
trezorui2.tutorial(), trezorui_api.tutorial(),
"tutorial", "tutorial",
br_code, br_code,
) )

View File

@ -487,7 +487,7 @@ async def confirm_output(
def tutorial(br_code: ButtonRequestType = BR_CODE_OTHER) -> Awaitable[ui.UiResult]: def tutorial(br_code: ButtonRequestType = BR_CODE_OTHER) -> Awaitable[ui.UiResult]:
"""Showing users how to interact with the device.""" """Showing users how to interact with the device."""
return interact(trezorui2.tutorial(), "tutorial", br_code) return interact(trezorui_api.tutorial(), "tutorial", br_code)
async def should_show_payment_request_details( async def should_show_payment_request_details(