1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-01 19:38:33 +00:00

fix(core/ui): fix receive dialogs for long xpubs

This commit is contained in:
Martin Milata 2023-03-14 21:16:56 +01:00
parent a4a7cb9e6c
commit c9a5ef7cfa
6 changed files with 67 additions and 33 deletions

View File

@ -15,24 +15,30 @@ use crate::{
use super::{theme, Frame, FrameMsg};
const MAX_XPUBS: usize = 16;
pub struct AddressDetails<T> {
qr_code: Frame<Qr, T>,
details: Frame<Paragraphs<ParagraphVecShort<T>>, T>,
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>,
xpubs: Vec<(T, T), 16>,
xpubs: Vec<(T, T), MAX_XPUBS>,
xpub_pages: Vec<u8, MAX_XPUBS>,
current_page: usize,
}
impl<T> AddressDetails<T>
where
T: ParagraphStrType + From<&'static str>,
T: ParagraphStrType,
{
pub fn new(
qr_address: T,
case_sensitive: bool,
account: Option<T>,
path: Option<T>,
) -> Result<Self, Error> {
) -> Result<Self, Error>
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<T> Paginate for AddressDetails<T>
@ -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<T> Component for AddressDetails<T>
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
}

View File

@ -81,13 +81,14 @@ where
})
}
pub fn update_content<F>(&mut self, ctx: &mut EventCtx, update_fn: F)
pub fn update_content<F, R>(&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
})
}
}

View File

@ -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<T> {
content: T,

View File

@ -358,7 +358,7 @@ where
impl<T> ComponentMsgObj for AddressDetails<T>
where
T: ParagraphStrType,
T: ParagraphStrType + Clone,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
Ok(CANCELLED.as_obj())

View File

@ -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(

View File

@ -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