1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 12:28:09 +00:00

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

fixes #3520
This commit is contained in:
matejcik 2024-03-15 11:47:31 +01:00 committed by matejcik
parent 0553a2d4b4
commit de2fe3d6d6
18 changed files with 190 additions and 194 deletions

View File

@ -0,0 +1 @@
Translate also texts for PIN progress loaders.

View File

@ -538,6 +538,10 @@ static void _librust_qstrs(void) {
MP_QSTR_sign_message__verify_address; MP_QSTR_sign_message__verify_address;
MP_QSTR_skip_first_paint; MP_QSTR_skip_first_paint;
MP_QSTR_spending_amount; 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_subprompt;
MP_QSTR_subtitle; MP_QSTR_subtitle;
MP_QSTR_text_mono; MP_QSTR_text_mono;

View File

@ -1234,6 +1234,10 @@ pub enum TranslatedString {
ethereum__staking_unstake = 840, // "UNSTAKE" ethereum__staking_unstake = 840, // "UNSTAKE"
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__staking_unstake_intro = 841, // "Unstake ETH from Everstake?" 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 { impl TranslatedString {
@ -2463,6 +2467,10 @@ impl TranslatedString {
Self::ethereum__staking_unstake => "UNSTAKE", Self::ethereum__staking_unstake => "UNSTAKE",
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__staking_unstake_intro => "Unstake ETH from Everstake?", 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), Qstr::MP_QSTR_ethereum__staking_unstake => Some(Self::ethereum__staking_unstake),
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__staking_unstake_intro => Some(Self::ethereum__staking_unstake_intro), 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, _ => None,
} }
} }

View File

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

View File

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

View File

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

View File

@ -756,6 +756,10 @@ class TR:
stellar__value_sha256: str = "Value (SHA-256):" stellar__value_sha256: str = "Value (SHA-256):"
stellar__wanna_clean_value_key_template: str = "Do you want to clear value key {0}?" stellar__wanna_clean_value_key_template: str = "Do you want to clear value key {0}?"
stellar__your_account: str = " your account" 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__baker_address: str = "Baker address:"
tezos__balance: str = "Balance:" tezos__balance: str = "Balance:"
tezos__ballot: str = "Ballot:" tezos__ballot: str = "Ballot:"

View File

@ -169,8 +169,6 @@ trezor.ui.layouts.tr.fido
import trezor.ui.layouts.tr.fido import trezor.ui.layouts.tr.fido
trezor.ui.layouts.tr.homescreen trezor.ui.layouts.tr.homescreen
import 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 trezor.ui.layouts.tr.recovery
import trezor.ui.layouts.tr.recovery import trezor.ui.layouts.tr.recovery
trezor.ui.layouts.tr.reset trezor.ui.layouts.tr.reset
@ -181,8 +179,6 @@ trezor.ui.layouts.tt.fido
import trezor.ui.layouts.tt.fido import trezor.ui.layouts.tt.fido
trezor.ui.layouts.tt.homescreen trezor.ui.layouts.tt.homescreen
import 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 trezor.ui.layouts.tt.recovery
import trezor.ui.layouts.tt.recovery import trezor.ui.layouts.tt.recovery
trezor.ui.layouts.tt.reset trezor.ui.layouts.tt.reset

View File

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

View File

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

View File

@ -44,7 +44,7 @@ async def reset_device(msg: ResetDevice) -> Success:
await confirm_reset_device(title) await confirm_reset_device(title)
# Rendering empty loader so users do not feel a freezing screen # 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 # wipe storage to make sure the device is in a clear state
storage.reset() storage.reset()

View File

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

View File

@ -1,6 +1,100 @@
from trezor import utils from typing import TYPE_CHECKING
if utils.UI_LAYOUT == "TT": import trezorui2
from .tt.progress import * # noqa: F401,F403 from trezor import TR, config, ui, utils
elif utils.UI_LAYOUT == "TR":
from .tr.progress import * # noqa: F401,F403 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)

View File

@ -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)

View File

@ -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)

View File

@ -677,6 +677,10 @@
"sign_message__confirm_message": "CONFIRM MESSAGE", "sign_message__confirm_message": "CONFIRM MESSAGE",
"sign_message__message_size": "Message size:", "sign_message__message_size": "Message size:",
"sign_message__verify_address": "VERIFY ADDRESS", "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__account_index": "Account index",
"solana__associated_token_account": "Associated token account", "solana__associated_token_account": "Associated token account",
"solana__confirm_multisig": "Confirm multisig", "solana__confirm_multisig": "Confirm multisig",

View File

@ -840,5 +840,9 @@
"838": "ethereum__staking_stake_address", "838": "ethereum__staking_stake_address",
"839": "ethereum__staking_stake_intro", "839": "ethereum__staking_stake_intro",
"840": "ethereum__staking_unstake", "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"
} }

View File

