1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-03 20:38:16 +00:00

xmr.ui: improves UX of the monero app (#95)

xmr.ui: improves UX of the monero app
This commit is contained in:
Tomas Susanka 2019-04-23 14:00:05 +02:00 committed by GitHub
commit 37a597a444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 88 additions and 57 deletions

View File

@ -237,7 +237,7 @@ void ui_screen_install_confirm_newvendor(const vendor_header *const vhdr,
void ui_screen_install(void) { void ui_screen_install(void) {
display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE);
display_loader(0, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install, display_loader(0, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install,
sizeof(toi_icon_install), COLOR_BLACK); sizeof(toi_icon_install), COLOR_BLACK, 0);
display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24, display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24,
"Installing firmware", -1, FONT_NORMAL, COLOR_BLACK, "Installing firmware", -1, FONT_NORMAL, COLOR_BLACK,
COLOR_WHITE); COLOR_WHITE);
@ -245,12 +245,12 @@ void ui_screen_install(void) {
void ui_screen_install_progress_erase(int pos, int len) { void ui_screen_install_progress_erase(int pos, int len) {
display_loader(250 * pos / len, -20, COLOR_BL_PROCESS, COLOR_WHITE, display_loader(250 * pos / len, -20, COLOR_BL_PROCESS, COLOR_WHITE,
toi_icon_install, sizeof(toi_icon_install), COLOR_BLACK); toi_icon_install, sizeof(toi_icon_install), COLOR_BLACK, 0);
} }
void ui_screen_install_progress_upload(int pos) { void ui_screen_install_progress_upload(int pos) {
display_loader(pos, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install, display_loader(pos, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install,
sizeof(toi_icon_install), COLOR_BLACK); sizeof(toi_icon_install), COLOR_BLACK, 0);
} }
// wipe UI // wipe UI
@ -275,14 +275,14 @@ void ui_screen_wipe_confirm(void) {
void ui_screen_wipe(void) { void ui_screen_wipe(void) {
display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE);
display_loader(0, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_wipe, display_loader(0, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_wipe,
sizeof(toi_icon_wipe), COLOR_BLACK); sizeof(toi_icon_wipe), COLOR_BLACK, 0);
display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24, "Wiping device", -1, display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24, "Wiping device", -1,
FONT_NORMAL, COLOR_BLACK, COLOR_WHITE); FONT_NORMAL, COLOR_BLACK, COLOR_WHITE);
} }
void ui_screen_wipe_progress(int pos, int len) { void ui_screen_wipe_progress(int pos, int len) {
display_loader(1000 * pos / len, -20, COLOR_BL_PROCESS, COLOR_WHITE, display_loader(1000 * pos / len, -20, COLOR_BL_PROCESS, COLOR_WHITE,
toi_icon_wipe, sizeof(toi_icon_wipe), COLOR_BLACK); toi_icon_wipe, sizeof(toi_icon_wipe), COLOR_BLACK, 0);
} }
// done UI // done UI
@ -301,7 +301,7 @@ void ui_screen_done(int restart_seconds, secbool full_redraw) {
display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE);
} }
display_loader(1000, -20, COLOR_BL_DONE, COLOR_WHITE, toi_icon_done, display_loader(1000, -20, COLOR_BL_DONE, COLOR_WHITE, toi_icon_done,
sizeof(toi_icon_done), COLOR_BLACK); sizeof(toi_icon_done), COLOR_BLACK, 0);
if (secfalse == full_redraw) { if (secfalse == full_redraw) {
display_bar(0, DISPLAY_RESY - 24 - 18, 240, 23, COLOR_WHITE); display_bar(0, DISPLAY_RESY - 24 - 18, 240, 23, COLOR_WHITE);
} }
@ -314,7 +314,7 @@ void ui_screen_done(int restart_seconds, secbool full_redraw) {
void ui_screen_fail(void) { void ui_screen_fail(void) {
display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE);
display_loader(1000, -20, COLOR_BL_FAIL, COLOR_WHITE, toi_icon_fail, display_loader(1000, -20, COLOR_BL_FAIL, COLOR_WHITE, toi_icon_fail,
sizeof(toi_icon_fail), COLOR_BLACK); sizeof(toi_icon_fail), COLOR_BLACK, 0);
display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24, display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24,
"Failed! Please, reconnect.", -1, FONT_NORMAL, "Failed! Please, reconnect.", -1, FONT_NORMAL,
COLOR_BLACK, COLOR_WHITE); COLOR_BLACK, COLOR_WHITE);

