Interim commit

Initial implementation of new `view_function` Controller loop and `back_stack`.
This commit is contained in:
kdmukai
2021-09-27 22:43:43 -05:00
parent a6e4728b7a
commit b8d33dd4ec
9 changed files with 364 additions and 347 deletions
+1
View File
@@ -0,0 +1 @@
from .controller import Controller
+129 -61
View File
@@ -46,7 +46,7 @@ class Controller(ConfigurableSingleton):
cls._instance = controller
# Input Buttons
controller.buttons = Buttons()
controller.buttons = Buttons.get_instance()
# models
controller.storage = SeedStorage()
@@ -60,6 +60,9 @@ class Controller(ConfigurableSingleton):
# Configure the Renderer
Renderer.configure_instance({"text_color": controller.color})
# TODO: Refactor so that we don't need the Renderer here
controller.renderer = Renderer.get_instance()
# Views
controller.menu_view = MenuView()
controller.seed_tools_view = SeedToolsView()
@@ -68,6 +71,8 @@ class Controller(ConfigurableSingleton):
controller.settings_tools_view = SettingsToolsView()
controller.screensaver = ScreensaverView(controller.buttons)
controller.back_stack = []
# Other behavior constants
controller.screensaver_activation_ms = 120 * 1000
@@ -78,11 +83,74 @@ class Controller(ConfigurableSingleton):
return Camera.get_instance()
def pop_back_stack(self):
if len(self.back_stack) > 0:
return self.back_stack.pop()
return (None, {})
def start(self) -> None:
opening_splash = OpeningSplashView()
# opening_splash.start()
""" Functions can be stored as variables in python!
This loop receives a view function to execute and stores it in the
`view_function` var along with any input arguments in the `function_args`
dict.
The `view_function` is executed w/its `function_args`. Each view returns either
a new view function to execute next or None.
Example:
def my_view(some_arg, other_arg):
print(other_arg)
def other_view():
return (my_view, {"some_arg": 1, "other_arg": "hello"})
When `other_view` is run, we capture its return values:
(view_function, function_args) = other_view()
And then we can execute that view function:
view_function(**function_args)
"""
try:
view_function = None
function_args = {}
while True:
if not view_function:
# None is a special case; render the Home screen
view_function = self.menu_view.display_main_menu
function_args = {}
# Home always wipes the back_stack
self.back_stack = []
print(f"Executing {view_function}")
# Each view returns the next view function to display plus any
# function_args in an argument dict.
(view_function, function_args) = view_function(**function_args)
print(f"view_function: {view_function}")
if view_function == self.pop_back_stack:
# "Back" arrow was clicked; load the previous view
(view_function, function_args) = self.pop_back_stack()
else:
self.back_stack.append((view_function, function_args))
finally:
# Clear the screen when exiting
self.renderer.display_blank_screen()
from seedsigner.gui.templates import BaseScreen, ButtonListScreen, FontTesterScreen, BottomButtonScreen
from seedsigner.gui.components import Fonts
opening_splash = OpeningSplashView()
opening_splash.start()
# tests = [
# ("OpenSans-SemiBold", 16),
@@ -238,7 +306,7 @@ class Controller(ConfigurableSingleton):
self.show_main_menu()
finally:
# Clear the screen when exiting
self.menu_view.display_blank_screen()
self.renderer.display_blank_screen()
else:
# Handle Unexpected crashes by restarting up to 3 times
@@ -251,12 +319,12 @@ class Controller(ConfigurableSingleton):
break
else:
print('Caught this error: ' + repr(error)) # debug
self.menu_view.draw_modal(["Crashed ..."], "", "restarting")
self.renderer.draw_modal(["Crashed ..."], "", "restarting")
time.sleep(5)
crash_cnt += 1
self.menu_view.draw_modal(["Crashed ..."], "", "requires hard restart")
self.renderer.draw_modal(["Crashed ..."], "", "requires hard restart")
def start_screensaver(self):
@@ -315,7 +383,7 @@ class Controller(ConfigurableSingleton):
def show_power_off(self):
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Power Off?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Power Off?")
if r == 1: #Yes
self.menu_view.display_power_off_screen()
call("sudo shutdown --poweroff now", shell=True)
@@ -358,12 +426,12 @@ class Controller(ConfigurableSingleton):
# Ask to save seed
if self.storage.slot_avaliable():
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,2,None)
if slot_num in (1,2,3):
self.storage.add_seed(seed, slot_num)
self.menu_view.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Main Menu")
self.renderer.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Main Menu")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
@@ -392,12 +460,12 @@ class Controller(ConfigurableSingleton):
# Ask to save seed
if self.storage.slot_avaliable():
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,2,None)
if slot_num in (1,2,3):
self.storage.add_seed(seed, slot_num)
self.menu_view.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Main Menu")
self.renderer.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Main Menu")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
@@ -430,12 +498,12 @@ class Controller(ConfigurableSingleton):
# Ask to save seed
if self.storage.slot_avaliable():
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,2,None)
if slot_num in (1,2,3):
self.storage.add_seed(seed, slot_num)
self.menu_view.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Main Menu")
self.renderer.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Main Menu")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
@@ -487,11 +555,11 @@ class Controller(ConfigurableSingleton):
if not seed:
# Seed is not valid, Exit if not valid with message
self.menu_view.draw_modal(["Seed Invalid", "check seed phrase", "and try again", ""], "", "Right to Continue")
self.renderer.draw_modal(["Seed Invalid", "check seed phrase", "and try again", ""], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.SEED_TOOLS_SUB_MENU
else:
self.menu_view.draw_modal(["Valid Seed!"], "", "Right to Continue")
self.renderer.draw_modal(["Valid Seed!"], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
while display_saved_seed == False:
@@ -504,7 +572,7 @@ class Controller(ConfigurableSingleton):
if seed:
self.storage.add_seed(seed, slot_num)
self.menu_view.draw_modal(["", "Saved to Slot #" + str(slot_num)], "", "Right to Exit")
self.renderer.draw_modal(["", "Saved to Slot #" + str(slot_num)], "", "Right to Exit")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.SEED_TOOLS_SUB_MENU
@@ -516,7 +584,7 @@ class Controller(ConfigurableSingleton):
r = 0
if self.storage.num_of_saved_seeds() == 0:
self.menu_view.draw_modal(["Store a seed phrase", "prior to adding", "a passphrase"], "Error", "Right to Continue")
self.renderer.draw_modal(["Store a seed phrase", "prior to adding", "a passphrase"], "Error", "Right to Continue")
self.buttons.wait_for([B.KEY_RIGHT])
return Path.SEED_TOOLS_SUB_MENU
@@ -533,11 +601,11 @@ class Controller(ConfigurableSingleton):
if self.storage.check_slot_passphrase(slot_num) == True:
# only display menu to remove/update if there is a passphrase to remove
r = self.menu_view.display_generic_selection_menu(["... [ Return to Seed Tools ]", "Change Passphrase", "Remove Passphrase"], "Passphrase Action")
r = self.renderer.display_generic_selection_menu(["... [ Return to Seed Tools ]", "Change Passphrase", "Remove Passphrase"], "Passphrase Action")
if r == 3:
# Remove Passphrase Workflow
self.storage.delete_passphrase(slot_num)
self.menu_view.draw_modal(["Passphrase Deleted", "from Slot #" + str(slot_num)], "", "Right to Continue")
self.renderer.draw_modal(["Passphrase Deleted", "from Slot #" + str(slot_num)], "", "Right to Continue")
self.buttons.wait_for([B.KEY_RIGHT])
return Path.SEED_TOOLS_SUB_MENU
@@ -552,9 +620,9 @@ class Controller(ConfigurableSingleton):
self.storage.save_passphrase(passphrase, slot_num)
if r in (0,1):
self.menu_view.draw_modal(["Passphrase Added", passphrase, "Added to Slot #" + str(slot_num)], "", "Right to Continue")
self.renderer.draw_modal(["Passphrase Added", passphrase, "Added to Slot #" + str(slot_num)], "", "Right to Continue")
elif r == 2:
self.menu_view.draw_modal(["Passphrase Updated", passphrase, "Added to Slot #" + str(slot_num)], "", "Right to Continue")
self.renderer.draw_modal(["Passphrase Updated", passphrase, "Added to Slot #" + str(slot_num)], "", "Right to Continue")
self.buttons.wait_for([B.KEY_RIGHT])
return Path.SEED_TOOLS_SUB_MENU
@@ -570,7 +638,7 @@ class Controller(ConfigurableSingleton):
# If there is a saved seed, ask to use saved seed
if self.storage.num_of_saved_seeds() > 0:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Use Saved Seed?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Use Saved Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,3,None)
if slot_num not in (1,2,3):
@@ -595,21 +663,21 @@ class Controller(ConfigurableSingleton):
# check if seed phrase is valid
if not seed:
self.menu_view.draw_modal(["Seed Invalid", "check seed phrase", "and try again"], "", "Right to Continue")
self.renderer.draw_modal(["Seed Invalid", "check seed phrase", "and try again"], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Add Seed Passphrase?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Add Seed Passphrase?")
if r == 1:
# display a tool to pick letters/numbers to make a passphrase
seed.passphrase = self.seed_tools_view.draw_passphrase_keyboard_entry()
if len(seed.passphrase) == 0:
self.menu_view.draw_modal(["No passphrase added", "to seed words"], "", "Left to Exit, Right to Continue")
self.renderer.draw_modal(["No passphrase added", "to seed words"], "", "Left to Exit, Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT])
if input == B.KEY_LEFT:
return Path.MAIN_MENU
else:
self.menu_view.draw_modal(["Optional passphrase", "added to seed words", seed.passphrase], "", "Right to Continue")
self.renderer.draw_modal(["Optional passphrase", "added to seed words", seed.passphrase], "", "Right to Continue")
self.buttons.wait_for([B.KEY_RIGHT])
# display seed phrase
@@ -625,14 +693,14 @@ class Controller(ConfigurableSingleton):
wallet_type = "multisig"
script_type = "native segwit"
derivation = self.settings.custom_derivation
r = self.menu_view.display_generic_selection_menu(["Single Sig", "Multisig"], "Wallet Type?")
r = self.renderer.display_generic_selection_menu(["Single Sig", "Multisig"], "Wallet Type?")
if r == 1:
wallet_type = "single sig"
elif r == 2:
wallet_type = "multisig"
# choose derivation standard
r = self.menu_view.display_generic_selection_menu(["Native Segwit", "Nested Segwit", "Custom"], "Derivation Path?")
r = self.renderer.display_generic_selection_menu(["Native Segwit", "Nested Segwit", "Custom"], "Derivation Path?")
if r == 1:
script_type = "native segwit"
elif r == 2:
@@ -648,12 +716,12 @@ class Controller(ConfigurableSingleton):
derivation = Settings.calc_derivation(self.settings.network, wallet_type, script_type)
if derivation == "" or derivation == None:
self.menu_view.draw_modal(["Invalid Derivation", "try again"], "", "Right to Continue")
self.renderer.draw_modal(["Invalid Derivation", "try again"], "", "Right to Continue")
return Path.SEED_TOOLS_SUB_MENU
if self.settings.software == "Prompt":
lines = ["Specter Desktop", "Blue Wallet", "Sparrow"]
r = self.menu_view.display_generic_selection_menu(lines, "Which Wallet?")
r = self.renderer.display_generic_selection_menu(lines, "Which Wallet?")
qr_xpub_type = Settings.getXPubType(lines[r-1])
else:
qr_xpub_type = self.settings.qr_xpub_type
@@ -697,7 +765,7 @@ class Controller(ConfigurableSingleton):
# reusable qr scan function
def scan_qr(scan_text="Scan QR"):
# Scan QR using Camera
self.menu_view.draw_modal(["Initializing Camera"])
self.renderer.draw_modal(["Initializing Camera"])
self.camera.start_video_stream_mode(resolution=(480, 480), framerate=12, format="rgb")
decoder = DecodeQR(wordlist=self.settings.wordlist)
@@ -738,46 +806,46 @@ class Controller(ConfigurableSingleton):
if decoder.isComplete() and decoder.isPSBT():
# first QR is PSBT
self.menu_view.draw_modal(["Validating PSBT"])
self.renderer.draw_modal(["Validating PSBT"])
psbt = decoder.getPSBT()
self.menu_view.draw_modal(["PSBT Valid!", "Enter", "seed phrase", "to sign this tx"], "", "Right to Continue")
self.renderer.draw_modal(["PSBT Valid!", "Enter", "seed phrase", "to sign this tx"], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
elif decoder.isComplete() and decoder.isSeed():
# first QR is Seed
self.menu_view.draw_modal(["Validating Seed"])
self.renderer.draw_modal(["Validating Seed"])
seed.mnemonic = decoder.getSeedPhrase()
if not seed:
# seed is not valid, Exit if not valid with message
self.menu_view.draw_modal(["Seed Invalid", "check seed phrase", "and try again", ""], "", "Right to Continue")
self.renderer.draw_modal(["Seed Invalid", "check seed phrase", "and try again", ""], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
else:
self.menu_view.draw_modal(["Valid Seed!"], "", "Right to Continue")
self.renderer.draw_modal(["Valid Seed!"], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Add Seed Passphrase?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Add Seed Passphrase?")
if r == 1:
# display a tool to pick letters/numbers to make a passphrase
seed.passphrase = self.seed_tools_view.draw_passphrase_keyboard_entry()
if len(seed.passphrase) == 0:
self.menu_view.draw_modal(["No passphrase added", "to seed words"], "", "Left to Exit, Right to Continue")
self.renderer.draw_modal(["No passphrase added", "to seed words"], "", "Left to Exit, Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT])
if input == B.KEY_LEFT:
return Path.MAIN_MENU
else:
self.menu_view.draw_modal(["Optional passphrase", "added to seed words"], "", "Right to Continue")
self.renderer.draw_modal(["Optional passphrase", "added to seed words"], "", "Right to Continue")
self.buttons.wait_for([B.KEY_RIGHT])
# Ask to save seed
if self.storage.slot_avaliable():
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,2,None)
if slot_num in (1,2,3):
self.storage.add_seed(seed, slot_num)
self.menu_view.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Continue")
self.renderer.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
# display seed phrase
@@ -794,17 +862,17 @@ class Controller(ConfigurableSingleton):
if decoder.isComplete() and decoder.isPSBT():
# second QR must be a PSBT
self.menu_view.draw_modal(["Validating PSBT"])
self.renderer.draw_modal(["Validating PSBT"])
psbt = decoder.getPSBT()
elif ( decoder.isComplete() and not decoder.isPSBT() ) or decoder.isInvalid():
self.menu_view.draw_modal(["Not a valid PSBT QR"], "", "Right to Exit")
self.renderer.draw_modal(["Not a valid PSBT QR"], "", "Right to Exit")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
else:
return Path.MAIN_MENU
elif ( decoder.isComplete() and not decoder.isPSBT() ) or decoder.isInvalid():
self.menu_view.draw_modal(["Not a valid PSBT QR"], "", "Right to Exit")
self.renderer.draw_modal(["Not a valid PSBT QR"], "", "Right to Exit")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
else:
@@ -813,7 +881,7 @@ class Controller(ConfigurableSingleton):
if not seed:
# No valid seed yet, If there is a saved seed, ask to use saved seed
if self.storage.num_of_saved_seeds() > 0:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Use Save Seed?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Use Save Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,3,None)
if slot_num == 0:
@@ -838,24 +906,24 @@ class Controller(ConfigurableSingleton):
return Path.MAIN_MENU
# check if seed phrase is valid
self.menu_view.draw_modal(["Validating Seed ..."])
self.renderer.draw_modal(["Validating Seed ..."])
if not seed:
self.menu_view.draw_modal(["Seed Invalid", "check seed phrase", "and try again"], "", "Right to Continue")
self.renderer.draw_modal(["Seed Invalid", "check seed phrase", "and try again"], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
if len(seed.passphrase) == 0:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Add Seed Passphrase?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Add Seed Passphrase?")
if r == 1:
# display a tool to pick letters/numbers to make a passphrase
seed.passphrase = self.seed_tools_view.draw_passphrase_keyboard_entry()
if len(seed.passphrase) == 0:
self.menu_view.draw_modal(["No passphrase added", "to seed words"], "", "Left to Exit, Right to Continue")
self.renderer.draw_modal(["No passphrase added", "to seed words"], "", "Left to Exit, Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT])
if input == B.KEY_LEFT:
return Path.MAIN_MENU
else:
self.menu_view.draw_modal(["Optional passphrase", "added to seed words", seed.passphrase], "", "Right to Continue")
self.renderer.draw_modal(["Optional passphrase", "added to seed words", seed.passphrase], "", "Right to Continue")
self.buttons.wait_for([B.KEY_RIGHT])
# display seed phrase
@@ -869,16 +937,16 @@ class Controller(ConfigurableSingleton):
# Ask to save seed
if self.storage.slot_avaliable() and used_saved_seed == False:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,2,None)
if slot_num in (1,2,3):
self.storage.add_seed(seed, slot_num)
self.menu_view.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Continue")
self.renderer.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
# show transaction information before sign
self.menu_view.draw_modal(["Parsing PSBT"])
self.renderer.draw_modal(["Parsing PSBT"])
p = PSBTParser(psbt,seed,self.settings.network)
self.signing_tools_view.display_transaction_information(p)
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT], False)
@@ -886,19 +954,19 @@ class Controller(ConfigurableSingleton):
return Path.MAIN_MENU
# Sign PSBT
self.menu_view.draw_modal(["PSBT Signing ..."])
self.renderer.draw_modal(["PSBT Signing ..."])
sig_cnt = PSBTParser.sigCount(psbt)
psbt.sign_with(p.root)
trimmed_psbt = PSBTParser.trim(psbt)
if sig_cnt == PSBTParser.sigCount(trimmed_psbt):
self.menu_view.draw_modal(["Signing failed", "left to exit", "or right to continue", "to display PSBT QR"], "", "")
self.renderer.draw_modal(["Signing failed", "left to exit", "or right to continue", "to display PSBT QR"], "", "")
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT], False)
if input == B.KEY_LEFT:
return Path.MAIN_MENU
# Display Animated QR Code
self.menu_view.draw_modal(["Generating PSBT QR ..."])
self.renderer.draw_modal(["Generating PSBT QR ..."])
e = EncodeQR(psbt=trimmed_psbt, qr_type=self.settings.qr_psbt_type, qr_density=self.settings.qr_density, wordlist=self.settings.wordlist)
while True:
image = e.nextPartImage(240,240,1)
@@ -967,12 +1035,12 @@ class Controller(ConfigurableSingleton):
r = self.settings_tools_view.display_persistent_settings()
if r is not None:
if r == True:
self.menu_view.draw_modal(["Persistent settings", "keeps settings saved", "across reboot.", "Seeds are never saved"], "Warning", "Right to Continue")
self.renderer.draw_modal(["Persistent settings", "keeps settings saved", "across reboot.", "Seeds are never saved"], "Warning", "Right to Continue")
input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
if input == B.KEY_RIGHT:
self.settings.persistent = r
else:
self.menu_view.draw_modal(["This will restore", "the default", "settings.", ""], "Warning", "Right to Continue")
self.renderer.draw_modal(["This will restore", "the default", "settings.", ""], "Warning", "Right to Continue")
input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
if input == B.KEY_RIGHT:
self.settings.persistent = r
@@ -1000,12 +1068,12 @@ class Controller(ConfigurableSingleton):
return Path.MAIN_MENU
def show_reset_tool(self):
self.menu_view.draw_modal(["This will restore", "default settings and", "restart the app", ""], "Warning", "Right to Continue")
self.renderer.draw_modal(["This will restore", "default settings and", "restart the app", ""], "Warning", "Right to Continue")
input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
if input == B.KEY_RIGHT:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Reset SeedSigner?")
r = self.renderer.display_generic_selection_menu(["Yes", "No"], "Reset SeedSigner?")
if r == 1: #Yes
self.menu_view.display_blank_screen()
self.renderer.display_blank_screen()
self.settings.restoreDefault()
time.sleep(0.1) # give time to write to disk
+13 -2
View File
@@ -84,6 +84,7 @@ class Renderer(ConfigurableSingleton):
self.disp.ShowImage(crop, 0, 0)
# TODO: Should probably move this to templates.py
def show_image_with_text(self, image, text, font=None, text_color="GREY", text_background=None):
image_copy = image.copy().convert("RGBA")
draw = ImageDraw.Draw(image_copy)
@@ -100,6 +101,7 @@ class Renderer(ConfigurableSingleton):
self.show_image(image_copy, alpha_overlay=text_overlay)
# TODO: Should probably move this to templates.py
def draw_modal(self, lines = [], title = "", bottom = "") -> None:
self.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), outline=0, fill=0)
@@ -141,14 +143,14 @@ class Renderer(ConfigurableSingleton):
return
# TODO: Should probably move this to templates.py
def draw_prompt_yes_no(self, lines = [], title = "", bottom = "") -> None:
self.draw_prompt_custom("", "Yes ", "No ", lines, title, bottom)
return
# TODO: Should probably move this to templates.py
def draw_prompt_custom(self, a_txt, b_txt, c_txt, lines = [], title = "", bottom = "") -> None:
self.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), outline=0, fill=0)
if len(title) > 0:
@@ -196,3 +198,12 @@ class Renderer(ConfigurableSingleton):
self.show_image()
return
def display_blank_screen(self):
self.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), outline=0, fill=0)
self.show_image()
+112 -62
View File
@@ -2,70 +2,118 @@
from .components import (EDGE_PADDING, COMPONENT_PADDING, Button, TopNav,
TextArea)
from dataclasses import dataclass
from PIL import ImageFont
from seedsigner.helpers import B
from seedsigner.helpers import B, Buttons
from seedsigner.views import View
@dataclass
class BaseScreen:
def __init__(self,
title:str,
font: ImageFont = None):
# Avoid setting defaults on parent dataclasses, otherwise you must have defaults on
# all child attrs. see: https://stackoverflow.com/a/53085935
# No base attrs specified yet
def __post_init__(self):
from seedsigner.gui import Renderer
self.renderer = Renderer.get_instance()
self.title = title
self.hw_inputs = Buttons.get_instance()
def render(self, show_image: bool = True):
# Clear the whole canvas
self.renderer.draw.rectangle((0, 0, self.renderer.canvas_width, self.renderer.canvas_height), fill=0)
if show_image:
# Will be false when called by child classes; they'll want to keep writing
# their own UI content before calling show_image() themselves.
self.renderer.show_image()
def run(self):
"""
Screen can run on its own until it returns a final exit input from the user.
For example: A basic menu screen where the user can key up and down. The
Screen can handle the UI updates to light up the currently selected menu item
on its own. Only when the user clicks to make a selection would run() exit
and returns the selected option.
But an alternate use case returns immediately after each user input so the
View can update its controlling logic accordingly (e.g. as the user joysticks
over different letters in the keyboard UI, we need to make matching changes
to the list of mnemonic seed words that match the new letter).
In this case, it would be called repeatedly in a loop:
* run() and wait for it to handle user input
* run() exits and returns the user input (e.g. KEY_UP)
* View updates its state of the world accordingly
* loop and call run() again
"""
raise Exception("Must implement in a child class")
@dataclass
class BaseTopNavScreen(BaseScreen):
title:str
def __post_init__(self):
super().__post_init__()
self.top_nav = TopNav(
text=self.title,
width=self.renderer.canvas_width,
height=(2 * EDGE_PADDING) + int(self.renderer.canvas_width * 2.0 / 15.0), # 32px on a 240x240 screen
font=font,
)
def render(self):
self.renderer.draw.rectangle((0, 0, self.renderer.canvas_width, self.renderer.canvas_height), fill=0)
def render(self, show_image: bool = True):
super().render(show_image=False) # Child always tells parent to wait
self.top_nav.render()
self.renderer.show_image()
if show_image:
self.renderer.show_image()
class ButtonListScreen(BaseScreen):
def __init__(self,
title: str,
button_data: list, # w/Python 3.9 we can be more specific: list[str]
is_button_text_centered: bool,
is_bottom_list: bool,
title_font: ImageFont = None,
button_font: ImageFont = None,
button_selected_color: str = "orange"):
super().__init__(title, title_font)
@dataclass
class ButtonListScreen(BaseTopNavScreen):
title: str # Not necessary to include this since it's in parent; just here for clarity
button_labels: list # w/Python 3.9 we can be more specific: list[str]
selected_button: int = 0
is_button_text_centered: bool = True
is_bottom_list: bool = False
button_font: ImageFont = None
button_selected_color: str = "orange"
self.button_data = button_data
self.is_bottom_list = is_bottom_list
def __post_init__(self):
super().__post_init__()
button_height = int(self.renderer.canvas_height * 3.0 / 20.0) # 36px on a 240x240 screen
if len(self.button_data) == 1:
if len(self.button_labels) == 1:
button_list_height = button_height
else:
button_list_height = (len(self.button_data) * button_height) + (COMPONENT_PADDING * (len(self.button_data) - 1))
button_list_height = (len(self.button_labels) * button_height) + (COMPONENT_PADDING * (len(self.button_labels) - 1))
if is_bottom_list:
if self.is_bottom_list:
button_list_y = self.renderer.canvas_height - (button_list_height + EDGE_PADDING)
else:
button_list_y = self.top_nav.height + int((self.renderer.canvas_height - self.top_nav.height - button_list_height) / 2)
self.buttons = []
for i, entry in enumerate(button_data):
for i, button_label in enumerate(self.button_labels):
button = Button(
text=entry["text"],
text=button_label,
screen_x=EDGE_PADDING,
screen_y=button_list_y + i * (button_height + COMPONENT_PADDING),
width=self.renderer.canvas_width - (2 * EDGE_PADDING),
height=button_height,
is_text_centered=is_button_text_centered,
font=button_font,
selected_color=button_selected_color
is_text_centered=self.is_button_text_centered,
font=self.button_font,
selected_color=self.button_selected_color
)
self.buttons.append(button)
@@ -73,43 +121,47 @@ class ButtonListScreen(BaseScreen):
self.selected_button = 0
def render(self):
self.renderer.draw.rectangle((0, 0, self.renderer.canvas_width, self.renderer.canvas_height), fill=0)
self.top_nav.render()
def render(self, show_image: bool = True):
super().render(show_image=False) # Child always tells parent to wait
for button in self.buttons:
button.render()
self.renderer.show_image()
def update_from_input(self, input):
if input == B.KEY_UP:
if self.selected_button == 0:
# Move selection up to top_nav
pass
else:
self.buttons[self.selected_button].is_selected = False
self.buttons[self.selected_button].render()
self.selected_button -= 1
self.buttons[self.selected_button].is_selected = True
self.buttons[self.selected_button].render()
elif input == B.KEY_DOWN:
if self.selected_button == len(self.buttons) - 1:
# Trap selection at bottom?
pass
else:
self.buttons[self.selected_button].is_selected = False
self.buttons[self.selected_button].render()
self.selected_button += 1
self.buttons[self.selected_button].is_selected = True
self.buttons[self.selected_button].render()
else:
return
self.renderer.show_image()
def run(self):
while True:
user_input = self.hw_inputs.wait_for([B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS], check_release=True, release_keys=[B.KEY_PRESS])
if user_input == B.KEY_UP:
if self.selected_button == 0:
# TODO: Move selection up to top_nav
pass
else:
self.buttons[self.selected_button].is_selected = False
self.buttons[self.selected_button].render()
self.selected_button -= 1
self.buttons[self.selected_button].is_selected = True
self.buttons[self.selected_button].render()
elif user_input == B.KEY_DOWN:
if self.selected_button == len(self.buttons) - 1:
# TODO: Trap selection at bottom?
pass
else:
self.buttons[self.selected_button].is_selected = False
self.buttons[self.selected_button].render()
self.selected_button += 1
self.buttons[self.selected_button].is_selected = True
self.buttons[self.selected_button].render()
elif user_input == B.KEY_PRESS:
return self.selected_button
# Write the screen updates
self.renderer.show_image()
class BottomButtonScreen(ButtonListScreen):
def __init__(self,
@@ -173,5 +225,3 @@ class FontTesterScreen(ButtonListScreen):
font=font,
button_font=button_font
)
+30 -21
View File
@@ -1,9 +1,10 @@
import RPi.GPIO as GPIO
import time
from seedsigner.models import Singleton
class Buttons:
class Buttons(Singleton):
KEY_UP_PIN = 6
KEY_DOWN_PIN = 19
KEY_LEFT_PIN = 5
@@ -14,29 +15,37 @@ class Buttons:
KEY2_PIN = 20
KEY3_PIN = 16
def __init__(self) -> None:
#init GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(Buttons.KEY_UP_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_DOWN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_LEFT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_RIGHT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_PRESS_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY1_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY2_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY3_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
self.GPIO = GPIO
self.override_ind = False
@classmethod
def get_instance(cls):
# This is the only way to access the one and only instance
if cls._instance is None:
cls._instance = cls.__new__(cls)
self.add_events([B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS, B.KEY_LEFT, B.KEY_RIGHT, B.KEY1, B.KEY2, B.KEY3])
#init GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(Buttons.KEY_UP_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_DOWN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_LEFT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_RIGHT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY_PRESS_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY1_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY2_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(Buttons.KEY3_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
# Track state over time so we can apply input delays/ignores as needed
self.cur_input = None # Track which direction or button was last pressed
self.cur_input_started = None # Track when that input began
self.last_input_time = int(time.time() * 1000) # How long has it been since the last input?
self.first_repeat_threshold = 175 # Long-press time required before returning continuous input
self.next_repeat_threshold = 250 # Amount of time where we no longer consider input a continuous hold
cls._instance.GPIO = GPIO
cls._instance.override_ind = False
cls._instance.add_events([B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS, B.KEY_LEFT, B.KEY_RIGHT, B.KEY1, B.KEY2, B.KEY3])
# Track state over time so we can apply input delays/ignores as needed
cls._instance.cur_input = None # Track which direction or button was last pressed
cls._instance.cur_input_started = None # Track when that input began
cls._instance.last_input_time = int(time.time() * 1000) # How long has it been since the last input?
cls._instance.first_repeat_threshold = 175 # Long-press time required before returning continuous input
cls._instance.next_repeat_threshold = 250 # Amount of time where we no longer consider input a continuous hold
return cls._instance
def wait_for(self, keys=[], check_release=True, release_keys=[]) -> int:
+47 -165
View File
@@ -2,7 +2,8 @@
from . import View
from seedsigner.gui.components import Fonts
from seedsigner.helpers import B, Path
from seedsigner.gui.templates import ButtonListScreen
from seedsigner.helpers import B, Path, Buttons
from seedsigner.models import SeedStorage, Settings, Seed
# External Dependencies
@@ -18,53 +19,48 @@ class MenuView(View):
self.menu_lines = []
self.selected_menu_num = 1
###
### Main Navigation
###
### Main Menu
def display_main_menu(self, sub_menu = None) -> int:
ret_val = 0
input = 0
lines = ["Seed Tools", "Scan QR", "Settings", "Power Off"]
button_labels = [
"Seed Tools",
"Scan QR",
"Settings",
"Power Off"
]
if sub_menu == Path.SEED_TOOLS_SUB_MENU:
return self.display_seed_tools_menu()
elif sub_menu == Path.SIGNING_TOOLS_SUB_MENU:
return Path.SIGN_TRANSACTION
elif sub_menu == Path.SETTINGS_SUB_MENU:
return self.display_settings_menu()
else:
self.draw_menu(lines, 1)
# if sub_menu == Path.SEED_TOOLS_SUB_MENU:
# return self.display_seed_tools_menu()
# elif sub_menu == Path.SIGNING_TOOLS_SUB_MENU:
# return Path.SIGN_TRANSACTION
# elif sub_menu == Path.SETTINGS_SUB_MENU:
# return self.display_settings_menu()
menu_screen = ButtonListScreen(title="SeedSigner", button_labels=button_labels, selected_button=0)
menu_screen.render()
selected_menu_num = menu_screen.run()
# Wait for Button Input (specifically menu selection/press)
while True:
if ret_val == 0:
input = self.buttons.wait_for([B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS], check_release=True, release_keys=[B.KEY_PRESS])
else:
return ret_val
if input == B.KEY_UP:
self.menu_up()
elif input == B.KEY_DOWN:
self.menu_down()
elif input == B.KEY_PRESS:
if self.selected_menu_num == 1:
ret_val = self.display_seed_tools_menu()
elif self.selected_menu_num == 2:
ret_val = Path.SIGN_TRANSACTION
elif self.selected_menu_num == 3:
ret_val = self.display_settings_menu()
elif self.selected_menu_num == 4:
ret_val = Path.POWER_OFF
print(f"selected_menu_num: {selected_menu_num}")
if selected_menu_num == 0:
print("returning selected_menu_num")
return (self.display_seed_tools_menu, {})
elif selected_menu_num == 1:
ret_val = Path.SIGN_TRANSACTION
elif selected_menu_num == 2:
return (self.display_settings_menu, {})
elif selected_menu_num == 3:
ret_val = Path.POWER_OFF
# if ret_val != Path.MAIN_MENU: # When no main menu, return to controller
# return ret_val
if ret_val != Path.MAIN_MENU: # When no main menu, return to controller
return ret_val
else:
self.draw_menu(lines)
### Seed Tools Menu
def display_seed_tools_menu(self) -> int:
seed_storage_line = "Store a Seed (temp)"
if self.controller.storage.num_of_saved_seeds() > 0:
@@ -100,8 +96,8 @@ class MenuView(View):
elif self.selected_menu_num == 7:
return Path.IMAGE_GEN_SEED
### Signing Tools Menu
### Signing Tools Menu
def display_signing_tools_menu(self) -> None:
lines = ["... [ Return to Main ]", "Generate xPub", "Sign a Transaction"]
self.draw_menu(lines, 1)
@@ -123,16 +119,22 @@ class MenuView(View):
return Path.SIGN_TRANSACTION
raise Exception("Unhandled case")
### Settings Menu
### Settings Menu
def display_settings_menu(self) -> int:
lines = ["... [ Return to Main ]", "Wallet: <wallet>", "Network: <network>", "QR Density: <density>", "Input / Output Tests", "Persistent Settings: <persistent>", "Camera Rotation", "Version Info", "Donate to SeedSigner", "Reset SeedSigner"]
lines = [
"... [ Return to Main ]",
f"Wallet: {Settings.get_instance().software}",
f"Network: {Settings.get_instance().network}",
f"QR Density: {Settings.get_instance().qr_density_name}",
"Input / Output Tests",
f"Persistent Settings: {Settings.get_instance().persistent_display}",
"Camera Rotation",
"Version Info",
"Donate to SeedSigner",
"Reset SeedSigner"
]
input = 0
lines[1] = lines[1].replace("<wallet>", Settings.get_instance().software)
lines[2] = lines[2].replace("<network>", Settings.get_instance().network)
lines[3] = lines[3].replace("<density>", Settings.get_instance().qr_density_name)
lines[5] = lines[5].replace("<persistent>", Settings.get_instance().persistent_display)
# Draw Menu
self.selected_menu_num = 1
@@ -168,21 +170,6 @@ class MenuView(View):
return Path.RESET
raise Exception("Unhandled case")
### Generic Single Menu Selection (returns 1,2,3,4,5,6 ...)
def display_generic_selection_menu(self, lines = [], title = None, bottom = None) -> int:
self.selected_menu_num = 1
self.draw_menu(lines, 1, title, bottom, True)
while True:
input = self.buttons.wait_for([B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS], check_release=True, release_keys=[B.KEY_PRESS])
if input == B.KEY_UP:
self.menu_up(title, bottom)
elif input == B.KEY_DOWN:
self.menu_down(title, bottom)
elif input == B.KEY_PRESS:
return self.selected_menu_num
raise Exception("Unhandled case")
### Generic Word 12 or 24 seed phrase menu
# internal method
@@ -276,111 +263,6 @@ class MenuView(View):
return int(re.search("#(\d+)", lines[self.selected_menu_num-1], re.IGNORECASE).group(1))
raise Exception("Unhandled case")
###
### Generic Reusable Menu Methods/Functions
###
### Generic Draw Menu Method
# TODO: Optimize updates by just redrawing the no-longer highlighted line and the newly highlighted line
def draw_menu(self, lines, selected_menu_num = 1, title = None, bottom = None, force_redraw = False) -> None:
if title == None:
t = "SeedSigner v" + self.controller.VERSION
else:
t = title
if bottom == None and len(lines) <= 5:
b = "Press Control Stick to Select"
elif bottom == None:
if len(lines) >= 6 and len(lines) <= 10:
if selected_menu_num <= 5:
b = "Page 1 of 2"
elif selected_menu_num >= 6 and selected_menu_num <= 10:
b = "Page 2 of 2"
elif len(lines) >= 11 and len(lines) <= 15:
if selected_menu_num <= 5:
b = "Page 1 of 3"
elif selected_menu_num >= 6 and selected_menu_num <= 10:
b = "Page 2 of 3"
elif selected_menu_num >= 11 and selected_menu_num <= 15:
b = "Page 3 of 3"
else:
b = "Press Control Stick to Select"
else:
b = bottom
if lines != self.menu_lines or selected_menu_num != self.selected_menu_num or force_redraw == True:
#Menu has changed, redraw
self.renderer.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), outline=0, fill=0)
tw, th = self.renderer.draw.textsize(t, font=Fonts.get_font("Assistant-Medium", 22))
self.renderer.draw.text(((240 - tw) / 2, 2), t, fill=self.color, font=Fonts.get_font("Assistant-Medium", 22))
num_of_lines = len(lines)
if selected_menu_num <= 5:
if num_of_lines >= 1:
self.draw_menu_text(15, 43 , lines[0], (True if selected_menu_num == 1 else False))
if num_of_lines >= 2:
self.draw_menu_text(15, 76 , lines[1], (True if selected_menu_num == 2 else False))
if num_of_lines >= 3:
self.draw_menu_text(15, 109, lines[2], (True if selected_menu_num == 3 else False))
if num_of_lines >= 4:
self.draw_menu_text(15, 142, lines[3], (True if selected_menu_num == 4 else False))
if num_of_lines >= 5:
self.draw_menu_text(15, 175, lines[4], (True if selected_menu_num == 5 else False))
elif selected_menu_num >= 6 and selected_menu_num <= 10:
if num_of_lines >= 6:
self.draw_menu_text(15, 43 , lines[5], (True if selected_menu_num == 6 else False))
if num_of_lines >= 7:
self.draw_menu_text(15, 76 , lines[6], (True if selected_menu_num == 7 else False))
if num_of_lines >= 8:
self.draw_menu_text(15, 109, lines[7], (True if selected_menu_num == 8 else False))
if num_of_lines >= 9:
self.draw_menu_text(15, 142, lines[8], (True if selected_menu_num == 9 else False))
if num_of_lines >= 10:
self.draw_menu_text(15, 175, lines[9], (True if selected_menu_num == 10 else False))
elif selected_menu_num >= 11 and selected_menu_num <= 15:
if num_of_lines >= 11:
self.draw_menu_text(15, 43 , lines[10], (True if selected_menu_num == 11 else False))
if num_of_lines >= 12:
self.draw_menu_text(15, 76 , lines[11], (True if selected_menu_num == 12 else False))
if num_of_lines >= 13:
self.draw_menu_text(15, 109, lines[12], (True if selected_menu_num == 13 else False))
if num_of_lines >= 14:
self.draw_menu_text(15, 142, lines[13], (True if selected_menu_num == 14 else False))
if num_of_lines >= 15:
self.draw_menu_text(15, 175, lines[14], (True if selected_menu_num == 15 else False))
tw, th = self.renderer.draw.textsize(b, font=Fonts.get_font("Assistant-Medium", 18))
self.renderer.draw.text(((240 - tw) / 2, 210), b, fill=self.color, font=Fonts.get_font("Assistant-Medium", 18))
self.renderer.show_image()
# saved update menu lines and selection
self.menu_lines = lines
self.selected_menu_num = selected_menu_num
### Generic Menu Navigation
def menu_up(self, title = None, bottom = None):
if self.selected_menu_num <= 1:
self.draw_menu(self.menu_lines, len(self.menu_lines), title, bottom)
else:
self.draw_menu(self.menu_lines, self.selected_menu_num - 1, title, bottom)
def menu_down(self, title = None, bottom = None):
if self.selected_menu_num >= len(self.menu_lines):
self.draw_menu(self.menu_lines, 1, title, bottom)
else:
self.draw_menu(self.menu_lines, self.selected_menu_num + 1, title, bottom)
### Internal View Method to Display a Line in a Menu Screen
def draw_menu_text(self, x, y, line, selected) -> None:
if selected == True:
self.renderer.draw.rectangle((5, y-3, 235, y+28), outline=0, fill=self.color)
self.renderer.draw.text((x, y) , line, fill="BLACK", font=Fonts.get_font("Assistant-Bold", 20))
else:
self.renderer.draw.text((x, y) , line, fill=self.color, font=Fonts.get_font("Assistant-Medium", 20))
return
+14 -14
View File
@@ -59,7 +59,7 @@ class SeedToolsView(View):
if len(self.words) >= cur_word:
initial_letters = list(self.words[cur_word - 1]) # zero-indexed
ret_val = self.renderer.draw_seed_word_keyboard_entry(num_word=cur_word, initial_letters=initial_letters)
ret_val = self.draw_seed_word_keyboard_entry(num_word=cur_word, initial_letters=initial_letters)
if ret_val == Keyboard.KEY_PREVIOUS_PAGE:
# Reload previous word
@@ -684,7 +684,7 @@ class SeedToolsView(View):
self.dice_selected = 5
self.roll_data = ""
self.renderer.draw_dice(self.dice_selected)
self.draw_dice(self.dice_selected)
time.sleep(1) # pause for 1 second before accepting input
# Wait for Button Input (specifically menu selection/press)
@@ -719,7 +719,7 @@ class SeedToolsView(View):
new_selection = 3
if self.dice_selected != new_selection and new_selection != 0:
self.renderer.draw_dice(new_selection)
self.draw_dice(new_selection)
return True
@@ -734,7 +734,7 @@ class SeedToolsView(View):
new_selection = 6
if self.dice_selected != new_selection and new_selection != 0:
self.renderer.draw_dice(new_selection)
self.draw_dice(new_selection)
return True
@@ -751,25 +751,25 @@ class SeedToolsView(View):
new_selection = 6
if self.dice_selected != new_selection and new_selection != 0:
self.renderer.draw_dice(new_selection)
self.draw_dice(new_selection)
return True
def dice_arrow_left(self):
if self.dice_selected == 1:
self.renderer.draw_prompt_custom("Undo ", "Cancel ", "Exit ", ["Action: ", "", ""])
self.draw_prompt_custom("Undo ", "Cancel ", "Exit ", ["Action: ", "", ""])
input = self.buttons.wait_for([B.KEY1, B.KEY2, B.KEY3])
if input == B.KEY1: #Undo
self.roll_number = self.roll_number - 1
self.roll_data = self.roll_data[:-1] # remove last character from string
if self.roll_number >= 1:
self.renderer.draw_dice(self.dice_selected)
self.draw_dice(self.dice_selected)
return True
else:
return False
elif input == B.KEY2: # Cancel
self.renderer.draw_dice(self.dice_selected)
self.draw_dice(self.dice_selected)
return True
elif input == B.KEY3: # Exit
return False
@@ -785,7 +785,7 @@ class SeedToolsView(View):
new_selection = 4
if self.dice_selected != new_selection and new_selection != 0:
self.renderer.draw_dice(new_selection)
self.draw_dice(new_selection)
return True
@@ -800,7 +800,7 @@ class SeedToolsView(View):
# Reset for the next UI render
self.dice_selected = 5
if self.roll_number < 100:
self.renderer.draw_dice(self.dice_selected)
self.draw_dice(self.dice_selected)
return True
@@ -1090,7 +1090,7 @@ class SeedToolsView(View):
)
while True:
# self.renderer.draw_text_over_image("click to exit", font=Fonts.get_font("Assistant-Medium", 18), text_color="BLACK", text_background="ORANGE")
# self.draw_text_over_image("click to exit", font=Fonts.get_font("Assistant-Medium", 18), text_color="BLACK", text_background="ORANGE")
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT, B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS])
if input == B.KEY_RIGHT:
@@ -1134,7 +1134,7 @@ class SeedToolsView(View):
cur_y = next_y
def read_seed_phrase_qr(self):
self.renderer.draw_modal(["Scanning..."], "Seed QR" ,"Right to Exit")
self.draw_modal(["Scanning..."], "Seed QR" ,"Right to Exit")
try:
self.controller.camera.start_video_stream_mode(resolution=(480, 480), framerate=12, format="rgb")
decoder = DecodeQR(wordlist=self.controller.settings.wordlist)
@@ -1155,10 +1155,10 @@ class SeedToolsView(View):
if decoder.isComplete() and decoder.isSeed():
self.words = decoder.getSeedPhrase()
elif not decoder.isPSBT():
self.renderer.draw_modal(["Not a valid Seed QR"], "", "Right to Exit")
self.draw_modal(["Not a valid Seed QR"], "", "Right to Exit")
input = self.buttons.wait_for([B.KEY_RIGHT])
else:
self.renderer.draw_modal(["QR Parsing Failed"], "", "Right to Exit")
self.draw_modal(["QR Parsing Failed"], "", "Right to Exit")
input = self.buttons.wait_for([B.KEY_RIGHT])
self.words = []
+18 -17
View File
@@ -3,8 +3,9 @@ from . import View
from seedsigner.helpers import B, QR, Keyboard, TextEntryDisplay
from seedsigner.models import EncodeQRDensity
class SettingsToolsView(View):
class SettingsToolsView(View):
def __init__(self) -> None:
View.__init__(self)
@@ -12,22 +13,23 @@ class SettingsToolsView(View):
self.donate_image = None
self.derivation = None
### Donate Menu Item
### Donate Menu Item
def display_donate_info_screen(self):
self.draw_modal(["You can support", "SeedSigner by donating", "any amount of BTC", "Thank You!!!"], "", "(Press right for a QR code)")
self.renderer.draw_modal(["You can support", "SeedSigner by donating", "any amount of BTC", "Thank You!!!"], "", "(Press right for a QR code)")
return True
def display_donate_qr(self):
self.draw_modal(["Loading..."])
self.renderer.draw_modal(["Loading..."])
self.donate_image = self.qr.qrimage("bc1qphlyv2dde290tqdlnk8uswztnshw3x9rjurexqqhksvu7vdevhtsuw4efe")
self.renderer.show_image(self.donate_image)
return True
### Display Network Selection
### Display Network Selection
def display_current_network(self) -> str:
r = self.controller.menu_view.display_generic_selection_menu(["... [ Return to Settings ]", "Mainnet", "Testnet"], "Which Network?")
r = self.renderer.display_generic_selection_menu(["... [ Return to Settings ]", "Mainnet", "Testnet"], "Which Network?")
if r == 2:
return "main"
elif r == 3:
@@ -35,10 +37,10 @@ class SettingsToolsView(View):
else:
return None
### Display Wallet Selection
### Display Wallet Selection
def display_wallet_selection(self) -> str:
r = self.controller.menu_view.display_generic_selection_menu(["... [ Return to Settings ]", "Prompt", "Specter Desktop", "Blue Wallet", "Sparrow"], "Which Wallet?")
r = self.renderer.display_generic_selection_menu(["... [ Return to Settings ]", "Prompt", "Specter Desktop", "Blue Wallet", "Sparrow"], "Which Wallet?")
if r == 2:
return "Prompt"
elif r == 3:
@@ -50,10 +52,10 @@ class SettingsToolsView(View):
else:
return None
### Display QR Density Selection
### Display QR Density Selection
def display_qr_density_selection(self) -> str:
r = self.controller.menu_view.display_generic_selection_menu(["... [ Return to Settings ]", "Low", "Medium", "High"], "Which QR Density?")
r = self.renderer.display_generic_selection_menu(["... [ Return to Settings ]", "Low", "Medium", "High"], "Which QR Density?")
if r == 2:
return EncodeQRDensity.LOW
elif r == 3:
@@ -63,13 +65,13 @@ class SettingsToolsView(View):
else:
return None
def display_persistent_settings(self) -> bool:
def display_persistent_settings(self) -> bool:
lines = ["... [ Return to Settings ]"]
lines.append("Yes")
lines.append("No")
r = self.controller.menu_view.display_generic_selection_menu(lines, "Use Persistent Settings?")
r = self.renderer.display_generic_selection_menu(lines, "Use Persistent Settings?")
if r == 1:
return None
elif r == 2:
@@ -87,7 +89,7 @@ class SettingsToolsView(View):
lines.append("180")
lines.append("270")
r = self.controller.menu_view.display_generic_selection_menu(lines, "Camera Rotation")
r = self.renderer.display_generic_selection_menu(lines, "Camera Rotation")
if r == 1:
return None
elif r == 2:
@@ -101,12 +103,11 @@ class SettingsToolsView(View):
else:
return None
###
### Version Info
###
def display_version_info(self):
def display_version_info(self):
line1 = "SeedSigner"
line2 = "Version v" + self.controller.VERSION
line3 = "(Joystick RIGHT to EXIT)"
@@ -120,10 +121,10 @@ class SettingsToolsView(View):
self.renderer.draw.text(((240 - tw) / 2, 210), line3, fill=View.color, font=Fonts.get_font("Assistant-Medium", 18))
self.renderer.show_image()
###
### Custom Derivation Path
###
def draw_derivation_keyboard_entry(self, existing_derivation = "m/"):
def render_right_panel():
row_height = 28
-5
View File
@@ -57,11 +57,6 @@ class View:
self.renderer.show_image()
def display_blank_screen(self):
self.renderer.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), outline=0, fill=0)
self.renderer.show_image()
###
### Reusable components
###