parent
a7cefce84c
commit
0acf94099a
@ -0,0 +1,270 @@
|
||||
use heapless::Vec;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::buffer::StrBuffer,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||
Child, Component, Event, EventCtx, Pad, Paginate, Qr,
|
||||
},
|
||||
geometry::Rect,
|
||||
model_tr::theme,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{ButtonController, ButtonControllerMsg, ButtonDetails, ButtonLayout, ButtonPos, Frame};
|
||||
|
||||
const MAX_XPUBS: usize = 16;
|
||||
|
||||
pub enum AddressDetailsMsg {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
pub struct AddressDetails {
|
||||
qr_code: Qr,
|
||||
details_view: Paragraphs<ParagraphVecShort<StrBuffer>>,
|
||||
xpub_view: Frame<Paragraphs<Paragraph<StrBuffer>>>,
|
||||
xpubs: Vec<(StrBuffer, StrBuffer), MAX_XPUBS>,
|
||||
current_page: usize,
|
||||
current_subpage: usize,
|
||||
area: Rect,
|
||||
pad: Pad,
|
||||
buttons: Child<ButtonController>,
|
||||
}
|
||||
|
||||
impl AddressDetails {
|
||||
pub fn new(
|
||||
qr_address: StrBuffer,
|
||||
case_sensitive: bool,
|
||||
account: Option<StrBuffer>,
|
||||
path: Option<StrBuffer>,
|
||||
) -> Result<Self, Error> {
|
||||
let qr_code = Qr::new(qr_address, case_sensitive)?.with_border(3);
|
||||
let details_view = {
|
||||
let mut para = ParagraphVecShort::new();
|
||||
if let Some(account) = account {
|
||||
para.add(Paragraph::new(&theme::TEXT_BOLD, "Account:".into()));
|
||||
para.add(Paragraph::new(&theme::TEXT_MONO, account));
|
||||
}
|
||||
if let Some(path) = path {
|
||||
para.add(Paragraph::new(&theme::TEXT_BOLD, "Derivation path:".into()));
|
||||
para.add(Paragraph::new(&theme::TEXT_MONO, path));
|
||||
}
|
||||
Paragraphs::new(para)
|
||||
};
|
||||
let xpub_view = Frame::new(
|
||||
"".into(),
|
||||
Paragraph::new(&theme::TEXT_MONO_DATA, "".into()).into_paragraphs(),
|
||||
);
|
||||
|
||||
let result = Self {
|
||||
qr_code,
|
||||
details_view,
|
||||
xpub_view,
|
||||
xpubs: Vec::new(),
|
||||
area: Rect::zero(),
|
||||
current_page: 0,
|
||||
current_subpage: 0,
|
||||
pad: Pad::with_background(theme::BG).with_clear(),
|
||||
buttons: Child::new(ButtonController::new(ButtonLayout::arrow_none_arrow())),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn add_xpub(&mut self, title: StrBuffer, xpub: StrBuffer) -> Result<(), Error> {
|
||||
self.xpubs
|
||||
.push((title, xpub))
|
||||
.map_err(|_| Error::OutOfRange)
|
||||
}
|
||||
|
||||
fn is_in_subpage(&self) -> bool {
|
||||
self.current_subpage > 0
|
||||
}
|
||||
|
||||
fn is_xpub_page(&self) -> bool {
|
||||
self.current_page > 1
|
||||
}
|
||||
|
||||
fn is_last_page(&self) -> bool {
|
||||
self.current_page == self.page_count() - 1
|
||||
}
|
||||
|
||||
fn is_last_subpage(&mut self) -> bool {
|
||||
self.current_subpage == self.subpages_in_current_page() - 1
|
||||
}
|
||||
|
||||
fn subpages_in_current_page(&mut self) -> usize {
|
||||
if self.is_xpub_page() {
|
||||
self.xpub_view.inner_mut().page_count()
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
// Button layout for the current page.
|
||||
// Normally there are arrows everywhere, apart from the right side of the last
|
||||
// page. On xpub pages there is SHOW MORE middle button when it cannot fit
|
||||
// one page. On xpub subpages there are wide arrows to scroll.
|
||||
fn get_button_layout(&mut self) -> ButtonLayout {
|
||||
let (left, middle, right) = if self.is_in_subpage() {
|
||||
let left = Some(ButtonDetails::up_arrow_icon_wide());
|
||||
let right = if self.is_last_subpage() {
|
||||
None
|
||||
} else {
|
||||
Some(ButtonDetails::down_arrow_icon_wide())
|
||||
};
|
||||
(left, None, right)
|
||||
} else {
|
||||
let left = Some(ButtonDetails::left_arrow_icon());
|
||||
let middle = if self.is_xpub_page() && self.subpages_in_current_page() > 1 {
|
||||
Some(ButtonDetails::armed_text("SHOW MORE".into()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let right = if self.is_last_page() {
|
||||
None
|
||||
} else {
|
||||
Some(ButtonDetails::right_arrow_icon())
|
||||
};
|
||||
(left, middle, right)
|
||||
};
|
||||
ButtonLayout::new(left, middle, right)
|
||||
}
|
||||
|
||||
/// Reflecting the current page in the buttons.
|
||||
fn update_buttons(&mut self, ctx: &mut EventCtx) {
|
||||
let btn_layout = self.get_button_layout();
|
||||
self.buttons.mutate(ctx, |_ctx, buttons| {
|
||||
buttons.set(btn_layout);
|
||||
});
|
||||
}
|
||||
|
||||
fn page_count(&self) -> usize {
|
||||
2 + self.xpubs.len()
|
||||
}
|
||||
|
||||
fn fill_xpub_page(&mut self, ctx: &mut EventCtx) {
|
||||
let i = self.current_page - 2;
|
||||
self.xpub_view.update_title(ctx, self.xpubs[i].0);
|
||||
self.xpub_view.update_content(ctx, |p| {
|
||||
p.inner_mut().update(self.xpubs[i].1);
|
||||
p.change_page(0)
|
||||
});
|
||||
}
|
||||
|
||||
fn change_page(&mut self, ctx: &mut EventCtx) {
|
||||
if self.is_xpub_page() {
|
||||
self.fill_xpub_page(ctx);
|
||||
}
|
||||
self.pad.clear();
|
||||
self.current_subpage = 0;
|
||||
}
|
||||
|
||||
fn change_subpage(&mut self, ctx: &mut EventCtx) {
|
||||
if self.is_xpub_page() {
|
||||
self.xpub_view
|
||||
.update_content(ctx, |p| p.change_page(self.current_subpage));
|
||||
self.pad.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for AddressDetails {
|
||||
type Msg = AddressDetailsMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
// QR code is being placed on the whole bounds, so it can be as big as possible
|
||||
// (it will not collide with the buttons, they are narrow and on the sides).
|
||||
// Therefore, also placing pad on the whole bounds.
|
||||
self.qr_code.place(bounds);
|
||||
self.pad.place(bounds);
|
||||
let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT);
|
||||
self.details_view.place(content_area);
|
||||
self.xpub_view.place(content_area);
|
||||
self.buttons.place(button_area);
|
||||
self.area = content_area;
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
// Possibly update the components that have e.g. marquee
|
||||
match self.current_page {
|
||||
0 => self.qr_code.event(ctx, event),
|
||||
1 => self.details_view.event(ctx, event),
|
||||
_ => self.xpub_view.event(ctx, event),
|
||||
};
|
||||
|
||||
let button_event = self.buttons.event(ctx, event);
|
||||
if let Some(ButtonControllerMsg::Triggered(button)) = button_event {
|
||||
if self.is_in_subpage() {
|
||||
match button {
|
||||
ButtonPos::Left => {
|
||||
// Going back
|
||||
self.current_subpage -= 1;
|
||||
}
|
||||
ButtonPos::Right => {
|
||||
// Going next
|
||||
self.current_subpage += 1;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
self.change_subpage(ctx);
|
||||
self.update_buttons(ctx);
|
||||
return None;
|
||||
} else {
|
||||
match button {
|
||||
ButtonPos::Left => {
|
||||
// Cancelling or going back
|
||||
if self.current_page == 0 {
|
||||
return Some(AddressDetailsMsg::Cancelled);
|
||||
}
|
||||
self.current_page -= 1;
|
||||
self.change_page(ctx);
|
||||
}
|
||||
ButtonPos::Right => {
|
||||
// Going to the next page
|
||||
self.current_page += 1;
|
||||
self.change_page(ctx);
|
||||
}
|
||||
ButtonPos::Middle => {
|
||||
// Going into subpage
|
||||
self.current_subpage = 1;
|
||||
self.change_subpage(ctx);
|
||||
}
|
||||
}
|
||||
self.update_buttons(ctx);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.pad.paint();
|
||||
self.buttons.paint();
|
||||
match self.current_page {
|
||||
0 => self.qr_code.paint(),
|
||||
1 => self.details_view.paint(),
|
||||
_ => self.xpub_view.paint(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_bounds")]
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
sink(self.area)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for AddressDetails {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("AddressDetails");
|
||||
match self.current_page {
|
||||
0 => self.qr_code.trace(t),
|
||||
1 => self.details_view.trace(t),
|
||||
_ => self.xpub_view.trace(t),
|
||||
}
|
||||
t.close();
|
||||
}
|
||||
}
|
Loading…
Reference in new issue