View File

@ -318,7 +318,7 @@ static void inflate_callback_loader(uint8_t byte, uint32_t pos,
void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor, void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor,
uint16_t bgcolor, const uint8_t *icon, uint32_t iconlen, uint16_t bgcolor, const uint8_t *icon, uint32_t iconlen,
uint16_t iconfgcolor) { uint16_t iconfgcolor, uint16_t slice_span) {
#if TREZOR_MODEL == T #if TREZOR_MODEL == T
uint16_t colortable[16], iconcolortable[16]; uint16_t colortable[16], iconcolortable[16];
set_color_table(colortable, fgcolor, bgcolor); set_color_table(colortable, fgcolor, bgcolor);
@ -378,7 +378,7 @@ void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor,
PIXELDATA(iconcolortable[c]); PIXELDATA(iconcolortable[c]);
} else { } else {
uint8_t c; uint8_t c;
if (progress > a) { if (progress > a && (slice_span == 0 || progress < (a + slice_span))) {
c = (img_loader[my][mx] & 0x00F0) >> 4; c = (img_loader[my][mx] & 0x00F0) >> 4;
} else { } else {
c = img_loader[my][mx] & 0x000F; c = img_loader[my][mx] & 0x000F;

View File

@ -84,7 +84,7 @@ void display_icon(int x, int y, int w, int h, const void *data, int datalen,
uint16_t fgcolor, uint16_t bgcolor); uint16_t fgcolor, uint16_t bgcolor);
void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor, void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor,
uint16_t bgcolor, const uint8_t *icon, uint32_t iconlen, uint16_t bgcolor, const uint8_t *icon, uint32_t iconlen,
uint16_t iconfgcolor); uint16_t iconfgcolor, uint16_t slice_span);
#ifndef TREZOR_PRINT_DISABLE #ifndef TREZOR_PRINT_DISABLE
void display_print_color(uint16_t fgcolor, uint16_t bgcolor); void display_print_color(uint16_t fgcolor, uint16_t bgcolor);

View File

