feat(core/ui): unify progress.py code between models, implement storage translations

fixes #3520
matejcik 1 month ago
parent 8baccff7c5
commit b66b00e2c4

@ -538,6 +538,10 @@ static void _librust_qstrs(void) {
MP_QSTR_sign_message__verify_address;
MP_QSTR_skip_first_paint;
MP_QSTR_spending_amount;
MP_QSTR_storage_msg__processing;
MP_QSTR_storage_msg__starting;
MP_QSTR_storage_msg__verifying_pin;
MP_QSTR_storage_msg__wrong_pin;
MP_QSTR_subprompt;
MP_QSTR_subtitle;
MP_QSTR_text_mono;

@ -1234,6 +1234,10 @@ pub enum TranslatedString {
ethereum__staking_unstake = 840, // "UNSTAKE"
#[cfg(feature = "universal_fw")]
ethereum__staking_unstake_intro = 841, // "Unstake ETH from Everstake?"
storage_msg__processing = 842, // "PROCESSING"
storage_msg__starting = 843, // "STARTING UP"
storage_msg__verifying_pin = 844, // "VERIFYING PIN"
storage_msg__wrong_pin = 845, // "WRONG PIN"
}
impl TranslatedString {
@ -2463,6 +2467,10 @@ impl TranslatedString {
Self::ethereum__staking_unstake => "UNSTAKE",
#[cfg(feature = "universal_fw")]
Self::ethereum__staking_unstake_intro => "Unstake ETH from Everstake?",
Self::storage_msg__processing => "PROCESSING",
Self::storage_msg__starting => "STARTING UP",
Self::storage_msg__verifying_pin => "VERIFYING PIN",
Self::storage_msg__wrong_pin => "WRONG PIN",
}
}
@ -3693,6 +3701,10 @@ impl TranslatedString {
Qstr::MP_QSTR_ethereum__staking_unstake => Some(Self::ethereum__staking_unstake),
#[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__staking_unstake_intro => Some(Self::ethereum__staking_unstake_intro),
Qstr::MP_QSTR_storage_msg__processing => Some(Self::storage_msg__processing),
Qstr::MP_QSTR_storage_msg__starting => Some(Self::storage_msg__starting),
Qstr::MP_QSTR_storage_msg__verifying_pin => Some(Self::storage_msg__verifying_pin),
Qstr::MP_QSTR_storage_msg__wrong_pin => Some(Self::storage_msg__wrong_pin),
_ => None,
}
}

