mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 20:38:10 +00:00
feat(core/ui): GetAddress flow demo
This commit is contained in:
parent
10234787a4
commit
1363495165
@ -75,6 +75,7 @@ pub trait Component {
|
||||
/// dirty flag for it. Any mutation of `T` has to happen through the `mutate`
|
||||
/// accessor, `T` can then request a paint call to be scheduled later by calling
|
||||
/// `EventCtx::request_paint` in its `event` pass.
|
||||
#[derive(Clone)]
|
||||
pub struct Child<T> {
|
||||
component: T,
|
||||
marked_for_paint: bool,
|
||||
|
@ -72,6 +72,7 @@ impl crate::trace::Trace for Image {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlendedImage {
|
||||
bg: Icon,
|
||||
fg: Icon,
|
||||
|
@ -10,6 +10,7 @@ use crate::{
|
||||
|
||||
use super::{text::TextStyle, TextLayout};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Label<'a> {
|
||||
text: TString<'a>,
|
||||
layout: TextLayout,
|
||||
|
@ -25,6 +25,7 @@ const CORNER_RADIUS: u8 = 4;
|
||||
const DARK: Color = Color::rgb(0, 0, 0);
|
||||
const LIGHT: Color = Color::rgb(0xff, 0xff, 0xff);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Qr {
|
||||
text: String<MAX_DATA>,
|
||||
border: i16,
|
||||
|
@ -47,6 +47,7 @@ pub trait ParagraphSource<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Paragraphs<T> {
|
||||
area: Rect,
|
||||
placement: LinearPlacement,
|
||||
@ -335,6 +336,7 @@ impl<'a> Paragraph<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TextLayoutProxy {
|
||||
offset: PageOffset,
|
||||
bounds: Rect,
|
||||
|
@ -7,6 +7,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Timeout {
|
||||
time_ms: u32,
|
||||
timer: Option<TimerToken>,
|
||||
|
@ -95,7 +95,7 @@ impl AddressDetails {
|
||||
// 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);
|
||||
self.xpub_view.update_content(&mut dummy_ctx, |p| {
|
||||
self.xpub_view.update_content(&mut dummy_ctx, |_ctx, p| {
|
||||
p.inner_mut().update(self.xpubs[i].1);
|
||||
let npages = p.page_count();
|
||||
p.change_page(page);
|
||||
|
@ -22,6 +22,7 @@ pub enum ButtonMsg {
|
||||
LongPressed,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Button {
|
||||
area: Rect,
|
||||
touch_expand: Option<Insets>,
|
||||
@ -386,7 +387,7 @@ impl crate::trace::Trace for Button {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
enum State {
|
||||
Initial,
|
||||
Pressed,
|
||||
@ -394,7 +395,7 @@ enum State {
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub enum ButtonContent {
|
||||
Empty,
|
||||
Text(TString<'static>),
|
||||
@ -510,6 +511,7 @@ impl Button {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum CancelConfirmMsg {
|
||||
Cancelled,
|
||||
Confirmed,
|
||||
|
@ -97,6 +97,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IconDialog<U> {
|
||||
image: Child<BlendedImage>,
|
||||
paragraphs: Paragraphs<ParagraphVecShort<'static>>,
|
||||
@ -228,3 +229,5 @@ where
|
||||
t.child("controls", &self.controls);
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> crate::ui::flow::Swipable for IconDialog<U> {}
|
||||
|
@ -15,6 +15,7 @@ use super::{Button, ButtonMsg, CancelInfoConfirmMsg};
|
||||
|
||||
const TITLE_HEIGHT: i16 = 42;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Frame<T> {
|
||||
border: Insets,
|
||||
title: Child<Label<'static>>,
|
||||
@ -109,10 +110,10 @@ where
|
||||
|
||||
pub fn update_content<F, R>(&mut self, ctx: &mut EventCtx, update_fn: F) -> R
|
||||
where
|
||||
F: Fn(&mut T) -> R,
|
||||
F: Fn(&mut EventCtx, &mut T) -> R,
|
||||
{
|
||||
self.content.mutate(ctx, |ctx, c| {
|
||||
let res = update_fn(c);
|
||||
let res = update_fn(ctx, c);
|
||||
c.request_complete_repaint(ctx);
|
||||
res
|
||||
})
|
||||
@ -195,3 +196,16 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> crate::ui::flow::Swipable for Frame<T>
|
||||
where
|
||||
T: Component + crate::ui::flow::Swipable,
|
||||
{
|
||||
fn can_swipe(&self, direction: crate::ui::flow::SwipeDirection) -> bool {
|
||||
self.inner().can_swipe(direction)
|
||||
}
|
||||
|
||||
fn swiped(&mut self, ctx: &mut EventCtx, direction: crate::ui::flow::SwipeDirection) {
|
||||
self.update_content(ctx, |ctx, inner| inner.swiped(ctx, direction))
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ const MENU_SEP_HEIGHT: i16 = 2;
|
||||
type VerticalMenuButtons = Vec<Button, N_ITEMS>;
|
||||
type AreasForSeparators = Vec<Rect, N_SEPS>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VerticalMenu {
|
||||
area: Rect,
|
||||
/// buttons placed vertically from top to bottom
|
||||
@ -150,3 +151,5 @@ impl crate::trace::Trace for VerticalMenu {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ui::flow::Swipable for VerticalMenu {}
|
||||
|
187
core/embed/rust/src/ui/model_mercury/flow/get_address.rs
Normal file
187
core/embed/rust/src/ui/model_mercury/flow/get_address.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use crate::{
|
||||
error,
|
||||
ui::{
|
||||
component::{
|
||||
image::BlendedImage,
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
Qr, Timeout,
|
||||
},
|
||||
flow::{
|
||||
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeDirection,
|
||||
SwipeFlow, SwipePage,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::{Frame, FrameMsg, IconDialog, VerticalMenu, VerticalMenuChoiceMsg},
|
||||
theme,
|
||||
};
|
||||
|
||||
const LONGSTRING: &'static str = "https://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKohttps://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKo";
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
|
||||
pub enum GetAddress {
|
||||
Address,
|
||||
Menu,
|
||||
QrCode,
|
||||
AccountInfo,
|
||||
Cancel,
|
||||
Success,
|
||||
}
|
||||
|
||||
impl FlowState for GetAddress {
|
||||
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
|
||||
match (self, direction) {
|
||||
(GetAddress::Address, SwipeDirection::Left) => {
|
||||
Decision::Goto(GetAddress::Menu, direction)
|
||||
}
|
||||
(GetAddress::Address, SwipeDirection::Up) => {
|
||||
Decision::Goto(GetAddress::Success, direction)
|
||||
}
|
||||
(GetAddress::Menu, SwipeDirection::Right) => {
|
||||
Decision::Goto(GetAddress::Address, direction)
|
||||
}
|
||||
(GetAddress::QrCode, SwipeDirection::Right) => {
|
||||
Decision::Goto(GetAddress::Menu, direction)
|
||||
}
|
||||
(GetAddress::AccountInfo, SwipeDirection::Right) => {
|
||||
Decision::Goto(GetAddress::Menu, direction)
|
||||
}
|
||||
(GetAddress::Cancel, SwipeDirection::Up) => Decision::Return(FlowMsg::Cancelled),
|
||||
_ => Decision::Nothing,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
|
||||
match (self, msg) {
|
||||
(GetAddress::Address, FlowMsg::Info) => {
|
||||
Decision::Goto(GetAddress::Menu, SwipeDirection::Left)
|
||||
}
|
||||
|
||||
(GetAddress::Menu, FlowMsg::Choice(0)) => {
|
||||
Decision::Goto(GetAddress::QrCode, SwipeDirection::Left)
|
||||
}
|
||||
|
||||
(GetAddress::Menu, FlowMsg::Choice(1)) => {
|
||||
Decision::Goto(GetAddress::AccountInfo, SwipeDirection::Left)
|
||||
}
|
||||
|
||||
(GetAddress::Menu, FlowMsg::Choice(2)) => {
|
||||
Decision::Goto(GetAddress::Cancel, SwipeDirection::Left)
|
||||
}
|
||||
|
||||
(GetAddress::Menu, FlowMsg::Cancelled) => {
|
||||
Decision::Goto(GetAddress::Address, SwipeDirection::Right)
|
||||
}
|
||||
|
||||
(GetAddress::QrCode, FlowMsg::Cancelled) => {
|
||||
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
|
||||
}
|
||||
|
||||
(GetAddress::AccountInfo, FlowMsg::Cancelled) => {
|
||||
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
|
||||
}
|
||||
|
||||
(GetAddress::Cancel, FlowMsg::Cancelled) => {
|
||||
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
|
||||
}
|
||||
|
||||
(GetAddress::Success, _) => Decision::Return(FlowMsg::Confirmed),
|
||||
_ => Decision::Nothing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::{
|
||||
micropython::{buffer::StrBuffer, map::Map, obj::Obj, util},
|
||||
ui::layout::obj::LayoutObj,
|
||||
};
|
||||
|
||||
pub extern "C" fn new_get_address(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, GetAddress::new) }
|
||||
}
|
||||
|
||||
impl GetAddress {
|
||||
fn new(_args: &[Obj], _kwargs: &Map) -> Result<Obj, error::Error> {
|
||||
let store = flow_store()
|
||||
.add(
|
||||
Frame::left_aligned(
|
||||
"Receive".into(),
|
||||
SwipePage::vertical(Paragraphs::new(Paragraph::new(
|
||||
&theme::TEXT_MONO,
|
||||
StrBuffer::from(LONGSTRING),
|
||||
))),
|
||||
)
|
||||
.with_subtitle("address".into())
|
||||
.with_info_button(),
|
||||
|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info),
|
||||
)?
|
||||
.add(
|
||||
Frame::left_aligned(
|
||||
"".into(),
|
||||
VerticalMenu::context_menu([
|
||||
("Address QR code", theme::ICON_QR_CODE),
|
||||
("Account info", theme::ICON_CHEVRON_RIGHT),
|
||||
("Cancel trans.", theme::ICON_CANCEL),
|
||||
]),
|
||||
)
|
||||
.with_cancel_button(),
|
||||
|msg| match msg {
|
||||
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => {
|
||||
Some(FlowMsg::Choice(i))
|
||||
}
|
||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||
},
|
||||
)?
|
||||
.add(
|
||||
Frame::left_aligned(
|
||||
"Receive address".into(),
|
||||
IgnoreSwipe::new(Qr::new(
|
||||
"https://youtu.be/iFkEs4GNgfc?si=Jl4UZSIAYGVcLQKo",
|
||||
true,
|
||||
)?),
|
||||
)
|
||||
.with_cancel_button(),
|
||||
|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled),
|
||||
)?
|
||||
.add(
|
||||
Frame::left_aligned(
|
||||
"Account info".into(),
|
||||
SwipePage::vertical(Paragraphs::new(Paragraph::new(
|
||||
&theme::TEXT_NORMAL,
|
||||
StrBuffer::from("taproot xp"),
|
||||
))),
|
||||
)
|
||||
.with_cancel_button(),
|
||||
|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled),
|
||||
)?
|
||||
.add(
|
||||
Frame::left_aligned(
|
||||
"Cancel receive".into(),
|
||||
SwipePage::vertical(Paragraphs::new(Paragraph::new(
|
||||
&theme::TEXT_NORMAL,
|
||||
StrBuffer::from("O rly?"),
|
||||
))),
|
||||
)
|
||||
.with_cancel_button(),
|
||||
|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled),
|
||||
)?
|
||||
.add(
|
||||
IconDialog::new(
|
||||
BlendedImage::new(
|
||||
theme::IMAGE_BG_CIRCLE,
|
||||
theme::IMAGE_FG_WARN,
|
||||
theme::SUCCESS_COLOR,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
),
|
||||
StrBuffer::from("Confirmed"),
|
||||
Timeout::new(100),
|
||||
),
|
||||
|_| Some(FlowMsg::Confirmed),
|
||||
)?;
|
||||
let res = SwipeFlow::new(GetAddress::Address, store)?;
|
||||
Ok(LayoutObj::new(res)?.into())
|
||||
}
|
||||
}
|
3
core/embed/rust/src/ui/model_mercury/flow/mod.rs
Normal file
3
core/embed/rust/src/ui/model_mercury/flow/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod get_address;
|
||||
|
||||
pub use get_address::GetAddress;
|
@ -56,7 +56,7 @@ use super::{
|
||||
SelectWordCount, SelectWordCountMsg, SelectWordMsg, ShareWords, SimplePage, Slip39Input,
|
||||
VerticalMenu, VerticalMenuChoiceMsg,
|
||||
},
|
||||
theme,
|
||||
flow, theme,
|
||||
};
|
||||
|
||||
impl TryFrom<CancelConfirmMsg> for Obj {
|
||||
@ -2152,6 +2152,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def show_wait_text(message: str, /) -> LayoutObj[None]:
|
||||
/// """Show single-line text in the middle of the screen."""
|
||||
Qstr::MP_QSTR_show_wait_text => obj_fn_1!(new_show_wait_text).as_obj(),
|
||||
|
||||
/// def flow_get_address() -> LayoutObj[UiResult]:
|
||||
/// """Get address / receive funds."""
|
||||
Qstr::MP_QSTR_flow_get_address => obj_fn_kw!(0, flow::get_address::new_get_address).as_obj(),
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -523,6 +523,11 @@ def confirm_firmware_update(
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
def show_wait_text(message: str, /) -> LayoutObj[None]:
|
||||
"""Show single-line text in the middle of the screen."""
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
def flow_get_address() -> LayoutObj[UiResult]:
|
||||
"""Get address / receive funds."""
|
||||
CONFIRMED: UiResult
|
||||
CANCELLED: UiResult
|
||||
INFO: UiResult
|
||||
|
@ -278,6 +278,17 @@ async def confirm_action(
|
||||
)
|
||||
|
||||
|
||||
async def flow_demo() -> None:
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(trezorui2.flow_get_address()),
|
||||
"get_address",
|
||||
BR_TYPE_OTHER,
|
||||
),
|
||||
ActionCancelled,
|
||||
)
|
||||
|
||||
|
||||
async def confirm_single(
|
||||
br_type: str,
|
||||
title: str,
|
||||
|
Loading…
Reference in New Issue
Block a user