from micropython import const from typing import TYPE_CHECKING from trezor import loop, res, ui, utils from trezor.ui.loader import Loader, LoaderDefault from ..common.confirm import CANCELLED, CONFIRMED, INFO, ConfirmBase, Pageable from .button import Button, ButtonAbort, ButtonCancel, ButtonConfirm, ButtonDefault if TYPE_CHECKING: from typing import Any from .button import ButtonContent, ButtonStyleType from trezor.ui.loader import LoaderStyleType class Confirm(ConfirmBase): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel def __init__( self, content: ui.Component, confirm: ButtonContent | None = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, cancel: ButtonContent | None = DEFAULT_CANCEL, cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE, major_confirm: bool = False, ) -> None: self.content = content button_confirm: Button | None = None button_cancel: Button | None = None if confirm is not None: if cancel is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(13, cells_x=2) else: area = ui.grid(9, n_x=2) button_confirm = Button(area, confirm, confirm_style) button_confirm.on_click = self.on_confirm if cancel is not None: if confirm is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(12, cells_x=1) else: area = ui.grid(8, n_x=2) button_cancel = Button(area, cancel, cancel_style) button_cancel.on_click = self.on_cancel super().__init__(content, button_confirm, button_cancel) class ConfirmPageable(Confirm): def __init__(self, pageable: Pageable, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) self.pageable = pageable async def handle_paging(self) -> None: from .swipe import SWIPE_HORIZONTAL, SWIPE_LEFT, SWIPE_RIGHT, Swipe if self.pageable.is_first(): directions = SWIPE_LEFT elif self.pageable.is_last(): directions = SWIPE_RIGHT else: directions = SWIPE_HORIZONTAL if __debug__: from apps.debug import swipe_signal swipe = await loop.race(Swipe(directions), swipe_signal()) else: swipe = await Swipe(directions) if swipe == SWIPE_LEFT: self.pageable.next() else: self.pageable.prev() self.content.repaint = True if self.confirm is not None: self.confirm.repaint = True if self.cancel is not None: self.cancel.repaint = True def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: tasks = super().create_tasks() if self.pageable.page_count() > 1: return tasks + (self.handle_paging(),) else: return tasks def on_render(self) -> None: PULSE_PERIOD = const(1_200_000) super().on_render() if not self.pageable.is_first(): t = ui.pulse(PULSE_PERIOD) c = ui.blend(ui.GREY, ui.DARK_GREY, t) icon = res.load(ui.ICON_SWIPE_RIGHT) if utils.DISABLE_ANIMATION: ui.display.icon(18, 68, icon, ui.GREY, ui.BG) else: ui.display.icon(18, 68, icon, c, ui.BG) if not self.pageable.is_last(): t = ui.pulse(PULSE_PERIOD, PULSE_PERIOD // 2) c = ui.blend(ui.GREY, ui.DARK_GREY, t) icon = res.load(ui.ICON_SWIPE_LEFT) if utils.DISABLE_ANIMATION: ui.display.icon(205, 68, icon, ui.GREY, ui.BG) else: ui.display.icon(205, 68, icon, c, ui.BG) class InfoConfirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel DEFAULT_INFO = res.load(ui.ICON_CLICK) # TODO: this should be (i) icon, not click DEFAULT_INFO_STYLE = ButtonDefault def __init__( self, content: ui.Component, confirm: ButtonContent = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, cancel: ButtonContent = DEFAULT_CANCEL, cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE, info: ButtonContent = DEFAULT_INFO, info_style: ButtonStyleType = DEFAULT_INFO_STYLE, ) -> None: super().__init__() self.content = content self.confirm = Button(ui.grid(14), confirm, confirm_style) self.confirm.on_click = self.on_confirm self.info = Button(ui.grid(13), info, info_style) self.info.on_click = self.on_info self.cancel = Button(ui.grid(12), cancel, cancel_style) self.cancel.on_click = self.on_cancel def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) if self.info is not None: self.info.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_info(self) -> None: raise ui.Result(INFO) if __debug__: def read_content(self) -> list[str]: return self.content.read_content() def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: from apps.debug import confirm_signal return super().create_tasks() + (confirm_signal(),) class HoldToConfirm(ui.Layout): DEFAULT_CONFIRM = "Hold to confirm" DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_LOADER_STYLE = LoaderDefault def __init__( self, content: ui.Component, confirm: str = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, loader_style: LoaderStyleType = DEFAULT_LOADER_STYLE, cancel: bool = True, ): super().__init__() self.content = content self.loader = Loader(loader_style) self.loader.on_start = self._on_loader_start if cancel: self.confirm = Button(ui.grid(17, n_x=4, cells_x=3), confirm, confirm_style) else: self.confirm = Button(ui.grid(4, n_x=1), confirm, confirm_style) self.confirm.on_press_start = self._on_press_start self.confirm.on_press_end = self._on_press_end self.confirm.on_click = self._on_click self.cancel = None if cancel: self.cancel = Button( ui.grid(16, n_x=4), res.load(ui.ICON_CANCEL), ButtonAbort ) self.cancel.on_click = self.on_cancel def _on_press_start(self) -> None: self.loader.start() def _on_press_end(self) -> None: self.loader.stop() def _on_loader_start(self) -> None: # Loader has either started growing, or returned to the 0-position. # In the first case we need to clear the content leftovers, in the latter # we need to render the content again. ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT - 58, ui.BG) self.content.dispatch(ui.REPAINT, 0, 0) def _on_click(self) -> None: if self.loader.elapsed_ms() >= self.loader.target_ms: self.on_confirm() def dispatch(self, event: int, x: int, y: int) -> None: if self.loader.start_ms is not None: if utils.DISABLE_ANIMATION: self.on_confirm() self.loader.dispatch(event, x, y) else: self.content.dispatch(event, x, y) self.confirm.dispatch(event, x, y) if self.cancel: self.cancel.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) def on_cancel(self) -> None: raise ui.Result(CANCELLED) if __debug__: def read_content(self) -> list[str]: return self.content.read_content() def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: from apps.debug import confirm_signal return super().create_tasks() + (confirm_signal(),)