@ -1571,18 +1571,22 @@ extern "C" fn new_show_group_share_success(
extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?;
let description: StrBuffer =
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
let title: Option<StrBuffer> = kwargs
.get(Qstr::MP_QSTR_title)
.and_then(Obj::try_into_option)
.unwrap_or(None);
let mut progress =
Progress::new(indeterminate, description).with_update_description(StrBuffer::alloc);
if let Some(title) = title {
progress = progress.with_title(title);
};
// Description updates are received as &str and we need to provide a way to
// convert them to StrBuffer.
let obj = LayoutObj::new(
Progress::new(indeterminate, description)
.with_title(title)
.with_update_description(StrBuffer::alloc),
)?;
let obj = LayoutObj::new(progress)?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -2074,9 +2078,9 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def show_progress(
/// *,
/// title: str,
/// description: str,
/// indeterminate: bool = False,
/// description: str = "",
/// title: str | None = None,
/// ) -> LayoutObj[UiResult]:
/// """Show progress loader. Please note that the number of lines reserved on screen for
/// description is determined at construction time. If you want multiline descriptions

@ -1547,10 +1547,18 @@ extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs:
extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?;
let description: StrBuffer =
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
let title: Option<StrBuffer> = kwargs
.get(Qstr::MP_QSTR_title)
.and_then(Obj::try_into_option)
.unwrap_or(None);
let (title, description) = if let Some(title) = title {
(title, description)
} else {
(description, StrBuffer::empty())
};
// Description updates are received as &str and we need to provide a way to
// convert them to StrBuffer.
@ -2130,9 +2138,9 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def show_progress(
/// *,
/// title: str,
/// description: str,
/// indeterminate: bool = False,
/// description: str = "",
/// title: str | None = None,
/// ) -> LayoutObj[UiResult]:
/// """Show progress loader. Please note that the number of lines reserved on screen for
/// description is determined at construction time. If you want multiline descriptions

@ -399,9 +399,9 @@ def show_group_share_success(
# rust/src/ui/model_tr/layout.rs
def show_progress(
*,
title: str,
description: str,
indeterminate: bool = False,
description: str = "",
title: str | None = None,
) -> LayoutObj[UiResult]:
"""Show progress loader. Please note that the number of lines reserved on screen for
description is determined at construction time. If you want multiline descriptions
@ -917,9 +917,9 @@ def show_remaining_shares(
# rust/src/ui/model_tt/layout.rs
def show_progress(
*,
title: str,
description: str,
indeterminate: bool = False,
description: str = "",
title: str | None = None,
) -> LayoutObj[UiResult]:
"""Show progress loader. Please note that the number of lines reserved on screen for
description is determined at construction time. If you want multiline descriptions

@ -756,6 +756,10 @@ class TR:
stellar__value_sha256: str = "Value (SHA-256):"
stellar__wanna_clean_value_key_template: str = "Do you want to clear value key {0}?"
stellar__your_account: str = " your account"
storage_msg__processing: str = "PROCESSING"
storage_msg__starting: str = "STARTING UP"
storage_msg__verifying_pin: str = "VERIFYING PIN"
storage_msg__wrong_pin: str = "WRONG PIN"
tezos__baker_address: str = "Baker address:"
tezos__balance: str = "Balance:"
tezos__ballot: str = "Ballot:"

@ -169,8 +169,6 @@ trezor.ui.layouts.tr.fido
import trezor.ui.layouts.tr.fido
trezor.ui.layouts.tr.homescreen
import trezor.ui.layouts.tr.homescreen
trezor.ui.layouts.tr.progress
import trezor.ui.layouts.tr.progress
trezor.ui.layouts.tr.recovery
import trezor.ui.layouts.tr.recovery
trezor.ui.layouts.tr.reset
@ -181,8 +179,6 @@ trezor.ui.layouts.tt.fido
import trezor.ui.layouts.tt.fido
trezor.ui.layouts.tt.homescreen
import trezor.ui.layouts.tt.homescreen
trezor.ui.layouts.tt.progress
import trezor.ui.layouts.tt.progress
trezor.ui.layouts.tt.recovery
import trezor.ui.layouts.tt.recovery
trezor.ui.layouts.tt.reset

@ -34,7 +34,7 @@ async def authenticate_device(msg: AuthenticateDevice) -> AuthenticityProof:
write_compact_size(h, len(msg.challenge))
h.extend(msg.challenge)
spinner = progress("", description=TR.progress__authenticity_check)
spinner = progress(TR.progress__authenticity_check)
spinner.report(0)
try:

@ -24,7 +24,7 @@ async def change_language(msg: ChangeLanguage) -> Success:
nonlocal loader
if loader is None:
workflow.close_others()
loader = progress("", TR.language__progress)
loader = progress(TR.language__progress)
loader.report(value)
if msg.data_length == 0:

@ -44,7 +44,7 @@ async def reset_device(msg: ResetDevice) -> Success:
await confirm_reset_device(title)
# Rendering empty loader so users do not feel a freezing screen
render_empty_loader(TR.progress__processing, "")
render_empty_loader(config.StorageMessage.PROCESSING_MSG)
# wipe storage to make sure the device is in a clear state
storage.reset()

@ -1,7 +1,9 @@
from typing import TYPE_CHECKING
from . import config
if TYPE_CHECKING:
from typing import Any
from typing import Any, Container
from trezor.ui.layouts.common import ProgressLayout
@ -11,13 +13,15 @@ _progress_layout: ProgressLayout | None = None
_started_with_empty_loader = False
keepalive_callback: Any = None
_ignore_loader_messages: tuple[str, ...] = ()
_ignore_loader_messages: Container[config.StorageMessage] = ()
def ignore_nonpin_loader_messages() -> None:
global _ignore_loader_messages
# TODO: handle translation of those (in C)
_ignore_loader_messages = ("Processing", "Starting up")
_ignore_loader_messages = (
config.StorageMessage.PROCESSING_MSG,
config.StorageMessage.STARTING_MSG,
)
def allow_all_loader_messages() -> None:
@ -25,7 +29,7 @@ def allow_all_loader_messages() -> None:
_ignore_loader_messages = ()
def render_empty_loader(message: str, description: str) -> None:
def render_empty_loader(message: config.StorageMessage, description: str = "") -> None:
"""Render empty loader to prevent the screen appear to be frozen."""
from trezor.ui.layouts.progress import pin_progress
@ -38,7 +42,9 @@ def render_empty_loader(message: str, description: str) -> None:
_started_with_empty_loader = True
def show_pin_timeout(seconds: int, progress: int, message: str) -> bool:
def show_pin_timeout(
seconds: int, progress: int, message: config.StorageMessage
) -> bool:
from trezor import TR
from trezor.ui.layouts.progress import pin_progress

@ -1,6 +1,100 @@
from trezor import utils
from typing import TYPE_CHECKING
if utils.UI_LAYOUT == "TT":
from .tt.progress import * # noqa: F401,F403
elif utils.UI_LAYOUT == "TR":
from .tr.progress import * # noqa: F401,F403
import trezorui2
from trezor import TR, config, ui, utils
if TYPE_CHECKING:
from typing import Any
from .common import ProgressLayout
def _storage_message_to_str(message: config.StorageMessage | None) -> str | None:
from trezor import TR
if message is None:
return None
if message == config.StorageMessage.NO_MSG:
return ""
if message == config.StorageMessage.VERIFYING_PIN_MSG:
return TR.storage_msg__verifying_pin
if message == config.StorageMessage.PROCESSING_MSG:
return TR.storage_msg__processing
if message == config.StorageMessage.STARTING_MSG:
return TR.storage_msg__starting
if message == config.StorageMessage.WRONG_PIN_MSG:
return TR.storage_msg__wrong_pin
raise RuntimeError # unknown message
class RustProgress:
def __init__(
self,
layout: Any,
):
self.layout = layout
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
self.layout.attach_timer_fn(self.set_timer)
self.layout.paint()
ui.refresh()
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
def set_timer(self, token: int, deadline: int) -> None:
raise RuntimeError # progress layouts should not set timers
def report(self, value: int, description: str | None = None):
msg = self.layout.progress_event(value, description or "")
assert msg is None
self.layout.paint()
ui.refresh()
def progress(
description: str | None = None,
title: str | None = None,
indeterminate: bool = False,
) -> ProgressLayout:
if description is None:
description = TR.progress__please_wait # def_arg
if title is not None:
title = title.upper()
elif not utils.MODEL_IS_T2B1:
# on TT, uppercase the description which ends up on top of the screen
# when no title is set
description = description.upper()
return RustProgress(
layout=trezorui2.show_progress(
description=description,
title=title,
indeterminate=indeterminate,
)
)
def bitcoin_progress(message: str) -> ProgressLayout:
return progress(message)
def coinjoin_progress(message: str) -> ProgressLayout:
return RustProgress(
layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False)
)
def pin_progress(title: config.StorageMessage, description: str) -> ProgressLayout:
return progress(description=description, title=_storage_message_to_str(title))
if not utils.BITCOIN_ONLY:
def monero_keyimage_sync_progress() -> ProgressLayout:
return progress(TR.progress__syncing)
def monero_live_refresh_progress() -> ProgressLayout:
return progress(TR.progress__refreshing, indeterminate=True)
def monero_transaction_progress_inner() -> ProgressLayout:
return progress(TR.progress__signing_transaction)

@ -1,69 +0,0 @@
from typing import TYPE_CHECKING
import trezorui2
from trezor import TR, ui, utils
if TYPE_CHECKING:
from typing import Any
from ..common import ProgressLayout
class RustProgress:
def __init__(
self,
layout: Any,
):
self.layout = layout
self.layout.attach_timer_fn(self.set_timer)
self.layout.paint()
ui.refresh()
def set_timer(self, token: int, deadline: int) -> None:
raise RuntimeError # progress layouts should not set timers
def report(self, value: int, description: str | None = None):
msg = self.layout.progress_event(value, description or "")
assert msg is None
self.layout.paint()
ui.refresh()
def progress(
message: str | None = None,
description: str | None = None,
indeterminate: bool = False,
) -> ProgressLayout:
return RustProgress(
layout=trezorui2.show_progress(
title=message.upper() if message else "",
indeterminate=indeterminate,
description=description or "",
)
)
def bitcoin_progress(description: str) -> ProgressLayout:
return progress("", description)
def coinjoin_progress(message: str) -> ProgressLayout:
return RustProgress(
layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False)
)
def pin_progress(message: str, description: str) -> ProgressLayout:
return progress(message, description)
if not utils.BITCOIN_ONLY:
def monero_keyimage_sync_progress() -> ProgressLayout:
return progress("", TR.progress__syncing)
def monero_live_refresh_progress() -> ProgressLayout:
return progress("", TR.progress__refreshing, indeterminate=True)
def monero_transaction_progress_inner() -> ProgressLayout:
return progress("", TR.progress__signing_transaction)

@ -1,72 +0,0 @@
from typing import TYPE_CHECKING
import trezorui2
from trezor import TR, ui, utils
if TYPE_CHECKING:
from typing import Any
from ..common import ProgressLayout
class RustProgress:
def __init__(
self,
layout: Any,
):
self.layout = layout
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
self.layout.attach_timer_fn(self.set_timer)
self.layout.paint()
ui.refresh()
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
def set_timer(self, token: int, deadline: int) -> None:
raise RuntimeError # progress layouts should not set timers
def report(self, value: int, description: str | None = None):
msg = self.layout.progress_event(value, description or "")
assert msg is None
self.layout.paint()
ui.refresh()
def progress(
message: str | None = None,
description: str | None = None,
indeterminate: bool = False,
) -> ProgressLayout:
message = message or TR.progress__please_wait # def_arg
return RustProgress(
layout=trezorui2.show_progress(
title=message.upper(),
indeterminate=indeterminate,
description=description or "",
)
)
def bitcoin_progress(message: str) -> ProgressLayout:
return progress(message)
def coinjoin_progress(message: str) -> ProgressLayout:
return RustProgress(
layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False)
)
def pin_progress(message: str, description: str) -> ProgressLayout:
return progress(message, description=description)
if not utils.BITCOIN_ONLY:
def monero_keyimage_sync_progress() -> ProgressLayout:
return progress("", TR.progress__syncing)
def monero_live_refresh_progress() -> ProgressLayout:
return progress("", TR.progress__refreshing, indeterminate=True)
def monero_transaction_progress_inner() -> ProgressLayout:
return progress("", TR.progress__signing_transaction)

@ -677,6 +677,10 @@
"sign_message__confirm_message": "CONFIRM MESSAGE",
"sign_message__message_size": "Message size:",
"sign_message__verify_address": "VERIFY ADDRESS",
"storage_msg__verifying_pin": "VERIFYING PIN",
"storage_msg__processing": "PROCESSING",
"storage_msg__starting": "STARTING UP",
"storage_msg__wrong_pin": "WRONG PIN",
"solana__account_index": "Account index",
"solana__associated_token_account": "Associated token account",
"solana__confirm_multisig": "Confirm multisig",

@ -840,5 +840,9 @@
"838": "ethereum__staking_stake_address",
"839": "ethereum__staking_stake_intro",
"840": "ethereum__staking_unstake",
"841": "ethereum__staking_unstake_intro"
"841": "ethereum__staking_unstake_intro",
"842": "storage_msg__processing",
"843": "storage_msg__starting",
"844": "storage_msg__verifying_pin",
"845": "storage_msg__wrong_pin"
}

