1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-01 20:08:26 +00:00

refactor(core): move show_checklist to UiFeatures

- checklist is now fixed to 3 items as they are not used in other way,
it can be switched to use heapless::Vec if the need for more items
appears.
This commit is contained in:
obrusvit 2024-11-01 17:14:21 +01:00
parent 466d9a796e
commit f8fec86dab
13 changed files with 178 additions and 199 deletions

View File

@ -227,6 +227,21 @@ extern "C" fn new_set_brightness(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
extern "C" fn new_show_checklist(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 active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let items: [TString<'static>; 3] = util::iter_into_array(items)?;
let layout = ModelUI::show_checklist(title, button, active, items)?;
Ok(LayoutObj::new_root(layout)?.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_homescreen(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 label: TString<'static> = kwargs let label: TString<'static> = kwargs
@ -543,6 +558,17 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// """Show the brightness configuration dialog.""" /// """Show the brightness configuration dialog."""
Qstr::MP_QSTR_set_brightness => obj_fn_kw!(0, new_set_brightness).as_obj(), Qstr::MP_QSTR_set_brightness => obj_fn_kw!(0, new_set_brightness).as_obj(),
/// def show_checklist(
/// *,
/// title: str,
/// items: Iterable[str],
/// active: int,
/// button: str,
/// ) -> LayoutObj[UiResult]:
/// """Checklist of backup steps. Active index is highlighted, previous items have check
/// mark next to them. Limited to 3 items."""
Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(),
/// def show_homescreen( /// def show_homescreen(
/// *, /// *,
/// label: str | None, /// label: str | None,

View File

@ -1063,47 +1063,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_checklist(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 active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let mut paragraphs = ParagraphVecLong::new();
for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() {
let style = match i.cmp(&active) {
Ordering::Less => &theme::TEXT_CHECKLIST_DONE,
Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED,
Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT,
};
let text: TString = item.try_into()?;
paragraphs.add(Paragraph::new(style, text));
}
let checklist_content = Checklist::from_paragraphs(
theme::ICON_CHEVRON_RIGHT,
theme::ICON_BULLET_CHECKMARK,
active,
paragraphs
.into_paragraphs()
.with_spacing(theme::CHECKLIST_SPACING),
)
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
.with_numerals()
.with_icon_done_color(theme::GREEN)
.with_done_offset(theme::CHECKLIST_DONE_OFFSET);
let obj = LayoutObj::new(SwipeUpScreen::new(
Frame::left_aligned(title, SwipeContent::new(checklist_content))
.with_footer(TR::instructions__swipe_up.into(), None)
.with_swipe(Direction::Up, SwipeSettings::default()),
))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_tutorial() -> Obj { extern "C" fn new_show_tutorial() -> Obj {
let block = || { let block = || {
let flow = flow::show_tutorial::new_show_tutorial()?; let flow = flow::show_tutorial::new_show_tutorial()?;
@ -1407,17 +1366,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// confirmation.""" /// confirmation."""
Qstr::MP_QSTR_flow_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), Qstr::MP_QSTR_flow_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
/// def show_checklist(
/// *,
/// title: str,
/// items: Iterable[str],
/// active: int,
/// button: str,
/// ) -> LayoutObj[UiResult]:
/// """Checklist of backup steps. Active index is highlighted, previous items have check
/// mark next to them."""
Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(),
/// def flow_continue_recovery( /// def flow_continue_recovery(
/// *, /// *,
/// first_screen: bool, /// first_screen: bool,

View File

@ -1,3 +1,5 @@
use core::cmp::Ordering;
use crate::{ use crate::{
error::{value_error, Error}, error::{value_error, Error},
io::BinaryData, io::BinaryData,
@ -8,7 +10,10 @@ use crate::{
component::{ component::{
connect::Connect, connect::Connect,
swipe_detect::SwipeSettings, swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs}, text::paragraphs::{
Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort,
Paragraphs, VecExt,
},
CachedJpeg, ComponentExt, Never, Timeout, CachedJpeg, ComponentExt, Never, Timeout,
}, },
geometry::Direction, geometry::Direction,
@ -218,6 +223,43 @@ impl UIFeaturesFirmware for ModelMercuryFeatures {
Ok(flow) Ok(flow)
} }
fn show_checklist(
title: TString<'static>,
button: TString<'static>,
active: usize,
items: [TString<'static>; 3],
) -> Result<impl LayoutMaybeTrace, Error> {
let mut paragraphs = ParagraphVecLong::new();
for (i, item) in items.into_iter().enumerate() {
let style = match i.cmp(&active) {
Ordering::Less => &theme::TEXT_CHECKLIST_DONE,
Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED,
Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT,
};
paragraphs.add(Paragraph::new(style, item));
}
let checklist_content = Checklist::from_paragraphs(
theme::ICON_CHEVRON_RIGHT,
theme::ICON_BULLET_CHECKMARK,
active,
paragraphs
.into_paragraphs()
.with_spacing(theme::CHECKLIST_SPACING),
)
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
.with_numerals()
.with_icon_done_color(theme::GREEN)
.with_done_offset(theme::CHECKLIST_DONE_OFFSET);
let layout = RootComponent::new(SwipeUpScreen::new(
Frame::left_aligned(title, SwipeContent::new(checklist_content))
.with_footer(TR::instructions__swipe_up.into(), None)
.with_swipe(Direction::Up, SwipeSettings::default()),
));
Ok(layout)
}
fn show_homescreen( fn show_homescreen(
label: TString<'static>, label: TString<'static>,
hold: bool, hold: bool,

View File

@ -1205,46 +1205,6 @@ 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) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let mut paragraphs = ParagraphVecLong::new();
for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() {
let style = match i.cmp(&active) {
Ordering::Less => &theme::TEXT_NORMAL,
Ordering::Equal => &theme::TEXT_BOLD,
Ordering::Greater => &theme::TEXT_NORMAL,
};
let text: TString = item.try_into()?;
paragraphs.add(Paragraph::new(style, text));
}
let confirm_btn = Some(ButtonDetails::text(button));
let obj = LayoutObj::new(
ButtonPage::new(
Checklist::from_paragraphs(
theme::ICON_ARROW_RIGHT_FAT,
theme::ICON_TICK_FAT,
active,
paragraphs
.into_paragraphs()
.with_spacing(theme::CHECKLIST_SPACING),
)
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
.with_current_offset(theme::CHECKLIST_CURRENT_OFFSET),
theme::BG,
)
.with_confirm_btn(confirm_btn),
)?;
Ok(obj.into())
};
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 { extern "C" fn new_confirm_recovery(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 description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
@ -1549,17 +1509,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Shows a backup seed.""" /// """Shows a backup seed."""
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
/// def show_checklist(
/// *,
/// title: str, # unused on TR
/// items: Iterable[str],
/// active: int,
/// button: str,
/// ) -> LayoutObj[UiResult]:
/// """Checklist of backup steps. Active index is highlighted, previous items have check
/// mark next to them."""
Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(),
/// def confirm_recovery( /// def confirm_recovery(
/// *, /// *,
/// title: str, # unused on TR /// title: str, # unused on TR

View File

@ -1,3 +1,5 @@
use core::cmp::Ordering;
use crate::{ use crate::{
error::Error, error::Error,
io::BinaryData, io::BinaryData,
@ -8,7 +10,10 @@ use crate::{
ui::{ ui::{
component::{ component::{
connect::Connect, connect::Connect,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, text::paragraphs::{
Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort,
Paragraphs, VecExt,
},
Component, ComponentExt, Empty, Label, LineBreaking, Paginate, Timeout, Component, ComponentExt, Empty, Label, LineBreaking, Paginate, Timeout,
}, },
layout::{ layout::{
@ -219,6 +224,42 @@ impl UIFeaturesFirmware for ModelTRFeatures {
)) ))
} }
fn show_checklist(
title: TString<'static>,
button: TString<'static>,
active: usize,
items: [TString<'static>; 3],
) -> Result<impl LayoutMaybeTrace, Error> {
let mut paragraphs = ParagraphVecLong::new();
for (i, item) in items.into_iter().enumerate() {
let style = match i.cmp(&active) {
Ordering::Less => &theme::TEXT_NORMAL,
Ordering::Equal => &theme::TEXT_BOLD,
Ordering::Greater => &theme::TEXT_NORMAL,
};
paragraphs.add(Paragraph::new(style, item));
}
let confirm_btn = Some(ButtonDetails::text(button));
let layout = RootComponent::new(
ButtonPage::new(
Checklist::from_paragraphs(
theme::ICON_ARROW_RIGHT_FAT,
theme::ICON_TICK_FAT,
active,
paragraphs
.into_paragraphs()
.with_spacing(theme::CHECKLIST_SPACING),
)
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
.with_current_offset(theme::CHECKLIST_CURRENT_OFFSET),
theme::BG,
)
.with_confirm_btn(confirm_btn),
);
Ok(layout)
}
fn show_homescreen( fn show_homescreen(
label: TString<'static>, label: TString<'static>,
hold: bool, hold: bool,

View File

@ -1131,49 +1131,6 @@ 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) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
extern "C" fn new_show_checklist(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 active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let mut paragraphs = ParagraphVecLong::new();
for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() {
let style = match i.cmp(&active) {
Ordering::Less => &theme::TEXT_CHECKLIST_DONE,
Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED,
Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT,
};
let text: TString = item.try_into()?;
paragraphs.add(Paragraph::new(style, text));
}
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
Dialog::new(
Checklist::from_paragraphs(
theme::ICON_LIST_CURRENT,
theme::ICON_LIST_CHECK,
active,
paragraphs
.into_paragraphs()
.with_spacing(theme::CHECKLIST_SPACING),
)
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
.with_current_offset(theme::CHECKLIST_CURRENT_OFFSET)
.with_done_offset(theme::CHECKLIST_DONE_OFFSET),
theme::button_bar(Button::with_text(button).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
),
))?;
Ok(obj.into())
};
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 { extern "C" fn new_confirm_recovery(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 title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -1499,17 +1456,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" /// """Show mnemonic for backup. Expects the words pre-divided into individual pages."""
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
/// def show_checklist(
/// *,
/// title: str,
/// items: Iterable[str],
/// active: int,
/// button: str,
/// ) -> LayoutObj[UiResult]:
/// """Checklist of backup steps. Active index is highlighted, previous items have check
/// mark next to them."""
Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(),
/// def confirm_recovery( /// def confirm_recovery(
/// *, /// *,
/// title: str, /// title: str,

View File

@ -1,3 +1,5 @@
use core::cmp::Ordering;
use crate::{ use crate::{
error::{value_error, Error}, error::{value_error, Error},
io::BinaryData, io::BinaryData,
@ -8,7 +10,10 @@ use crate::{
component::{ component::{
connect::Connect, connect::Connect,
image::BlendedImage, image::BlendedImage,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, text::paragraphs::{
Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort,
Paragraphs, VecExt,
},
ComponentExt, Empty, Jpeg, Label, Never, Timeout, ComponentExt, Empty, Jpeg, Label, Never, Timeout,
}, },
layout::{ layout::{
@ -240,6 +245,45 @@ impl UIFeaturesFirmware for ModelTTFeatures {
Ok(layout) Ok(layout)
} }
fn show_checklist(
title: TString<'static>,
button: TString<'static>,
active: usize,
items: [TString<'static>; 3],
) -> Result<impl LayoutMaybeTrace, Error> {
let mut paragraphs = ParagraphVecLong::new();
for (i, item) in items.into_iter().enumerate() {
let style = match i.cmp(&active) {
Ordering::Less => &theme::TEXT_CHECKLIST_DONE,
Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED,
Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT,
};
paragraphs.add(Paragraph::new(style, item));
}
let layout = RootComponent::new(Frame::left_aligned(
theme::label_title(),
title,
Dialog::new(
Checklist::from_paragraphs(
theme::ICON_LIST_CURRENT,
theme::ICON_LIST_CHECK,
active,
paragraphs
.into_paragraphs()
.with_spacing(theme::CHECKLIST_SPACING),
)
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
.with_current_offset(theme::CHECKLIST_CURRENT_OFFSET)
.with_done_offset(theme::CHECKLIST_DONE_OFFSET),
theme::button_bar(Button::with_text(button).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
),
));
Ok(layout)
}
fn show_homescreen( fn show_homescreen(
label: TString<'static>, label: TString<'static>,
hold: bool, hold: bool,

View File

@ -75,6 +75,13 @@ pub trait UIFeaturesFirmware {
fn set_brightness(current_brightness: Option<u8>) -> Result<impl LayoutMaybeTrace, Error>; fn set_brightness(current_brightness: Option<u8>) -> Result<impl LayoutMaybeTrace, Error>;
fn show_checklist(
title: TString<'static>,
button: TString<'static>,
active: usize,
items: [TString<'static>; 3],
) -> Result<impl LayoutMaybeTrace, Error>;
fn show_homescreen( fn show_homescreen(
label: TString<'static>, label: TString<'static>,
hold: bool, hold: bool,

View File

@ -257,18 +257,6 @@ def flow_show_share_words(
confirmation.""" confirmation."""
# rust/src/ui/model_mercury/layout.rs
def show_checklist(
*,
title: str,
items: Iterable[str],
active: int,
button: str,
) -> LayoutObj[UiResult]:
"""Checklist of backup steps. Active index is highlighted, previous items have check
mark next to them."""
# rust/src/ui/model_mercury/layout.rs # rust/src/ui/model_mercury/layout.rs
def flow_continue_recovery( def flow_continue_recovery(
*, *,
@ -619,18 +607,6 @@ def show_share_words(
"""Shows a backup seed.""" """Shows a backup seed."""
# rust/src/ui/model_tr/layout.rs
def show_checklist(
*,
title: str, # unused on TR
items: Iterable[str],
active: int,
button: str,
) -> LayoutObj[UiResult]:
"""Checklist of backup steps. Active index is highlighted, previous items have check
mark next to them."""
# rust/src/ui/model_tr/layout.rs # rust/src/ui/model_tr/layout.rs
def confirm_recovery( def confirm_recovery(
*, *,
@ -933,18 +909,6 @@ def show_share_words(
"""Show mnemonic for backup. Expects the words pre-divided into individual pages.""" """Show mnemonic for backup. Expects the words pre-divided into individual pages."""
# rust/src/ui/model_tt/layout.rs
def show_checklist(
*,
title: str,
items: Iterable[str],
active: int,
button: str,
) -> LayoutObj[UiResult]:
"""Checklist of backup steps. Active index is highlighted, previous items have check
mark next to them."""
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
def confirm_recovery( def confirm_recovery(
*, *,

View File

@ -196,6 +196,18 @@ def set_brightness(
"""Show the brightness configuration dialog.""" """Show the brightness configuration dialog."""
# rust/src/ui/api/firmware_upy.rs
def show_checklist(
*,
title: str,
items: Iterable[str],
active: int,
button: str,
) -> LayoutObj[UiResult]:
"""Checklist of backup steps. Active index is highlighted, previous items have check
mark next to them. Limited to 3 items."""
# rust/src/ui/api/firmware_upy.rs # rust/src/ui/api/firmware_upy.rs
def show_homescreen( def show_homescreen(
*, *,

View File

@ -98,7 +98,7 @@ async def slip39_show_checklist(
) -> None: ) -> None:
items = _slip_39_checklist_items(step, advanced, count, threshold) items = _slip_39_checklist_items(step, advanced, count, threshold)
result = await interact( result = await interact(
trezorui2.show_checklist( trezorui_api.show_checklist(
title=TR.reset__title_shamir_backup, title=TR.reset__title_shamir_backup,
button=TR.buttons__continue, button=TR.buttons__continue,
active=step, active=step,

View File

@ -122,7 +122,7 @@ def slip39_show_checklist(
) )
return raise_if_not_confirmed( return raise_if_not_confirmed(
trezorui2.show_checklist( trezorui_api.show_checklist(
title=TR.reset__slip39_checklist_title, title=TR.reset__slip39_checklist_title,
button=TR.buttons__continue, button=TR.buttons__continue,
active=step, active=step,

View File

@ -120,7 +120,7 @@ def slip39_show_checklist(
) )
return raise_if_not_confirmed( return raise_if_not_confirmed(
trezorui2.show_checklist( trezorui_api.show_checklist(
title=TR.reset__slip39_checklist_title, title=TR.reset__slip39_checklist_title,
button=TR.buttons__continue, button=TR.buttons__continue,
active=step, active=step,