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:
parent
a4a7cb9e6c
commit
c9a5ef7cfa
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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())
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user