@ -12196,7 +12196,7 @@
"TR_en_test_cancel.py::test_cancel_message_via_initialize[message1]": "41b9fc37f230520f9d94df226ff8433822e56ea4276a31ef99171a774e477045", "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_cancel.py::test_cancel_on_paginated": "79f025ebfbdc5a81dae1385b17d2d802280063479c8480fa5281734fe1542385",
"TR_en_test_debuglink.py::test_softlock_instability": "cff029b728b242ec07f405a0fcd12a77212f0a28e9ec14f1b9e2db1b63293783", "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_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_hash": "0cb01b8002472dff316d6eb3ba901dffb762d88fdf61e5ef8642ca7772d3e71c",
"TR_en_test_language.py::test_error_invalid_data_length": "4ffbed72e7ed7fbab85f830952200adf7758af81b658b56de4672344120456a6", "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_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_emu": "2a63f0bd10ba99e223f571482d4af635653bb8a3bddc1d8400777ee5519bc605",
"TT_en_test_firmware_hash.py::test_firmware_hash_hw": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", "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_data_length": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_error_invalid_header_magic": "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_invalid_signature": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_error_too_long": "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_error_version_mismatch": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739",
"TT_en_test_language.py::test_full_language_change[cs]": "1cf02eca93e97af9c4a4193a04050f43ddc7c627cca85dc7486050977d89d95b", "TT_en_test_language.py::test_full_language_change[cs]": "82f805dacbd77f3fe46224744a2c2fe24623a2a32aa3b538ffc15ea147c26c61",
"TT_en_test_language.py::test_full_language_change[de]": "d05c45a698f9e8a00b7f64bee10077dbce8e592fb5f76b76ba37e62df8d34e6c", "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[en]": "5f6cec419aad5e5658f6fda45c2cd40fa4581823e7f9f0c4199970cd179bfd4e",
"TT_en_test_language.py::test_full_language_change[es]": "fcb472f8786ab55cc7b90450a002165fd74ed88edc0d41a0dffd741b801d7f0b", "TT_en_test_language.py::test_full_language_change[es]": "6c3c6983dedaf60e8818c0acdff7f1ba83ff9106333d834b8bed02d910bf5b1f",
"TT_en_test_language.py::test_full_language_change[fr]": "a6b00695cb76f4fcc7197d37df49c3b1997da6ed23f2c21330d30e0f28dc910d", "TT_en_test_language.py::test_full_language_change[fr]": "7e1f16d84163fa6b8a8acce859f5c5e97ded578c28f75629e4f9bd587cd051a8",
"TT_en_test_language.py::test_header_trailing_data": "be40adb967cdb6ed5857aebeabbd13c65900fc8c2aa30ff90e239b911fbd9c59", "TT_en_test_language.py::test_header_trailing_data": "073921ad154051d3ae330aa981a7c8ddf725006ffd007a9f131cdec0ccb82097",
"TT_en_test_language.py::test_language_is_removed_after_wipe": "3e1fb6ae0bdcc7b306971d831c48e989d176cb57b10e69b319f546e0f5437f9c", "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_reject_update": "78b8f16e00c5d653a7d5c0929d21ae8ae1c76798fe28f0eb6edee8e2ee8b0202",
"TT_en_test_language.py::test_silent_first_install[False-False]": "91184a50875388be3e29102184c2828150196f29cc59cb0e81fa6623d8ddb796", "TT_en_test_language.py::test_silent_first_install[False-False]": "7476715631cb4348ca5bc8ecdac99792abfc77b10da654305e1d09ce5e838176",
"TT_en_test_language.py::test_silent_first_install[None-False]": "91184a50875388be3e29102184c2828150196f29cc59cb0e81fa6623d8ddb796", "TT_en_test_language.py::test_silent_first_install[None-False]": "7476715631cb4348ca5bc8ecdac99792abfc77b10da654305e1d09ce5e838176",
"TT_en_test_language.py::test_silent_first_install[True-True]": "5620e376c365737f72b1b86d4942c162ef1f65ff159cb8262f2411e01fa0b016", "TT_en_test_language.py::test_silent_first_install[True-True]": "944168e4747abf8a2a894ed59aad65de2d0e7193488b22869e3f59be034f2e5f",
"TT_en_test_language.py::test_switch_from_english[None]": "0f92d5c7b12c2ab21152dbc9a5498859990476f91267f337dfad4b35c87e5de2", "TT_en_test_language.py::test_switch_from_english[None]": "dc4fef22119297c57f6b239143880cdd8d9f08199ff596467ab17db1a64b015d",
"TT_en_test_language.py::test_switch_from_english[True]": "0f92d5c7b12c2ab21152dbc9a5498859990476f91267f337dfad4b35c87e5de2", "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_from_english_not_silent": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_test_language.py::test_switch_language": "2d7cc1088467af2f13ec08755cd2d11e39b463209e92ef9a06d1eec0cfb8dde0", "TT_en_test_language.py::test_switch_language": "c6d3537688d4ad0169fa2e8d43d9d68f7965ac09e15e31eb618d4a40dfd9be5d",
"TT_en_test_language.py::test_translations_renders_on_screen": "9e96d81519ee2405b37a18dd856209373366665d1d9921da5ea73edcf2a74e88", "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": "0e2a71ac60add6bebc03645b6f4f6f19abdba56f1e55e799479b116e5a66aef6",
"TT_en_test_msg_applysettings.py::test_apply_homescreen_jpeg_progressive": "4d7c67024ee17436071e5cf2f79c36453249c95314a732580623cb1f1cdbfdf3", "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", "TT_en_test_msg_applysettings.py::test_apply_homescreen_jpeg_wrong_size": "4d7c67024ee17436071e5cf2f79c36453249c95314a732580623cb1f1cdbfdf3",