mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-08-02 20:08:31 +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};
|
use super::{theme, Frame, FrameMsg};
|
||||||
|
|
||||||
|
const MAX_XPUBS: usize = 16;
|
||||||
|
|
||||||
pub struct AddressDetails<T> {
|
pub struct AddressDetails<T> {
|
||||||
qr_code: Frame<Qr, T>,
|
qr_code: Frame<Qr, T>,
|
||||||
details: Frame<Paragraphs<ParagraphVecShort<T>>, T>,
|
details: Frame<Paragraphs<ParagraphVecShort<T>>, T>,
|
||||||
xpub_view: Frame<Paragraphs<Paragraph<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,
|
current_page: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AddressDetails<T>
|
impl<T> AddressDetails<T>
|
||||||
where
|
where
|
||||||
T: ParagraphStrType + From<&'static str>,
|
T: ParagraphStrType,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
qr_address: T,
|
qr_address: T,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
account: Option<T>,
|
account: Option<T>,
|
||||||
path: Option<T>,
|
path: Option<T>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
T: From<&'static str>,
|
||||||
|
{
|
||||||
let mut para = ParagraphVecShort::new();
|
let mut para = ParagraphVecShort::new();
|
||||||
if let Some(a) = account {
|
if let Some(a) = account {
|
||||||
para.add(Paragraph::new(&theme::TEXT_NORMAL, "Account:".into()));
|
para.add(Paragraph::new(&theme::TEXT_NORMAL, "Account:".into()));
|
||||||
@ -68,16 +74,50 @@ where
|
|||||||
.with_cancel_button()
|
.with_cancel_button()
|
||||||
.with_border(theme::borders_horizontal_scroll()),
|
.with_border(theme::borders_horizontal_scroll()),
|
||||||
xpubs: Vec::new(),
|
xpubs: Vec::new(),
|
||||||
|
xpub_pages: Vec::new(),
|
||||||
current_page: 0,
|
current_page: 0,
|
||||||
};
|
};
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_xpub(&mut self, title: T, xpub: T) -> Result<(), Error> {
|
pub fn add_xpub(&mut self, title: T, xpub: T) -> Result<(), Error> {
|
||||||
|
self.xpub_pages.push(1u8).map_err(|_| Error::OutOfRange)?;
|
||||||
self.xpubs
|
self.xpubs
|
||||||
.push((title, xpub))
|
.push((title, xpub))
|
||||||
.map_err(|_| Error::OutOfRange)
|
.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>
|
impl<T> Paginate for AddressDetails<T>
|
||||||
@ -85,30 +125,23 @@ where
|
|||||||
T: ParagraphStrType + Clone,
|
T: ParagraphStrType + Clone,
|
||||||
{
|
{
|
||||||
fn page_count(&mut self) -> usize {
|
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) {
|
fn change_page(&mut self, to_page: usize) {
|
||||||
self.current_page = to_page;
|
self.current_page = to_page;
|
||||||
if to_page > 1 {
|
if to_page > 1 {
|
||||||
let i = to_page - 2;
|
let i = to_page - 2;
|
||||||
// Context is needed for updating child so that it can request repaint. In this
|
let (xpub_index, xpub_page) = self.lookup(i);
|
||||||
// case the parent component that handles paging always requests complete
|
self.switch_xpub(xpub_index, xpub_page);
|
||||||
// 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)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for AddressDetails<T>
|
impl<T> Component for AddressDetails<T>
|
||||||
where
|
where
|
||||||
T: ParagraphStrType,
|
T: ParagraphStrType + Clone,
|
||||||
{
|
{
|
||||||
type Msg = ();
|
type Msg = ();
|
||||||
|
|
||||||
@ -116,6 +149,13 @@ where
|
|||||||
self.qr_code.place(bounds);
|
self.qr_code.place(bounds);
|
||||||
self.details.place(bounds);
|
self.details.place(bounds);
|
||||||
self.xpub_view.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
|
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
|
where
|
||||||
F: Fn(&mut T),
|
F: Fn(&mut T) -> R,
|
||||||
{
|
{
|
||||||
self.content.mutate(ctx, |ctx, c| {
|
self.content.mutate(ctx, |ctx, c| {
|
||||||
update_fn(c);
|
let res = update_fn(c);
|
||||||
c.request_complete_repaint(ctx)
|
c.request_complete_repaint(ctx);
|
||||||
|
res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::ui::{
|
|||||||
|
|
||||||
use super::{theme, ScrollBar, Swipe, SwipeDirection};
|
use super::{theme, ScrollBar, Swipe, SwipeDirection};
|
||||||
|
|
||||||
const SCROLLBAR_HEIGHT: i16 = 32;
|
const SCROLLBAR_HEIGHT: i16 = 18;
|
||||||
|
|
||||||
pub struct HorizontalPage<T> {
|
pub struct HorizontalPage<T> {
|
||||||
content: T,
|
content: T,
|
||||||
|
@ -358,7 +358,7 @@ where
|
|||||||
|
|
||||||
impl<T> ComponentMsgObj for AddressDetails<T>
|
impl<T> ComponentMsgObj for AddressDetails<T>
|
||||||
where
|
where
|
||||||
T: ParagraphStrType,
|
T: ParagraphStrType + Clone,
|
||||||
{
|
{
|
||||||
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
Ok(CANCELLED.as_obj())
|
Ok(CANCELLED.as_obj())
|
||||||
|
@ -413,7 +413,7 @@ async def show_address(
|
|||||||
|
|
||||||
def xpub_title(i: int):
|
def xpub_title(i: int):
|
||||||
result = f"MULTISIG XPUB #{i + 1}\n"
|
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
|
return result
|
||||||
|
|
||||||
result = await interact(
|
result = await interact(
|
||||||
@ -430,8 +430,7 @@ async def show_address(
|
|||||||
"show_address_details",
|
"show_address_details",
|
||||||
ButtonRequestType.Address,
|
ButtonRequestType.Address,
|
||||||
)
|
)
|
||||||
# Can only go back from the address details but corner button returns INFO.
|
assert result is CANCELLED
|
||||||
assert result in (INFO, CANCELLED)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
result = await interact(
|
result = await interact(
|
||||||
|
@ -277,7 +277,7 @@ def test_show_multisig_xpubs(
|
|||||||
def input_flow():
|
def input_flow():
|
||||||
yield # show address
|
yield # show address
|
||||||
layout = client.debug.wait_layout() # TODO: do not need to *wait* now?
|
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
|
assert layout.get_content().replace(" ", "") == address
|
||||||
|
|
||||||
client.debug.click(CORNER_BUTTON)
|
client.debug.click(CORNER_BUTTON)
|
||||||
@ -290,16 +290,10 @@ def test_show_multisig_xpubs(
|
|||||||
assert "Multisig 2 of 3" in layout.text
|
assert "Multisig 2 of 3" in layout.text
|
||||||
|
|
||||||
# Three xpub pages with the same testing logic
|
# Three xpub pages with the same testing logic
|
||||||
for xpub_num in range(3):
|
for xpub_num in range(6):
|
||||||
expected_title = f"MULTISIG XPUB #{xpub_num + 1} " + (
|
|
||||||
"(YOURS)" if i == xpub_num else "(COSIGNER)"
|
|
||||||
)
|
|
||||||
|
|
||||||
client.debug.swipe_left()
|
client.debug.swipe_left()
|
||||||
layout = client.debug.wait_layout()
|
layout = client.debug.wait_layout()
|
||||||
assert layout.get_title() == expected_title
|
assert "MULTISIG XPUB" in layout.get_title()
|
||||||
content = layout.get_content().replace(" ", "")
|
|
||||||
assert xpubs[xpub_num] in content
|
|
||||||
|
|
||||||
client.debug.click(CORNER_BUTTON)
|
client.debug.click(CORNER_BUTTON)
|
||||||
yield # show address
|
yield # show address
|
||||||
|
Loading…
Reference in New Issue
Block a user