@ -198,7 +198,7 @@ STATIC mp_obj_t mod_trezorui_Display_icon(size_t n_args, const mp_obj_t *args) {
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_icon_obj, 6, 6, STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_icon_obj, 6, 6,
mod_trezorui_Display_icon); mod_trezorui_Display_icon);
/// def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, /// def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int,
/// icon: bytes = None, iconfgcolor: int = None) -> None: /// icon: bytes = None, iconfgcolor: int = None, slice_span: int = None) -> None:
/// ''' /// '''
/// Renders a rotating loader graphic. /// Renders a rotating loader graphic.
/// Progress determines its position (0-1000), fgcolor is used as foreground /// Progress determines its position (0-1000), fgcolor is used as foreground
@ -206,6 +206,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_icon_obj, 6, 6,
/// icon is drawn in the middle using the color specified in iconfgcolor. /// icon is drawn in the middle using the color specified in iconfgcolor.
/// Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels /// Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels
/// size. /// size.
/// If slice_span is defined the progress is sliced to emulate indeterminate
/// loader.
/// ''' /// '''
STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args, STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args,
const mp_obj_t *args) { const mp_obj_t *args) {
@ -213,8 +215,11 @@ STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args,
mp_int_t yoffset = mp_obj_get_int(args[2]); mp_int_t yoffset = mp_obj_get_int(args[2]);
mp_int_t fgcolor = mp_obj_get_int(args[3]); mp_int_t fgcolor = mp_obj_get_int(args[3]);
mp_int_t bgcolor = mp_obj_get_int(args[4]); mp_int_t bgcolor = mp_obj_get_int(args[4]);
if (n_args > 5) { // icon provided mp_buffer_info_t icon = {.buf = NULL, .len=0};
mp_buffer_info_t icon; uint16_t iconfgcolor = 0;
uint16_t slice_span = 0;
if (n_args > 5 && args[5] != mp_const_none) { // icon provided
mp_get_buffer_raise(args[5], &icon, MP_BUFFER_READ); mp_get_buffer_raise(args[5], &icon, MP_BUFFER_READ);
const uint8_t *data = icon.buf; const uint8_t *data = icon.buf;
if (icon.len < 8 || memcmp(data, "TOIg", 4) != 0) { if (icon.len < 8 || memcmp(data, "TOIg", 4) != 0) {
@ -229,21 +234,25 @@ STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args,
if (datalen != icon.len - 12) { if (datalen != icon.len - 12) {
mp_raise_ValueError("Invalid size of data"); mp_raise_ValueError("Invalid size of data");
} }
uint16_t iconfgcolor;
if (n_args > 6) { // icon color provided if (n_args > 6) { // icon color provided
iconfgcolor = mp_obj_get_int(args[6]); iconfgcolor = mp_obj_get_int(args[6]);
} else { } else {
iconfgcolor = ~bgcolor; // invert iconfgcolor = ~bgcolor; // invert
} }
display_loader(progress, yoffset, fgcolor, bgcolor, icon.buf, icon.len,
iconfgcolor);
} else {
display_loader(progress, yoffset, fgcolor, bgcolor, NULL, 0, 0);
} }
if (n_args > 7 && args[7] != mp_const_none) { // slice span provided
slice_span = mp_obj_get_int(args[7]);
}
display_loader(progress, yoffset, fgcolor, bgcolor, icon.buf, icon.len,
iconfgcolor, slice_span);
return mp_const_none; return mp_const_none;
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_loader_obj, 5, STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_loader_obj, 5,
7, mod_trezorui_Display_loader); 8, mod_trezorui_Display_loader);
/// def print(self, text: str) -> None: /// def print(self, text: str) -> None:
/// ''' /// '''

View File

@ -51,12 +51,14 @@ class Display:
The icon needs to be in TREZOR Optimized Image Format (TOIF) - gray-scale mode. The icon needs to be in TREZOR Optimized Image Format (TOIF) - gray-scale mode.
''' '''
def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, icon: bytes = None, iconfgcolor: int = None) -> None: def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, icon: bytes = None, iconfgcolor: int = None, slice_span: int = None) -> None:
''' '''
Renders a rotating loader graphic. Renders a rotating loader graphic.
Progress determines its position (0-1000), fgcolor is used as foreground color, bgcolor as background. Progress determines its position (0-1000), fgcolor is used as foreground color, bgcolor as background.
When icon and iconfgcolor are provided, an icon is drawn in the middle using the color specified in iconfgcolor. When icon and iconfgcolor are provided, an icon is drawn in the middle using the color specified in iconfgcolor.
Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels size. Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels size.
If slice_span is defined the progress is sliced to emulate indeterminate
loader.
''' '''
def print(self, text: str) -> None: def print(self, text: str) -> None:

View File