@ -12196,7 +12196,7 @@
"TR_en_test_cancel.py::test_cancel_message_via_initialize[message1]": "41b9fc37f230520f9d94df226ff8433822e56ea4276a31ef99171a774e477045",
"TR_en_test_cancel.py::test_cancel_on_paginated": "79f025ebfbdc5a81dae1385b17d2d802280063479c8480fa5281734fe1542385",
"TR_en_test_debuglink.py::test_softlock_instability": "cff029b728b242ec07f405a0fcd12a77212f0a28e9ec14f1b9e2db1b63293783",
"TR_en_test_firmware_hash.py::test_firmware_hash_emu": "a5099e7016da59ba2a1efac6a060711383f199d674c8357146738c42b79be6ff",
"TR_en_test_firmware_hash.py::test_firmware_hash_emu": "a020914cfcb0f51772fc14300eb89899b605e114cb75655874aed44ef29695c0",
"TR_en_test_firmware_hash.py::test_firmware_hash_hw": "8c801bd0142e5c1ad4aad50b34c7debb1b8f17a2e0a87eb7f95531b9fd15e095",
"TR_en_test_language.py::test_error_invalid_data_hash": "0cb01b8002472dff316d6eb3ba901dffb762d88fdf61e5ef8642ca7772d3e71c",
"TR_en_test_language.py::test_error_invalid_data_length": "4ffbed72e7ed7fbab85f830952200adf7758af81b658b56de4672344120456a6",
@ -19514,28 +19514,28 @@
"TT_en_test_debuglink.py::test_softlock_instability": "55cb4cbeec68bd8ccee460034677cf8053f8f688d5c3559f360c38a205b34e37",
"TT_en_test_firmware_hash.py::test_firmware_hash_emu": "2a63f0bd10ba99e223f571482d4af635653bb8a3bddc1d8400777ee5519bc605",
"TT_en_test_firmware_hash.py::test_firmware_hash_hw": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_test_language.py::test_error_invalid_data_hash": "67782b952469c260afac92bac1efc2ba9c2f342319ae95fb5e91c843da2a6e46",
"TT_en_test_language.py::test_error_invalid_data_hash": "aaf31bdc64a531bc9084a003b2ad78a9fb91ba9b138090b1e2daf78ca4c106f9",
"TT_en_test_language.py::test_error_invalid_data_length": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_error_invalid_header_magic": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_error_invalid_signature": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_error_too_long": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_error_version_mismatch": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_full_language_change[cs]": "1cf02eca93e97af9c4a4193a04050f43ddc7c627cca85dc7486050977d89d95b",
"TT_en_test_language.py::test_full_language_change[de]": "d05c45a698f9e8a00b7f64bee10077dbce8e592fb5f76b76ba37e62df8d34e6c",
"TT_en_test_language.py::test_full_language_change[cs]": "82f805dacbd77f3fe46224744a2c2fe24623a2a32aa3b538ffc15ea147c26c61",
"TT_en_test_language.py::test_full_language_change[de]": "637494aacf526b8c6465a5c48a35eee4fa71d90d9f90d7c675c555bc5df75442",
"TT_en_test_language.py::test_full_language_change[en]": "5f6cec419aad5e5658f6fda45c2cd40fa4581823e7f9f0c4199970cd179bfd4e",
"TT_en_test_language.py::test_full_language_change[es]": "fcb472f8786ab55cc7b90450a002165fd74ed88edc0d41a0dffd741b801d7f0b",
"TT_en_test_language.py::test_full_language_change[fr]": "a6b00695cb76f4fcc7197d37df49c3b1997da6ed23f2c21330d30e0f28dc910d",
"TT_en_test_language.py::test_header_trailing_data": "be40adb967cdb6ed5857aebeabbd13c65900fc8c2aa30ff90e239b911fbd9c59",
"TT_en_test_language.py::test_language_is_removed_after_wipe": "3e1fb6ae0bdcc7b306971d831c48e989d176cb57b10e69b319f546e0f5437f9c",
"TT_en_test_language.py::test_full_language_change[es]": "6c3c6983dedaf60e8818c0acdff7f1ba83ff9106333d834b8bed02d910bf5b1f",
"TT_en_test_language.py::test_full_language_change[fr]": "7e1f16d84163fa6b8a8acce859f5c5e97ded578c28f75629e4f9bd587cd051a8",
"TT_en_test_language.py::test_header_trailing_data": "073921ad154051d3ae330aa981a7c8ddf725006ffd007a9f131cdec0ccb82097",
"TT_en_test_language.py::test_language_is_removed_after_wipe": "094c2025512f5e4b32344313a4c3d9a1e62d8b1e04eee4dbc282509082d76979",
"TT_en_test_language.py::test_reject_update": "78b8f16e00c5d653a7d5c0929d21ae8ae1c76798fe28f0eb6edee8e2ee8b0202",
"TT_en_test_language.py::test_silent_first_install[False-False]": "91184a50875388be3e29102184c2828150196f29cc59cb0e81fa6623d8ddb796",
"TT_en_test_language.py::test_silent_first_install[None-False]": "91184a50875388be3e29102184c2828150196f29cc59cb0e81fa6623d8ddb796",
"TT_en_test_language.py::test_silent_first_install[True-True]": "5620e376c365737f72b1b86d4942c162ef1f65ff159cb8262f2411e01fa0b016",
"TT_en_test_language.py::test_switch_from_english[None]": "0f92d5c7b12c2ab21152dbc9a5498859990476f91267f337dfad4b35c87e5de2",
"TT_en_test_language.py::test_switch_from_english[True]": "0f92d5c7b12c2ab21152dbc9a5498859990476f91267f337dfad4b35c87e5de2",
"TT_en_test_language.py::test_silent_first_install[False-False]": "7476715631cb4348ca5bc8ecdac99792abfc77b10da654305e1d09ce5e838176",
"TT_en_test_language.py::test_silent_first_install[None-False]": "7476715631cb4348ca5bc8ecdac99792abfc77b10da654305e1d09ce5e838176",
"TT_en_test_language.py::test_silent_first_install[True-True]": "944168e4747abf8a2a894ed59aad65de2d0e7193488b22869e3f59be034f2e5f",
"TT_en_test_language.py::test_switch_from_english[None]": "dc4fef22119297c57f6b239143880cdd8d9f08199ff596467ab17db1a64b015d",
"TT_en_test_language.py::test_switch_from_english[True]": "dc4fef22119297c57f6b239143880cdd8d9f08199ff596467ab17db1a64b015d",
"TT_en_test_language.py::test_switch_from_english_not_silent": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_test_language.py::test_switch_language": "2d7cc1088467af2f13ec08755cd2d11e39b463209e92ef9a06d1eec0cfb8dde0",
"TT_en_test_language.py::test_translations_renders_on_screen": "9e96d81519ee2405b37a18dd856209373366665d1d9921da5ea73edcf2a74e88",
"TT_en_test_language.py::test_switch_language": "c6d3537688d4ad0169fa2e8d43d9d68f7965ac09e15e31eb618d4a40dfd9be5d",
"TT_en_test_language.py::test_translations_renders_on_screen": "30c57160aa25242ac290960c65bfe9d2f0bd3ee74ca97809f33351900c3a8c5e",
"TT_en_test_msg_applysettings.py::test_apply_homescreen_jpeg": "0e2a71ac60add6bebc03645b6f4f6f19abdba56f1e55e799479b116e5a66aef6",
"TT_en_test_msg_applysettings.py::test_apply_homescreen_jpeg_progressive": "4d7c67024ee17436071e5cf2f79c36453249c95314a732580623cb1f1cdbfdf3",
"TT_en_test_msg_applysettings.py::test_apply_homescreen_jpeg_wrong_size": "4d7c67024ee17436071e5cf2f79c36453249c95314a732580623cb1f1cdbfdf3",

Loading…
Cancel
Save