Icon font update

- Renamed `seedsigner-glyphs.otf` to `seedsigner-icons.otf`
- Updated `seedsigner-icons.otf`
- Renamed `SeedSignerCustomIconConstants` to `SeedSignerIconConstants`
- Updated `SeedSignerCustomIconConstants` names and Unicode values in src/seedsigner/gui/components.py
This commit is contained in:
Easy
2023-08-03 16:43:42 -07:00
parent c6df80c075
commit 40abf591d7
17 changed files with 375 additions and 73 deletions
+42 -33
View File
@@ -28,7 +28,7 @@ class GUIConstants:
REGTEST_COLOR = "#00caf1"
ICON_FONT_NAME__FONT_AWESOME = "Font_Awesome_6_Free-Solid-900"
ICON_FONT_NAME__SEEDSIGNER = "seedsigner-glyphs"
ICON_FONT_NAME__SEEDSIGNER = "seedsigner-icons"
ICON_FONT_SIZE = 22
ICON_INLINE_FONT_SIZE = 24
ICON_LARGE_BUTTON_SIZE = 36
@@ -69,10 +69,6 @@ class FontAwesomeIconConstants:
ANGLE_RIGHT = "\uf105"
ANGLE_UP = "\uf106"
CAMERA = "\uf030"
CARET_DOWN = "\uf0d7"
CARET_LEFT = "\uf0d9"
CARET_RIGHT = "\uf0da"
CARET_UP = "\uf0d8"
CHEVRON_UP = "\uf077"
CHEVRON_DOWN = "\uf078"
SOLID_CIRCLE_CHECK = "\uf058"
@@ -85,46 +81,59 @@ class FontAwesomeIconConstants:
DICE_FOUR = "\uf524"
DICE_FIVE = "\uf523"
DICE_SIX = "\uf526"
GEAR = "\uf013"
KEY = "\uf084"
KEYBOARD = "\uf11c"
LOCK = "\uf023"
MAP = "\uf279"
PAPER_PLANE = "\uf1d8"
PEN = "\uf304"
PLUS = "+"
POWER_OFF = "\uf011"
ROTATE_RIGHT = "\uf2f9"
SCREWDRIVER_WRENCH = "\uf7d9"
SDCARD = "\uf7c2"
SQUARE = "\uf0c8"
SQUARE_CARET_DOWN = "\uf150"
SQUARE_CARET_LEFT = "\uf191"
SQUARE_CARET_RIGHT = "\uf152"
SQUARE_CARET_UP = "\uf151"
SQUARE_CHECK = "\uf14a"
TRIANGLE_EXCLAMATION = "\uf071"
UNLOCK = "\uf09c"
QRCODE = "\uf029"
X = "\u0058"
SDCARD = "\uf7c2"
class SeedSignerCustomIconConstants:
LARGE_CHEVRON_LEFT = "\ue900"
SMALL_CHEVRON_RIGHT = "\ue901"
PAGE_UP = "\ue903"
PAGE_DOWN = "\ue902"
PLUS = "\ue904"
CIRCLE_CHECK = "\ue907"
CIRCLE_EXCLAMATION = "\ue908"
CIRCLE_X = "\ue909"
FINGERPRINT = "\ue90a"
PATH = "\ue90b"
BITCOIN_LOGO_STRAIGHT = "\ue90c"
BITCOIN_LOGO_TILTED = "\ue90d"
class SeedSignerIconConstants:
SCAN = "\ue900"
SEEDS = "\ue901"
SETTINGS = "\ue902"
TOOLS = "\ue903"
MIN_VALUE = LARGE_CHEVRON_LEFT
MAX_VALUE = BITCOIN_LOGO_TILTED
BACK = "\ue904"
CHEVRON_DOWN = "\ue905"
CHEVRON_LEFT = "\ue906"
CHEVRON_RIGHT = "\ue907"
CHEVRON_UP = "\ue908"
CLOSE = "\ue909"
PAGE_DOWN = "\ue90a"
PAGE_UP = "\ue90b"
PLUS = "\ue90c"
POWER = "\ue90d"
ERROR = "\ue90e"
SUCCESS = "\ue90f"
WARNING = "\ue910"
ADDRESS = "\ue911"
CHANGE = "\ue912"
DERIVATION = "\ue913"
FEE = "\ue914"
FINGERPRINT = "\ue915"
PASSPHRASE = "\ue916"
BITCOIN = "\ue917"
BITCOIN_ALT = "\ue918"
BRIGHTNESS = "\ue919"
MICROSD = "\ue91a"
QRCODE = "\ue91b"
MIN_VALUE = BACK
MAX_VALUE = BITCOIN_ALT
@@ -425,7 +434,7 @@ class TextArea(BaseComponent):
class Icon(BaseComponent):
screen_x: int = 0
screen_y: int = 0
icon_name: str = SeedSignerCustomIconConstants.BITCOIN_LOGO_TILTED
icon_name: str = SeedSignerIconConstants.BITCOIN_ALT
icon_size: int = GUIConstants.ICON_FONT_SIZE
icon_color: str = GUIConstants.BODY_FONT_COLOR
@@ -859,7 +868,7 @@ class BtcAmount(BaseComponent):
btc_icon = Icon(
image_draw=draw,
canvas=self.paste_image,
icon_name=SeedSignerCustomIconConstants.BITCOIN_LOGO_TILTED,
icon_name=SeedSignerIconConstants.BITCOIN_ALT,
icon_color=btc_color,
icon_size=self.icon_size,
screen_x=0,
@@ -947,7 +956,7 @@ class BtcAmount(BaseComponent):
btc_icon = Icon(
image_draw=draw,
canvas=self.paste_image,
icon_name=SeedSignerCustomIconConstants.BITCOIN_LOGO_TILTED,
icon_name=SeedSignerIconConstants.BITCOIN_ALT,
icon_color=btc_color,
icon_size=self.icon_size,
screen_x=0,
@@ -1279,7 +1288,7 @@ class TopNav(BaseComponent):
if self.show_back_button:
self.left_button = IconButton(
icon_name=SeedSignerCustomIconConstants.LARGE_CHEVRON_LEFT,
icon_name=SeedSignerIconConstants.BACK,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
screen_x=GUIConstants.EDGE_PADDING,
screen_y=GUIConstants.EDGE_PADDING,
@@ -1289,7 +1298,7 @@ class TopNav(BaseComponent):
if self.show_power_button:
self.right_button = IconButton(
icon_name=FontAwesomeIconConstants.POWER_OFF,
icon_name=SeedSignerIconConstants.POWER,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
screen_x=self.width - GUIConstants.TOP_NAV_BUTTON_SIZE - GUIConstants.EDGE_PADDING,
screen_y=GUIConstants.EDGE_PADDING,
+2 -2
View File
@@ -655,7 +655,7 @@ class PSBTChangeDetailsScreen(ButtonListScreen):
# Adjust the vertical spacing
screen_y -= GUIConstants.COMPONENT_PADDING
self.components.append(IconTextLine(
icon_name=SeedSignerCustomIconConstants.FINGERPRINT,
icon_name=SeedSignerIconConstants.FINGERPRINT,
icon_color="blue",
value_text=f"""{"Multisig" if self.is_multisig else self.fingerprint}: {"Change" if self.is_change_derivation_path else "Addr"} #{self.derivation_path_addr_index}""",
is_text_centered=False,
@@ -665,7 +665,7 @@ class PSBTChangeDetailsScreen(ButtonListScreen):
if self.is_change_addr_verified:
self.components.append(IconTextLine(
icon_name=SeedSignerCustomIconConstants.CIRCLE_CHECK,
icon_name=SeedSignerIconConstants.SUCCESS,
icon_color="#00dd00",
value_text="Address verified!",
is_text_centered=False,
+6 -6
View File
@@ -692,7 +692,7 @@ class QRDisplayScreen(BaseScreen):
canvas=rectangle,
screen_x=GUIConstants.EDGE_PADDING*2 + 1,
screen_y=GUIConstants.COMPONENT_PADDING + 4, # +4 fudge factor to account for where the chevron is drawn relative to baseline
icon_name=FontAwesomeIconConstants.CHEVRON_UP,
icon_name=SeedSignerIconConstants.CHEVRON_UP,
icon_size=GUIConstants.BODY_FONT_SIZE,
)
chevron_up_icon.render()
@@ -702,7 +702,7 @@ class QRDisplayScreen(BaseScreen):
canvas=rectangle,
screen_x=chevron_up_icon.screen_x,
screen_y=chevron_up_icon.screen_y + chevron_up_icon.icon_size + GUIConstants.BODY_LINE_SPACING,
icon_name=FontAwesomeIconConstants.CHEVRON_DOWN,
icon_name=SeedSignerIconConstants.CHEVRON_DOWN,
icon_size=chevron_up_icon.icon_size,
)
chevron_down_icon.render()
@@ -824,7 +824,7 @@ class QRDisplayScreen(BaseScreen):
@dataclass
class LargeIconStatusScreen(ButtonListScreen):
title: str = "Success!"
status_icon_name: str = SeedSignerCustomIconConstants.CIRCLE_CHECK
status_icon_name: str = SeedSignerIconConstants.SUCCESS
status_icon_size: int = GUIConstants.ICON_PRIMARY_SCREEN_SIZE
status_color: str = GUIConstants.SUCCESS_COLOR
status_headline: str = "Success!" # The colored text under the large icon
@@ -948,7 +948,7 @@ class WarningEdgesMixin:
@dataclass
class WarningScreen(WarningEdgesMixin, LargeIconStatusScreen):
title: str = "Caution"
status_icon_name: str = SeedSignerCustomIconConstants.CIRCLE_EXCLAMATION
status_icon_name: str = SeedSignerIconConstants.WARNING
status_color: str = "yellow"
status_headline: str = "Privacy Leak!" # The colored text under the alert icon
@@ -1242,7 +1242,7 @@ class MicroSDToastScreen(BaseScreen):
if self.action == MicroSD.ACTION__REMOVED:
self.toast = ToastOverlay(
icon_name=FontAwesomeIconConstants.SDCARD,
icon_name=SeedSignerIconConstants.MICROSD,
color=GUIConstants.NOTIFICATION_COLOR,
label_text="MicroSD removed"
)
@@ -1250,7 +1250,7 @@ class MicroSDToastScreen(BaseScreen):
elif self.action == MicroSD.ACTION__INSERTED:
self.toast = ToastOverlay(
icon_name=FontAwesomeIconConstants.SDCARD,
icon_name=SeedSignerIconConstants.MICROSD,
color=GUIConstants.NOTIFICATION_COLOR,
label_text="MicroSD inserted"
)
+5 -5
View File
@@ -421,7 +421,7 @@ class SeedFinalizeScreen(ButtonListScreen):
super().__post_init__()
self.fingerprint_icontl = IconTextLine(
icon_name=SeedSignerCustomIconConstants.FINGERPRINT,
icon_name=SeedSignerIconConstants.FINGERPRINT,
icon_color="blue",
icon_size=GUIConstants.ICON_FONT_SIZE + 12,
label_text="fingerprint",
@@ -441,7 +441,7 @@ class SeedOptionsScreen(ButtonListScreen):
has_passphrase: bool = False
def __post_init__(self):
self.top_nav_icon_name = SeedSignerCustomIconConstants.FINGERPRINT
self.top_nav_icon_name = SeedSignerIconConstants.FINGERPRINT
self.top_nav_icon_color = "blue"
self.title = self.fingerprint
self.is_button_text_centered = False
@@ -584,7 +584,7 @@ class SeedExportXpubDetailsScreen(WarningEdgesMixin, ButtonListScreen):
# Set up the fingerprint and passphrase displays
self.fingerprint_line = IconTextLine(
icon_name=SeedSignerCustomIconConstants.FINGERPRINT,
icon_name=SeedSignerIconConstants.FINGERPRINT,
icon_color="blue",
label_text="Fingerprint",
value_text=self.fingerprint,
@@ -594,7 +594,7 @@ class SeedExportXpubDetailsScreen(WarningEdgesMixin, ButtonListScreen):
self.components.append(self.fingerprint_line)
self.derivation_line = IconTextLine(
icon_name=SeedSignerCustomIconConstants.PATH,
icon_name=SeedSignerIconConstants.DERIVATION,
label_text="Derivation",
value_text=self.derivation_path,
screen_x=GUIConstants.COMPONENT_PADDING,
@@ -1009,7 +1009,7 @@ class SeedReviewPassphraseScreen(ButtonListScreen):
super().__post_init__()
self.components.append(IconTextLine(
icon_name=SeedSignerCustomIconConstants.FINGERPRINT,
icon_name=SeedSignerIconConstants.FINGERPRINT,
icon_color="blue",
label_text="changes fingerprint",
value_text=f"{self.fingerprint_without} >> {self.fingerprint_with}",
+4 -4
View File
@@ -362,7 +362,7 @@ class ToolsCalcFinalWordDoneScreen(ButtonListScreen):
))
self.components.append(IconTextLine(
icon_name=SeedSignerCustomIconConstants.FINGERPRINT,
icon_name=SeedSignerIconConstants.FINGERPRINT,
icon_color="blue",
label_text="fingerprint",
value_text=self.fingerprint,
@@ -386,7 +386,7 @@ class ToolsAddressExplorerAddressTypeScreen(ButtonListScreen):
if self.fingerprint:
self.components.append(IconTextLine(
icon_name=SeedSignerCustomIconConstants.FINGERPRINT,
icon_name=SeedSignerIconConstants.FINGERPRINT,
icon_color="blue",
label_text="Fingerprint",
value_text=self.fingerprint,
@@ -396,7 +396,7 @@ class ToolsAddressExplorerAddressTypeScreen(ButtonListScreen):
if self.script_type != SettingsConstants.CUSTOM_DERIVATION:
self.components.append(IconTextLine(
icon_name=SeedSignerCustomIconConstants.PATH,
icon_name=SeedSignerIconConstants.DERIVATION,
label_text="Derivation",
value_text=SettingsDefinition.get_settings_entry(attr_name=SettingsConstants.SETTING__SCRIPT_TYPES).get_selection_option_display_name_by_value(value=self.script_type),
screen_x=GUIConstants.EDGE_PADDING,
@@ -404,7 +404,7 @@ class ToolsAddressExplorerAddressTypeScreen(ButtonListScreen):
))
else:
self.components.append(IconTextLine(
icon_name=SeedSignerCustomIconConstants.PATH,
icon_name=SeedSignerIconConstants.DERIVATION,
label_text="Derivation",
value_text=self.custom_derivation_path,
screen_x=GUIConstants.EDGE_PADDING,
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
class PSBTSelectSeedView(View):
SCAN_SEED = ("Scan a seed", FontAwesomeIconConstants.QRCODE)
SCAN_SEED = ("Scan a seed", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
button_data = []
@@ -41,7 +41,7 @@ class PSBTSelectSeedView(View):
# Doesn't look like this seed can sign the current PSBT
button_str += " (?)"
self.button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue"))
self.button_data.append((button_str, SeedSignerIconConstants.FINGERPRINT, "blue"))
self.button_data.append(self.SCAN_SEED)
self.button_data.append(self.TYPE_12WORD)
@@ -531,7 +531,7 @@ class PSBTSigningErrorView(View):
# Just a WarningScreen here; only use DireWarningScreen for true security risks.
selected_menu_num = WarningScreen(
title="PSBT Error",
status_icon_name=SeedSignerCustomIconConstants.CIRCLE_EXCLAMATION,
status_icon_name=SeedSignerIconConstants.WARNING,
status_headline="Signing Failed",
text="Signing with this seed did not add a valid signature.",
button_data=["Select Diff Seed"],
+9 -9
View File
@@ -49,7 +49,7 @@ class SeedsMenuView(View):
button_data = []
for seed in self.seeds:
button_data.append((seed["fingerprint"], SeedSignerCustomIconConstants.FINGERPRINT, "blue"))
button_data.append((seed["fingerprint"], SeedSignerIconConstants.FINGERPRINT, "blue"))
button_data.append("Load a seed")
selected_menu_num = self.run_screen(
@@ -74,10 +74,10 @@ class SeedsMenuView(View):
Loading seeds, passphrases, etc
****************************************************************************"""
class LoadSeedView(View):
SEED_QR = (" Scan a SeedQR", FontAwesomeIconConstants.QRCODE)
SEED_QR = (" Scan a SeedQR", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
CREATE = (" Create a seed", FontAwesomeIconConstants.PLUS)
CREATE = (" Create a seed", SeedSignerIconConstants.PLUS)
def run(self):
button_data = [
@@ -340,11 +340,11 @@ class SeedDiscardView(View):
Views for actions on individual seeds:
****************************************************************************"""
class SeedOptionsView(View):
SCAN_PSBT = ("Scan PSBT", FontAwesomeIconConstants.QRCODE)
SCAN_PSBT = ("Scan PSBT", SeedSignerIconConstants.QRCODE)
VERIFY_ADDRESS = "Verify Addr"
EXPORT_XPUB = "Export Xpub"
EXPLORER = "Address Explorer"
BACKUP = ("Backup Seed", None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT)
BACKUP = ("Backup Seed", None, None, None, SeedSignerIconConstants.CHEVRON_RIGHT)
BIP85_CHILD_SEED = "BIP-85 Child Seed"
DISCARD = ("Discard Seed", None, None, "red")
@@ -1354,7 +1354,7 @@ class SeedTranscribeSeedQRConfirmQRPromptView(View):
def run(self):
SCAN = ("Confirm SeedQR", FontAwesomeIconConstants.QRCODE)
SCAN = ("Confirm SeedQR", SeedSignerIconConstants.QRCODE)
DONE = "Done"
button_data = [SCAN, DONE]
@@ -1529,7 +1529,7 @@ class SeedSingleSigAddressVerificationSelectSeedView(View):
def run(self):
seeds = self.controller.storage.seeds
SCAN_SEED = ("Scan a seed", FontAwesomeIconConstants.QRCODE)
SCAN_SEED = ("Scan a seed", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
button_data = []
@@ -1538,7 +1538,7 @@ class SeedSingleSigAddressVerificationSelectSeedView(View):
for seed in seeds:
button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue"))
button_data.append((button_str, SeedSignerIconConstants.FINGERPRINT, "blue"))
text = "Select seed to verify"
button_data.append(SCAN_SEED)
@@ -1784,7 +1784,7 @@ class AddressVerificationSuccessView(View):
class LoadMultisigWalletDescriptorView(View):
def run(self):
SCAN = ("Scan Descriptor", FontAwesomeIconConstants.QRCODE)
SCAN = ("Scan Descriptor", SeedSignerIconConstants.QRCODE)
CANCEL = "Cancel"
button_data = [SCAN, CANCEL]
selected_menu_num = seed_screens.LoadMultisigWalletDescriptorScreen(
+2 -2
View File
@@ -40,7 +40,7 @@ class SettingsMenuView(View):
title = "Settings"
# Set up the next nested level of menuing
button_data.append(("Advanced", None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT))
button_data.append(("Advanced", None, None, None, SeedSignerIconConstants.CHEVRON_RIGHT))
next_destination = Destination(SettingsMenuView, view_args={"visibility": SettingsConstants.VISIBILITY__ADVANCED})
button_data.append(self.IO_TEST)
@@ -50,7 +50,7 @@ class SettingsMenuView(View):
title = "Advanced"
# So far there are no real Developer options; disabling for now
# button_data.append(("Developer Options", None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT))
# button_data.append(("Developer Options", None, None, None, SeedSignerIconConstants.CHEVRON_RIGHT))
# next_destination = Destination(SettingsMenuView, view_args={"visibility": SettingsConstants.VISIBILITY__DEVELOPER})
next_destination = None
+4 -4
View File
@@ -425,8 +425,8 @@ class ToolsCalcFinalWordDoneView(View):
Address Explorer Views
****************************************************************************"""
class ToolsAddressExplorerSelectSourceView(View):
SCAN_SEED = ("Scan a seed", FontAwesomeIconConstants.QRCODE)
SCAN_DESCRIPTOR = ("Scan wallet descriptor", FontAwesomeIconConstants.QRCODE)
SCAN_SEED = ("Scan a seed", SeedSignerIconConstants.QRCODE)
SCAN_DESCRIPTOR = ("Scan wallet descriptor", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
@@ -436,7 +436,7 @@ class ToolsAddressExplorerSelectSourceView(View):
button_data = []
for seed in seeds:
button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue"))
button_data.append((button_str, SeedSignerIconConstants.FINGERPRINT, "blue"))
button_data = button_data + [self.SCAN_SEED, self.SCAN_DESCRIPTOR, self.TYPE_12WORD, self.TYPE_24WORD]
selected_menu_num = self.run_screen(
@@ -630,7 +630,7 @@ class ToolsAddressExplorerAddressListView(View):
end_digits = -4
button_data.append(f"{cur_index}:{address[:8]}...{address[end_digits:]}")
button_data.append(("Next {}".format(addrs_per_screen), None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT))
button_data.append(("Next {}".format(addrs_per_screen), None, None, None, SeedSignerIconConstants.CHEVRON_RIGHT))
selected_menu_num = self.run_screen(
ButtonListScreen,
+5 -5
View File
@@ -137,10 +137,10 @@ class Destination:
#
#########################################################################################
class MainMenuView(View):
SCAN = ("Scan", FontAwesomeIconConstants.QRCODE)
SEEDS = ("Seeds", FontAwesomeIconConstants.KEY)
TOOLS = ("Tools", FontAwesomeIconConstants.SCREWDRIVER_WRENCH)
SETTINGS = ("Settings", FontAwesomeIconConstants.GEAR)
SCAN = ("Scan", SeedSignerIconConstants.SCAN)
SEEDS = ("Seeds", SeedSignerIconConstants.SEEDS)
TOOLS = ("Tools", SeedSignerIconConstants.TOOLS)
SETTINGS = ("Settings", SeedSignerIconConstants.SETTINGS)
def run(self):
button_data = [self.SCAN, self.SEEDS, self.TOOLS, self.SETTINGS]
@@ -176,7 +176,7 @@ class MainMenuView(View):
class PowerOptionsView(View):
RESET = ("Restart", FontAwesomeIconConstants.ROTATE_RIGHT)
POWER_OFF = ("Power Off", FontAwesomeIconConstants.POWER_OFF)
POWER_OFF = ("Power Off", SeedSignerIconConstants.POWER)
def run(self):
button_data = [self.RESET, self.POWER_OFF]
+8
View File
@@ -0,0 +1,8 @@
# Screenshot Generator
From the project root, run:
```
pytest tests/screenshot_generator/generator.py --locale en
```
By default it writes the screenshots to a dir that's parallel to the project root: `seedsigner-screenshots/<locale>`.
+11
View File
@@ -0,0 +1,11 @@
import pytest
def pytest_addoption(parser):
parser.addoption("--locale", action="store", default=None)
@pytest.fixture(scope='session')
def target_locale(request):
return request.config.option.locale
+221
View File
@@ -0,0 +1,221 @@
import embit
import os
import pathlib
import pytest
from mock import Mock, patch
from seedsigner.controller import Controller
from seedsigner.gui.renderer import Renderer
from seedsigner.hardware.buttons import HardwareButtons
from seedsigner.hardware.camera import Camera
from seedsigner.models.decode_qr import DecodeQR
from seedsigner.models.qr_type import QRType
from seedsigner.models.seed import Seed
from seedsigner.models.settings import Settings
from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition
from seedsigner.views import (main_menu_views, psbt_views, scan_views, seed_views,
settings_views, tools_views)
from seedsigner.views.view import View
from .utils import ScreenshotComplete, ScreenshotRenderer
def test_generate_screenshots(target_locale):
"""
The `Renderer` class is mocked so that calls in the normal code are ignored
(necessary to avoid having it trying to wire up hardware dependencies).
When the `Renderer` instance is needed, we patch in our own test-only
`ScreenshotRenderer`.
"""
# Disable hardware dependencies by essentially wiping out this class
HardwareButtons.get_instance = Mock()
Camera.get_instance = Mock()
# Prep the ScreenshotRenderer that will be patched over the normal Renderer
screenshot_root = "/Users/kdmukai/dev/seedsigner-screenshots"
ScreenshotRenderer.configure_instance()
screenshot_renderer: ScreenshotRenderer = ScreenshotRenderer.get_instance()
# Replace the core `Singleton` calls so that only our ScreenshotRenderer is used.
Renderer.configure_instance = Mock()
Renderer.get_instance = Mock(return_value=screenshot_renderer)
controller = Controller.get_instance()
# Set up some test data that we'll need in the `Controller` for certain Views
mnemonic_12 = "forum undo fragile fade shy sign arrest garment culture tube off merit".split()
mnemonic_24 = "attack pizza motion avocado network gather crop fresh patrol unusual wild holiday candy pony ranch winter theme error hybrid van cereal salon goddess expire".split()
mnemonic_12b = ["abandon"] * 11 + ["about"]
seed_12 = Seed(mnemonic=mnemonic_12, passphrase="cap*BRACKET3stove", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
seed_12b = Seed(mnemonic=mnemonic_12b, wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
seed_24 = Seed(mnemonic=mnemonic_24, passphrase="some-PASS*phrase9", wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
controller.storage.seeds.append(seed_12)
controller.storage.seeds.append(seed_12b)
controller.storage.set_pending_seed(seed_24)
# Load a PSBT into memory
BASE64_PSBT_1 = """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"""
decoder = DecodeQR()
decoder.add_data(BASE64_PSBT_1)
controller.psbt = decoder.get_psbt()
controller.psbt_seed = seed_12b
# Multisig wallet descriptor for the multisig in the above PSBT
MULTISIG_WALLET_DESCRIPTOR = """wsh(sortedmulti(1,[22bde1a9/48h/1h/0h/2h]tpubDFfsBrmpj226ZYiRszYi2qK6iGvh2vkkghfGB2YiRUVY4rqqedHCFEgw12FwDkm7rUoVtq9wLTKc6BN2sxswvQeQgp7m8st4FP8WtP8go76/{0,1}/*,[73c5da0a/48h/1h/0h/2h]tpubDFH9dgzveyD8zTbPUFuLrGmCydNvxehyNdUXKJAQN8x4aZ4j6UZqGfnqFrD4NqyaTVGKbvEW54tsvPTK2UoSbCC1PJY8iCNiwTL3RWZEheQ/{0,1}/*))#3jhtf6yx"""
controller.multisig_wallet_descriptor = embit.descriptor.Descriptor.from_string(MULTISIG_WALLET_DESCRIPTOR)
# Parse the main `babel/messages.pot` for overall stats
messages_source_path = os.path.join(pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve(), "babel", "messages.pot")
with open(messages_source_path, 'r') as messages_source_file:
num_source_messages = messages_source_file.read().count("msgid \"") - 1
def screencap_view(view_cls: View, view_name: str, view_args: dict={}):
screenshot_renderer.set_screenshot_filename(f"{view_name}.png")
try:
print(f"Running {view_name}")
view_cls(**view_args).run()
except ScreenshotComplete:
# Slightly hacky way to exit ScreenshotRenderer as expected
pass
print(f"Completed {view_name}")
except Exception as e:
# Something else went wrong
print(repr(e))
raise e
# Automatically populate all Settings options Views
settings_views_list = []
settings_views_list.append(settings_views.SettingsMenuView)
for settings_entry in SettingsDefinition.settings_entries:
if settings_entry.visibility == SettingsConstants.VISIBILITY__HIDDEN:
continue
settings_views_list.append((settings_views.SettingsEntryUpdateSelectionView, dict(attr_name=settings_entry.attr_name), f"SettingsEntryUpdateSelectionView_{settings_entry.attr_name}"))
settings_views_list.append(settings_views.IOTestView)
settings_views_list.append(settings_views.DonateView)
screenshot_sections = {
"Main Menu Views": [
main_menu_views.MainMenuView,
main_menu_views.PowerOffView,
(scan_views.SettingsUpdatedView, dict(config_name="Keith's Settings")),
],
"Seed Views": [
seed_views.SeedsMenuView,
seed_views.LoadSeedView,
seed_views.SeedMnemonicEntryView,
seed_views.SeedMnemonicInvalidView,
seed_views.SeedFinalizeView,
seed_views.SeedPassphraseWarningView,
seed_views.SeedAddPassphraseView,
seed_views.SeedReviewPassphraseView,
(seed_views.SeedOptionsView, dict(seed_num=0)),
(seed_views.SeedBackupView, dict(seed_num=0)),
(seed_views.SeedWordsWarningView, dict(seed_num=0)),
(seed_views.SeedWordsView, dict(seed_num=0)),
(seed_views.SeedWordsView, dict(seed_num=0, page_index=2), "SeedWordsView_2"),
(seed_views.SeedTranscribeSeedQRFormatView, dict(seed_num=0)),
(seed_views.SeedTranscribeSeedQRWarningView, dict(seed_num=0)),
(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=0, seedqr_format=QRType.SEED__SEEDQR, num_modules=25), "SeedTranscribeSeedQRWholeQRView_12_Standard"),
(seed_views.SeedTranscribeSeedQRWholeQRView, dict(seed_num=0, seedqr_format=QRType.SEED__COMPACTSEEDQR, num_modules=21), "SeedTranscribeSeedQRWholeQRView_12_Compact"),
# Screenshot doesn't render properly due to how the transparency mask is pre-rendered
# (seed_views.SeedTranscribeSeedQRZoomedInView, dict(seed_num=0, seedqr_format=QRType.SEED__SEEDQR)),
(seed_views.SeedTranscribeSeedQRConfirmQRPromptView, dict(seed_num=0)),
# Screenshot can't render live preview screens
# (seed_views.SeedTranscribeSeedQRConfirmScanView, dict(seed_num=0)),
seed_views.LoadMultisigWalletDescriptorView,
seed_views.MultisigWalletDescriptorView,
],
"PSBT Views": [
psbt_views.PSBTSelectSeedView,
psbt_views.PSBTOverviewView,
psbt_views.PSBTUnsupportedScriptTypeWarningView,
psbt_views.PSBTNoChangeWarningView,
psbt_views.PSBTMathView,
(psbt_views.PSBTAddressDetailsView, dict(address_num=0)),
# TODO: Render Multisig change w/ and w/out the multisig wallet descriptor onboard
(psbt_views.PSBTChangeDetailsView, dict(change_address_num=0)),
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_change"),
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_selftransfer"),
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=True), "PSBTAddressVerificationFailedView_multisig_change"),
(psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=True), "PSBTAddressVerificationFailedView_multisig_selftransfer"),
psbt_views.PSBTFinalizeView,
psbt_views.PSBTSelectCoordinatorView,
psbt_views.PSBTSigningErrorView,
],
"Tools Views": [
tools_views.ToolsMenuView,
tools_views.ToolsDiceEntropyMnemonicLengthView,
(tools_views.ToolsDiceEntropyEntryView, dict(total_rolls=50)),
],
"Settings Views": settings_views_list,
}
locales = []
if target_locale is None:
locales = SettingsConstants.ALL_LOCALES
else:
locales = [locale_tuple for locale_tuple in SettingsConstants.ALL_LOCALES if locale_tuple[0] == target_locale]
if not locales:
raise Exception(f"Invalid locale: {target_locale}")
for locale, display_name in locales:
Settings.get_instance().set_value(SettingsConstants.SETTING__LOCALE, value=locale)
screenshot_renderer.set_screenshot_path(os.path.join(screenshot_root, locale))
locale_readme = f"""# SeedSigner Screenshots: {display_name}\n"""
# Report the translation progress
if locale != SettingsConstants.LOCALE__ENGLISH:
translated_messages_path = os.path.join(pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve(), "src", "seedsigner", "resources", "babel", locale, "LC_MESSAGES", "messages.po")
with open(translated_messages_path, 'r') as translation_file:
locale_translations = translation_file.read()
num_locale_translations = locale_translations.count("msgid \"") - locale_translations.count("""msgstr ""\n\n""") - 1
locale_readme += f"## Translation progress: {num_locale_translations / num_source_messages:.1%}\n\n"
locale_readme += "---\n\n"
for section_name, screenshot_list in screenshot_sections.items():
locale_readme += "\n\n---\n\n"
locale_readme += f"## {section_name}\n\n"
locale_readme += """<table style="border: 0;">"""
locale_readme += f"""<tr><td align="center">"""
for screenshot in screenshot_list:
if type(screenshot) == tuple:
if len(screenshot) == 2:
view_cls, view_args = screenshot
view_name = view_cls.__name__
elif len(screenshot) == 3:
view_cls, view_args, view_name = screenshot
else:
view_cls = screenshot
view_args = {}
view_name = view_cls.__name__
screencap_view(view_cls, view_name, view_args)
locale_readme += """<table align="left" style="border: 1px solid gray;">"""
locale_readme += f"""<tr><td align="center">{view_name}<br/><br/><img src="{view_name}.png"></td></tr>"""
locale_readme += """</table>"""
locale_readme += "</td></tr></table>"
with open(os.path.join(screenshot_renderer.screenshot_path, "README.md"), 'w') as readme_file:
readme_file.write(locale_readme)
# Write the main README; ensure it writes all locales, not just the one that may
# have been specified for this run.
main_readme = """# SeedSigner Screenshots \n\n"""
for locale, display_name in SettingsConstants.ALL_LOCALES:
main_readme += f"* [{display_name}]({locale}/README.md)\n"
with open(os.path.join(screenshot_root, "README.md"), 'w') as readme_file:
readme_file.write(main_readme)
+53
View File
@@ -0,0 +1,53 @@
import os
from PIL import Image, ImageDraw
from seedsigner.gui.renderer import Renderer
class ScreenshotComplete(Exception):
pass
class ScreenshotRenderer(Renderer):
screenshot_path: str = None
screenshot_filename: str = None
@classmethod
def configure_instance(cls):
# Instantiate the one and only Renderer instance
renderer = cls.__new__(cls)
cls._instance = renderer
# Hard-coding output values for now
renderer.canvas_width = 240
renderer.canvas_height = 240
renderer.canvas = Image.new('RGB', (renderer.canvas_width, renderer.canvas_height))
renderer.draw = ImageDraw.Draw(renderer.canvas)
def set_screenshot_filename(self, filename:str):
self.screenshot_filename = filename
def set_screenshot_path(self, path):
if not os.path.exists(path):
os.makedirs(path)
self.screenshot_path = path
def show_image(self, image=None, alpha_overlay=None, is_background_thread: bool = False):
if is_background_thread:
return
if alpha_overlay:
if image == None:
image = self.canvas
image = Image.alpha_composite(image, alpha_overlay)
if image:
# Always write to the current canvas, rather than trying to replace it
self.canvas.paste(image)
self.canvas.save(os.path.join(self.screenshot_path, self.screenshot_filename))
raise ScreenshotComplete()