@ -42,7 +42,7 @@ async def require_confirm_tx_key(ctx, export_key=False):
return await require_confirm(ctx, content, ButtonRequestType.SignTx) return await require_confirm(ctx, content, ButtonRequestType.SignTx)
async def require_confirm_transaction(ctx, tsx_data, network_type): async def require_confirm_transaction(ctx, state, tsx_data, network_type):
""" """
Ask for confirmation from user. Ask for confirmation from user.
""" """
@ -70,10 +70,7 @@ async def require_confirm_transaction(ctx, tsx_data, network_type):
await _require_confirm_payment_id(ctx, tsx_data.payment_id) await _require_confirm_payment_id(ctx, tsx_data.payment_id)
await _require_confirm_fee(ctx, tsx_data.fee) await _require_confirm_fee(ctx, tsx_data.fee)
await transaction_step(state, 0)
text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE)
text.normal("Signing...")
text.render()
async def _require_confirm_output(ctx, dst, network_type, payment_id): async def _require_confirm_output(ctx, dst, network_type, payment_id):
@ -119,46 +116,68 @@ async def _require_confirm_fee(ctx, fee):
await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
@ui.layout @ui.layout_no_slide
async def transaction_step(ctx, step, sub_step=None, sub_step_total=None): async def transaction_step(state, step, sub_step=None):
info = [] info = []
if step == 100: if step == 0:
info = ["Processing inputs", "%d/%d" % (sub_step + 1, sub_step_total)] info = ["Signing..."]
elif step == 100:
info = ["Processing inputs", "%d/%d" % (sub_step + 1, state.input_count)]
elif step == 200: elif step == 200:
info = ["Sorting"] info = ["Sorting..."]
elif step == 300: elif step == 300:
info = [ info = ["Hashing inputs", "%d/%d" % (sub_step + 1, state.input_count)]
"Processing inputs", elif step == 350:
"phase 2", info = ["Processing..."]
"%d/%d" % (sub_step + 1, sub_step_total),
]
elif step == 400: elif step == 400:
info = ["Processing outputs", "%d/%d" % (sub_step + 1, sub_step_total)] info = ["Processing outputs", "%d/%d" % (sub_step + 1, state.output_count)]
elif step == 500: elif step == 500:
info = ["Postprocessing..."] info = ["Postprocessing..."]
elif step == 600: elif step == 600:
info = ["Signing inputs", "%d/%d" % (sub_step + 1, sub_step_total)] info = ["Signing inputs", "%d/%d" % (sub_step + 1, state.input_count)]
else: else:
info = ["Processing..."] info = ["Processing..."]
state.progress_cur += 1
ui.display.clear()
text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE) text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE)
text.normal(*info)
text.render() text.render()
p = int(1000.0 * state.progress_cur / state.progress_total)
ui.display.loader(p, -4, ui.WHITE, ui.BG)
ui.display.text_center(ui.WIDTH // 2, 210, info[0], ui.NORMAL, ui.FG, ui.BG)
if len(info) > 1:
ui.display.text_center(ui.WIDTH // 2, 235, info[1], ui.NORMAL, ui.FG, ui.BG)
ui.display.refresh()
@ui.layout
@ui.layout_no_slide
async def keyimage_sync_step(ctx, current, total_num): async def keyimage_sync_step(ctx, current, total_num):
if current is None: if current is None:
return return
ui.display.clear()
text = Text("Syncing", ui.ICON_SEND, icon_color=ui.BLUE) text = Text("Syncing", ui.ICON_SEND, icon_color=ui.BLUE)
text.normal("%d/%d" % (current + 1, total_num))
text.render() text.render()
p = (int(1000.0 * (current + 1) / total_num)) if total_num > 0 else 0
ui.display.loader(p, 18, ui.WHITE, ui.BG)
ui.display.refresh()
@ui.layout
@ui.layout_no_slide
async def live_refresh_step(ctx, current): async def live_refresh_step(ctx, current):
if current is None: if current is None:
return return
ui.display.clear()
text = Text("Refreshing", ui.ICON_SEND, icon_color=ui.BLUE) text = Text("Refreshing", ui.ICON_SEND, icon_color=ui.BLUE)
text.normal("%d" % current)
text.render() text.render()
step = 6
p = int(1000.0 * (current / step)) % 1000
if p == 0 and current > 0:
p = 1000
ui.display.loader(p, 18, ui.WHITE, ui.BG, None, 0, 1000 // step)
ui.display.text_center(ui.WIDTH // 2, 145, "%d" % current, ui.NORMAL, ui.FG, ui.BG)
ui.display.refresh()

View File

@ -68,6 +68,9 @@ class State:
self.input_count = 0 self.input_count = 0
self.output_count = 0 self.output_count = 0
self.progress_total = 0
self.progress_cur = 0
self.output_change = None self.output_change = None
self.fee = 0 self.fee = 0

View File

@ -36,16 +36,19 @@ async def init_transaction(
state.mem_trace(1) state.mem_trace(1)
state.input_count = tsx_data.num_inputs
state.output_count = len(tsx_data.outputs)
state.progress_total = 4 + 3 * state.input_count + state.output_count
state.progress_cur = 0
# Ask for confirmation # Ask for confirmation
await confirms.require_confirm_transaction( await confirms.require_confirm_transaction(
state.ctx, tsx_data, state.creds.network_type state.ctx, state, tsx_data, state.creds.network_type
) )
gc.collect() gc.collect()
state.mem_trace(3) state.mem_trace(3)
# Basic transaction parameters # Basic transaction parameters
state.input_count = tsx_data.num_inputs
state.output_count = len(tsx_data.outputs)
state.output_change = tsx_data.change_dts state.output_change = tsx_data.change_dts
state.mixin = tsx_data.mixin state.mixin = tsx_data.mixin
state.fee = tsx_data.fee state.fee = tsx_data.fee

View File

@ -32,9 +32,7 @@ async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):
state.current_input_index += 1 state.current_input_index += 1
await confirms.transaction_step( await confirms.transaction_step(state, state.STEP_INP, state.current_input_index)
state.ctx, state.STEP_INP, state.current_input_index, state.input_count
)
if state.current_input_index >= state.input_count: if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs") raise ValueError("Too many inputs")

View File

@ -21,7 +21,7 @@ async def tsx_inputs_permutation(state: State, permutation: list):
MoneroTransactionInputsPermutationAck, MoneroTransactionInputsPermutationAck,
) )
await transaction_step(state.ctx, state.STEP_PERM) await transaction_step(state, state.STEP_PERM)
""" """
Set permutation on the inputs - sorted by key image on host. Set permutation on the inputs - sorted by key image on host.

View File

@ -33,7 +33,7 @@ async def input_vini(
) )
await confirms.transaction_step( await confirms.transaction_step(
state.ctx, state.STEP_VINI, state.current_input_index + 1, state.input_count state, state.STEP_VINI, state.current_input_index + 1
) )
if state.current_input_index >= state.input_count: if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs") raise ValueError("Too many inputs")

View File

@ -12,7 +12,7 @@ from apps.monero.xmr import crypto
async def all_inputs_set(state: State): async def all_inputs_set(state: State):
state.mem_trace(0) state.mem_trace(0)
await confirms.transaction_step(state.ctx, state.STEP_ALL_IN) await confirms.transaction_step(state, state.STEP_ALL_IN)
from trezor.messages.MoneroTransactionAllInputsSetAck import ( from trezor.messages.MoneroTransactionAllInputsSetAck import (
MoneroTransactionAllInputsSetAck, MoneroTransactionAllInputsSetAck,

View File

@ -23,10 +23,7 @@ async def set_output(
# Progress update only for master message (skip for offloaded BP msg) # Progress update only for master message (skip for offloaded BP msg)
if not is_offloaded_bp: if not is_offloaded_bp:
await confirms.transaction_step( await confirms.transaction_step(
state.ctx, state, state.STEP_OUT, state.current_output_index + 1
state.STEP_OUT,
state.current_output_index + 1,
state.output_count,
) )
state.mem_trace(1, True) state.mem_trace(1, True)

View File

@ -18,7 +18,7 @@ from apps.monero.xmr import crypto
async def all_outputs_set(state: State): async def all_outputs_set(state: State):
state.mem_trace(0) state.mem_trace(0)
await confirms.transaction_step(state.ctx, state.STEP_ALL_OUT) await confirms.transaction_step(state, state.STEP_ALL_OUT)
state.mem_trace(1) state.mem_trace(1)
_validate(state) _validate(state)

View File

@ -48,7 +48,7 @@ async def sign_input(
:return: Generated signature MGs[i] :return: Generated signature MGs[i]
""" """
await confirms.transaction_step( await confirms.transaction_step(
state.ctx, state.STEP_SIGN, state.current_input_index + 1, state.input_count state, state.STEP_SIGN, state.current_input_index + 1
) )
state.current_input_index += 1 state.current_input_index += 1