diff --git a/core/embed/rust/src/ui/model_tt/component/address_details.rs b/core/embed/rust/src/ui/model_tt/component/address_details.rs index 8868b67da5..245c8e2e6c 100644 --- a/core/embed/rust/src/ui/model_tt/component/address_details.rs +++ b/core/embed/rust/src/ui/model_tt/component/address_details.rs @@ -15,24 +15,30 @@ use crate::{ use super::{theme, Frame, FrameMsg}; +const MAX_XPUBS: usize = 16; + pub struct AddressDetails { qr_code: Frame, details: Frame>, T>, xpub_view: Frame>, T>, - xpubs: Vec<(T, T), 16>, + xpubs: Vec<(T, T), MAX_XPUBS>, + xpub_pages: Vec, current_page: usize, } impl AddressDetails where - T: ParagraphStrType + From<&'static str>, + T: ParagraphStrType, { pub fn new( qr_address: T, case_sensitive: bool, account: Option, path: Option, - ) -> Result { + ) -> Result + where + T: From<&'static str>, + { let mut para = ParagraphVecShort::new(); if let Some(a) = account { para.add(Paragraph::new(&theme::TEXT_NORMAL, "Account:".into())); @@ -68,16 +74,50 @@ where .with_cancel_button() .with_border(theme::borders_horizontal_scroll()), xpubs: Vec::new(), + xpub_pages: Vec::new(), current_page: 0, }; Ok(result) } pub fn add_xpub(&mut self, title: T, xpub: T) -> Result<(), Error> { + self.xpub_pages.push(1u8).map_err(|_| Error::OutOfRange)?; self.xpubs .push((title, xpub)) .map_err(|_| Error::OutOfRange) } + + fn switch_xpub(&mut self, i: usize, page: usize) -> usize + where + T: Clone, + { + // Context is needed for updating child so that it can request repaint. In this + // case the parent component that handles paging always requests complete + // repaint after page change so we can use a dummy context here. + let mut dummy_ctx = EventCtx::new(); + self.xpub_view + .update_title(&mut dummy_ctx, self.xpubs[i].0.clone()); + self.xpub_view.update_content(&mut dummy_ctx, |p| { + p.inner_mut().update(self.xpubs[i].1.clone()); + let npages = p.page_count(); + p.change_page(page); + npages + }) + } + + fn lookup(&self, scrollbar_page: usize) -> (usize, usize) { + let mut xpub_index = 0; + let mut xpub_page = scrollbar_page; + for xpub_len in &self.xpub_pages { + if *xpub_len as usize <= xpub_page { + xpub_page -= *xpub_len as usize; + xpub_index += 1; + } else { + break; + } + } + (xpub_index, xpub_page) + } } impl Paginate for AddressDetails @@ -85,30 +125,23 @@ where T: ParagraphStrType + Clone, { fn page_count(&mut self) -> usize { - 2 + self.xpubs.len() + let total_xpub_pages: u8 = self.xpub_pages.iter().copied().sum(); + 2 + (total_xpub_pages as usize) } fn change_page(&mut self, to_page: usize) { self.current_page = to_page; if to_page > 1 { let i = to_page - 2; - // Context is needed for updating child so that it can request repaint. In this - // case the parent component that handles paging always requests complete - // repaint after page change so we can use a dummy context here. - let mut dummy_ctx = EventCtx::new(); - self.xpub_view - .update_title(&mut dummy_ctx, self.xpubs[i].0.clone()); - self.xpub_view.update_content(&mut dummy_ctx, |p| { - p.inner_mut().update(self.xpubs[i].1.clone()); - p.change_page(0) - }); + let (xpub_index, xpub_page) = self.lookup(i); + self.switch_xpub(xpub_index, xpub_page); } } } impl Component for AddressDetails where - T: ParagraphStrType, + T: ParagraphStrType + Clone, { type Msg = (); @@ -116,6 +149,13 @@ where self.qr_code.place(bounds); self.details.place(bounds); self.xpub_view.place(bounds); + + self.xpub_pages.clear(); + for i in 0..self.xpubs.len() { + let npages = self.switch_xpub(i, 0) as u8; + unwrap!(self.xpub_pages.push(npages)); + } + bounds } diff --git a/core/embed/rust/src/ui/model_tt/component/frame.rs b/core/embed/rust/src/ui/model_tt/component/frame.rs index 2291e24423..e9ce917948 100644 --- a/core/embed/rust/src/ui/model_tt/component/frame.rs +++ b/core/embed/rust/src/ui/model_tt/component/frame.rs @@ -81,13 +81,14 @@ where }) } - pub fn update_content(&mut self, ctx: &mut EventCtx, update_fn: F) + pub fn update_content(&mut self, ctx: &mut EventCtx, update_fn: F) -> R where - F: Fn(&mut T), + F: Fn(&mut T) -> R, { self.content.mutate(ctx, |ctx, c| { - update_fn(c); - c.request_complete_repaint(ctx) + let res = update_fn(c); + c.request_complete_repaint(ctx); + res }) } } diff --git a/core/embed/rust/src/ui/model_tt/component/horizontal_page.rs b/core/embed/rust/src/ui/model_tt/component/horizontal_page.rs index 1652335baa..6aa3722ce6 100644 --- a/core/embed/rust/src/ui/model_tt/component/horizontal_page.rs +++ b/core/embed/rust/src/ui/model_tt/component/horizontal_page.rs @@ -8,7 +8,7 @@ use crate::ui::{ use super::{theme, ScrollBar, Swipe, SwipeDirection}; -const SCROLLBAR_HEIGHT: i16 = 32; +const SCROLLBAR_HEIGHT: i16 = 18; pub struct HorizontalPage { content: T, diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index ec661f2096..5b1fb233f0 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -358,7 +358,7 @@ where impl ComponentMsgObj for AddressDetails where - T: ParagraphStrType, + T: ParagraphStrType + Clone, { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { Ok(CANCELLED.as_obj()) diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index d36d4398bc..9cf93027ee 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -413,7 +413,7 @@ async def show_address( def xpub_title(i: int): result = f"MULTISIG XPUB #{i + 1}\n" - result += " (YOURS)" if i == multisig_index else " (COSIGNER)" + result += "(YOURS)" if i == multisig_index else "(COSIGNER)" return result result = await interact( @@ -430,8 +430,7 @@ async def show_address( "show_address_details", ButtonRequestType.Address, ) - # Can only go back from the address details but corner button returns INFO. - assert result in (INFO, CANCELLED) + assert result is CANCELLED else: result = await interact( diff --git a/tests/device_tests/bitcoin/test_getaddress_show.py b/tests/device_tests/bitcoin/test_getaddress_show.py index cd138a3eb6..31364683d9 100644 --- a/tests/device_tests/bitcoin/test_getaddress_show.py +++ b/tests/device_tests/bitcoin/test_getaddress_show.py @@ -277,7 +277,7 @@ def test_show_multisig_xpubs( def input_flow(): yield # show address layout = client.debug.wait_layout() # TODO: do not need to *wait* now? - assert layout.get_title() == "RECEIVE ADDRESS (MULTISIG)" + assert "RECEIVE ADDRESS (MULTISIG)" in layout.get_title() assert layout.get_content().replace(" ", "") == address client.debug.click(CORNER_BUTTON) @@ -290,16 +290,10 @@ def test_show_multisig_xpubs( assert "Multisig 2 of 3" in layout.text # Three xpub pages with the same testing logic - for xpub_num in range(3): - expected_title = f"MULTISIG XPUB #{xpub_num + 1} " + ( - "(YOURS)" if i == xpub_num else "(COSIGNER)" - ) - + for xpub_num in range(6): client.debug.swipe_left() layout = client.debug.wait_layout() - assert layout.get_title() == expected_title - content = layout.get_content().replace(" ", "") - assert xpubs[xpub_num] in content + assert "MULTISIG XPUB" in layout.get_title() client.debug.click(CORNER_BUTTON) yield # show address