diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 83824a301..612c5a869 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -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; diff --git a/core/embed/rust/src/ui/model_mercury/component/footer.rs b/core/embed/rust/src/ui/model_mercury/component/footer.rs index 91038a1cd..4161b2ba8 100644 --- a/core/embed/rust/src/ui/model_mercury/component/footer.rs +++ b/core/embed/rust/src/ui/model_mercury/component/footer.rs @@ -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, } } @@ -138,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); + } +} diff --git a/core/embed/rust/src/ui/model_mercury/component/frame.rs b/core/embed/rust/src/ui/model_mercury/component/frame.rs index 608a78d1f..09269b577 100644 --- a/core/embed/rust/src/ui/model_mercury/component/frame.rs +++ b/core/embed/rust/src/ui/model_mercury/component/frame.rs @@ -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, diff --git a/core/embed/rust/src/ui/model_mercury/component/share_words.rs b/core/embed/rust/src/ui/model_mercury/component/share_words.rs index 5d3cfb9dc..564d3173d 100644 --- a/core/embed/rust/src/ui/model_mercury/component/share_words.rs +++ b/core/embed/rust/src/ui/model_mercury/component/share_words.rs @@ -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); diff --git a/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs b/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs index 84b89b236..13edb4381 100644 --- a/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs +++ b/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs @@ -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; diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_reset_device.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_reset_device.rs new file mode 100644 index 000000000..32475935a --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_reset_device.rs @@ -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 { + 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 { + 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 { + 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()) + } +} diff --git a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs index c56037f4f..13065dd9e 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs @@ -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 { diff --git a/core/embed/rust/src/ui/model_mercury/flow/mod.rs b/core/embed/rust/src/ui/model_mercury/flow/mod.rs index be27c0c4c..6cd147fa8 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/mod.rs @@ -1,3 +1,5 @@ pub mod get_address; +pub mod confirm_reset_device; pub use get_address::GetAddress; +pub use confirm_reset_device::ConfirmResetDevice; diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index d61cc7ef6..0e1c65967 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -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( /// *, diff --git a/core/embed/rust/src/ui/model_mercury/theme/mod.rs b/core/embed/rust/src/ui/model_mercury/theme/mod.rs index f33f0b754..41f884e70 100644 --- a/core/embed/rust/src/ui/model_mercury/theme/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/theme/mod.rs @@ -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) diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index 81fd7e4b7..f2cfe8838 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -355,7 +355,8 @@ class TR: inputs__return: str = "RETURN" inputs__show: str = "SHOW" inputs__space: str = "SPACE" - joint__title: str = "Joint transaction" + 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:" language__change_to_template: str = "Change language to {0}?" diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index 2550c6b9c..f946047b4 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -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", diff --git a/core/translations/cs.json b/core/translations/cs.json index 91e348db5..ed18e1b32 100644 --- a/core/translations/cs.json +++ b/core/translations/cs.json @@ -383,6 +383,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:", diff --git a/core/translations/de.json b/core/translations/de.json index 220da14ae..691ad4fba 100644 --- a/core/translations/de.json +++ b/core/translations/de.json @@ -383,6 +383,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:", diff --git a/core/translations/en.json b/core/translations/en.json index d7d2a5b26..05645cc3f 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -357,6 +357,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:", diff --git a/core/translations/es.json b/core/translations/es.json index d79925141..8ab1f422e 100644 --- a/core/translations/es.json +++ b/core/translations/es.json @@ -383,6 +383,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:", diff --git a/core/translations/fr.json b/core/translations/fr.json index a8380bac4..e4acd9b3f 100644 --- a/core/translations/fr.json +++ b/core/translations/fr.json @@ -383,6 +383,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 :", diff --git a/core/translations/order.json b/core/translations/order.json index 3a50bace4..97c9086a6 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -851,5 +851,6 @@ "849": "cardano__delegating_to_key_hash", "850": "cardano__delegating_to_script", "851": "cardano__deposit", - "852": "cardano__vote_delegation" + "852": "cardano__vote_delegation", + "853": "instructions__swipe_up" } diff --git a/core/translations/signatures.json b/core/translations/signatures.json index 6afc92391..6e303bad2 100644 --- a/core/translations/signatures.json +++ b/core/translations/signatures.json @@ -1,8 +1,8 @@ { "current": { - "merkle_root": "1f974d2401250eb1b9ca75b0b8067d583c2a1520f87697f8f68f5bf2d66b44c2", - "datetime": "2024-05-16T21:16:11.144158", - "commit": "3d74e30fa7f27695813040baffb620461d5e0677" + "merkle_root": "4b19a878ad4d4daf9941ca0a7e024bcb5aa7e456c292821ef9ed5144c72e2531", + "datetime": "2024-05-17T10:01:56.790696", + "commit": "93fca0189d3622a316657465d1b96b642e8665a1" }, "history": [ {