Left nav top btn flexibility; Discard Seed dialogue; PSBT w/self-xfer and change fixes

This commit is contained in:
kdmukai
2022-03-02 07:24:10 -06:00
parent 3161472e84
commit 5f2da8a265
9 changed files with 119 additions and 66 deletions
+1 -2
View File
@@ -29,10 +29,9 @@ Screens will be functional but not necessarily in their final presentation state
* 12th/24th word calc
* SeedQR/CompactSeedQR manual transcription UI w/configurable UI style (dots vs grid)
* Single sig address scan and verification
* QR display framerate speed controls, pop up guide UI
* SettingsQR standalone UI refinement
* Fix broken tests
* Scrollable ListButtonView w/upper canvas area
* All GUI Components support scrollable Screens
## v0.5.0 Pre-Release 3.x
+1 -1
View File
@@ -142,7 +142,7 @@ class Controller(Singleton):
title="Warning",
warning_headline="Pre-Release Code",
warning_text="Do not use this with real funds or to create new secure keys!",
show_top_nav_back_button=False,
show_top_nav_left_button=False,
).display()
+17 -11
View File
@@ -835,8 +835,12 @@ class TopNav(BaseComponent):
font_name: str = GUIConstants.TOP_NAV_TITLE_FONT_NAME
font_size: int = GUIConstants.TOP_NAV_TITLE_FONT_SIZE
font_color: str = "#fcfcfc"
show_back_button: bool = True
show_power_button: bool = False
show_left_button: bool = True
left_button_icon_name: str = SeedSignerCustomIconConstants.LARGE_CHEVRON_LEFT
left_button_icon_color: str = GUIConstants.BUTTON_FONT_COLOR
show_right_button: bool = False
right_button_icon_name: str = FontAwesomeIconConstants.POWER_OFF
right_button_icon_color: str = GUIConstants.BUTTON_FONT_COLOR
is_selected: bool = False
@@ -847,20 +851,22 @@ class TopNav(BaseComponent):
self.font = Fonts.get_font(self.font_name, self.font_size)
if self.show_back_button:
if self.show_left_button:
self.back_button = IconButton(
icon_name=SeedSignerCustomIconConstants.LARGE_CHEVRON_LEFT,
icon_name=self.left_button_icon_name,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
icon_color=self.left_button_icon_color,
screen_x=GUIConstants.EDGE_PADDING,
screen_y=GUIConstants.EDGE_PADDING,
width=GUIConstants.TOP_NAV_BUTTON_SIZE,
height=GUIConstants.TOP_NAV_BUTTON_SIZE,
)
if self.show_power_button:
if self.show_right_button:
self.power_button = IconButton(
icon_name=FontAwesomeIconConstants.POWER_OFF,
icon_name=self.right_button_icon_name,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
icon_color=self.right_button_icon_color,
screen_x=self.width - GUIConstants.TOP_NAV_BUTTON_SIZE - GUIConstants.EDGE_PADDING,
screen_y=GUIConstants.EDGE_PADDING,
width=GUIConstants.TOP_NAV_BUTTON_SIZE,
@@ -890,7 +896,7 @@ class TopNav(BaseComponent):
start_y=0
)
if self.show_back_button:
if self.show_left_button:
# Don't let the title intrude on the BACK button
min_x = self.back_button.screen_x + self.back_button.width + GUIConstants.COMPONENT_PADDING
if self.text_x < min_x:
@@ -902,17 +908,17 @@ class TopNav(BaseComponent):
from .screens import RET_CODE__BACK_BUTTON, RET_CODE__POWER_BUTTON
if not self.is_selected:
return None
if self.show_back_button:
if self.show_left_button:
return RET_CODE__BACK_BUTTON
if self.show_power_button:
if self.show_right_button:
return RET_CODE__POWER_BUTTON
def render(self):
if self.show_back_button:
if self.show_left_button:
self.back_button.is_selected = self.is_selected
self.back_button.render()
if self.show_power_button:
if self.show_right_button:
self.power_button.is_selected = self.is_selected
self.power_button.render()
+10 -7
View File
@@ -18,6 +18,8 @@ class PSBTOverviewScreen(ButtonListScreen):
change_amount: int = 0
fee_amount: int = 0
num_inputs: int = 0
num_self_transfer_outputs: int = 0
num_change_outputs: int = 0
destination_addresses: List[str] = None
@@ -131,11 +133,7 @@ class PSBTOverviewScreen(ButtonListScreen):
destination_column = []
if not self.destination_addresses:
# This is an internal transfer; no external addresses
destination_column.append(f"self transfer")
elif len(self.destination_addresses) <= 3:
if len(self.destination_addresses) <= 3:
for addr in self.destination_addresses:
destination_column.append(truncate_destination_addr(addr))
else:
@@ -144,10 +142,15 @@ class PSBTOverviewScreen(ButtonListScreen):
destination_column.append(f"[ ... ]")
destination_column.append(f"recipient {len(self.destination_addresses)}")
if self.num_self_transfer_outputs > 0:
for i in range(0, self.num_self_transfer_outputs):
destination_column.append(f"self transfer")
destination_column.append(f"fee")
if self.change_amount > 0 and self.destination_addresses:
destination_column.append("change")
if self.num_change_outputs > 0:
for i in range(0, self.num_change_outputs):
destination_column.append("change")
max_destination_text_width = 0
for destination in destination_column:
+13 -9
View File
@@ -9,7 +9,7 @@ from seedsigner.helpers.threads import BaseThread
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.settings import SettingsConstants
from ..components import (CheckedSelectionButton, GUIConstants, BaseComponent, Button, Icon, IconButton, LargeIconButton, SeedSignerCustomIconConstants, TopNav,
from ..components import (CheckedSelectionButton, FontAwesomeIconConstants, GUIConstants, BaseComponent, Button, Icon, IconButton, LargeIconButton, SeedSignerCustomIconConstants, TopNav,
TextArea, load_icon, load_image)
from seedsigner.helpers import B, Buttons
@@ -180,8 +180,10 @@ class LoadingScreenThread(BaseThread):
class BaseTopNavScreen(BaseScreen):
title: str = "Screen Title"
title_font_size: int = GUIConstants.TOP_NAV_TITLE_FONT_SIZE
show_top_nav_back_button: bool = True
show_top_nav_power_button: bool = False
show_top_nav_left_button: bool = True
top_nav_left_button_icon_name: str = SeedSignerCustomIconConstants.LARGE_CHEVRON_LEFT
show_top_nav_right_button: bool = False
top_nav_right_button_icon_name: str = FontAwesomeIconConstants.POWER_OFF
def __post_init__(self):
super().__post_init__()
@@ -190,8 +192,10 @@ class BaseTopNavScreen(BaseScreen):
font_size=self.title_font_size,
width=self.canvas_width,
height=GUIConstants.TOP_NAV_HEIGHT,
show_back_button=self.show_top_nav_back_button,
show_power_button=self.show_top_nav_power_button,
show_left_button=self.show_top_nav_left_button,
left_button_icon_name=self.top_nav_left_button_icon_name,
show_right_button=self.show_top_nav_right_button,
right_button_icon_name=self.top_nav_right_button_icon_name,
)
self.is_input_in_top_nav = False
@@ -234,7 +238,7 @@ class TextTopNavScreen(BaseTopNavScreen):
if user_input == B.KEY_UP:
if not self.top_nav.is_selected:
# Only move navigation up there if there's something to select
if self.top_nav.show_back_button or self.top_nav.show_power_button:
if self.top_nav.show_left_button or self.top_nav.show_right_button:
self.top_nav.is_selected = True
self.top_nav.render()
@@ -405,7 +409,7 @@ class ButtonListScreen(BaseTopNavScreen):
# OR keyed UP from the top of the list.
# Move selection up to top_nav
# Only move navigation up there if there's something to select
if self.top_nav.show_back_button or self.top_nav.show_power_button:
if self.top_nav.show_left_button or self.top_nav.show_right_button:
self.buttons[self.selected_button].is_selected = False
self.buttons[self.selected_button].render()
@@ -580,7 +584,7 @@ class LargeButtonScreen(BaseTopNavScreen):
if self.selected_button in [0, 2]:
swap_selected_button(self.selected_button + 1)
elif user_input == B.KEY_RIGHT and self.top_nav.is_selected and not self.top_nav.show_power_button:
elif user_input == B.KEY_RIGHT and self.top_nav.is_selected and not self.top_nav.show_right_button:
self.top_nav.is_selected = False
self.top_nav.render()
@@ -592,7 +596,7 @@ class LargeButtonScreen(BaseTopNavScreen):
swap_selected_button(self.selected_button - 1)
else:
# Left from the far edge takes us up to the BACK arrow
if self.top_nav.show_back_button:
if self.top_nav.show_left_button:
self.top_nav.is_selected = True
self.top_nav.render()
+3 -3
View File
@@ -9,12 +9,11 @@ from seedsigner.helpers.threads import BaseThread, ThreadsafeCounter
from seedsigner.models.seed import Seed
from .screen import BaseScreen, BaseTopNavScreen, ButtonListScreen, WarningScreenMixin
from .screen import BaseTopNavScreen, ButtonListScreen, WarningScreenMixin
from ..components import FontAwesomeIconConstants, Fonts, FormattedAddress, IconTextLine, SeedSignerCustomIconConstants, TextArea, GUIConstants, TextDoesNotFitException, calc_text_centering
from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay
from seedsigner.helpers import B
from seedsigner.models.encode_qr import EncodeQR
@@ -27,7 +26,8 @@ class SeedValidScreen(ButtonListScreen):
def __post_init__(self):
# TODO: Replace this with explicit "Continue"/"Discard" options
self.show_top_nav_back_button = True
self.show_top_nav_left_button = True
self.top_nav_left_button_icon_name = FontAwesomeIconConstants.X
super().__post_init__()
+36 -10
View File
@@ -86,11 +86,33 @@ class PSBTOverviewView(View):
def run(self):
psbt_parser = self.controller.psbt_parser
change_data = psbt_parser.change_data
"""
change_data = [
{
'address': 'bc1q............',
'amount': 397621401,
'fingerprint': ['22bde1a9', '73c5da0a'],
'derivation_path': ['m/48h/1h/0h/2h/1/0', 'm/48h/1h/0h/2h/1/0']
}, {},
]
"""
num_change_outputs = 0
num_self_transfer_outputs = 0
for change_output in change_data:
print(f"""{change_output["derivation_path"][0]}""")
if change_output["derivation_path"][0].split("/")[-2] == "1":
num_change_outputs += 1
else:
num_self_transfer_outputs += 1
screen = psbt_screens.PSBTOverviewScreen(
spend_amount=psbt_parser.spend_amount,
change_amount=psbt_parser.change_amount,
fee_amount=psbt_parser.fee_amount,
num_inputs=psbt_parser.num_inputs,
num_self_transfer_outputs=num_self_transfer_outputs,
num_change_outputs=num_change_outputs,
destination_addresses=psbt_parser.destination_addresses,
)
@@ -255,9 +277,17 @@ class PSBTChangeDetailsView(View):
is_change_derivation_path = int(derivation_path.split("/")[-2]) == 1
derivation_path_addr_index = int(derivation_path.split("/")[-1])
VERIFY_MULTISIG = "Verify Multisig Change"
NEXT = "Next"
if is_change_derivation_path:
title = "Your Change"
VERIFY_MULTISIG = "Verify Multisig Change"
else:
title = "Self-Transfer"
VERIFY_MULTISIG = "Verify Multisig Addr"
# if psbt_parser.num_change_outputs > 1:
# title += f" (#{self.change_address_num + 1})"
is_change_addr_verified = False
if psbt_parser.is_multisig:
# TODO:
@@ -278,11 +308,7 @@ class PSBTChangeDetailsView(View):
# change_data["address"]
# Save for Nick
is_change_addr_verified = True
button_data = ["Next"]
title = "Your Change"
if psbt_parser.num_change_outputs > 1:
title += f" (#{self.change_address_num + 1})"
button_data = [NEXT]
selected_menu_num = psbt_screens.PSBTChangeDetailsScreen(
title=title,
@@ -297,7 +323,10 @@ class PSBTChangeDetailsView(View):
is_change_addr_verified=is_change_addr_verified,
).display()
if button_data[selected_menu_num] == NEXT:
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
elif button_data[selected_menu_num] == NEXT:
if self.change_address_num < psbt_parser.num_change_outputs - 1:
return Destination(PSBTChangeDetailsView, view_args={"change_address_num": self.change_address_num + 1})
else:
@@ -307,9 +336,6 @@ class PSBTChangeDetailsView(View):
elif button_data[selected_menu_num] == VERIFY_MULTISIG:
return Destination(NotYetImplementedView)
elif selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
class PSBTFinalizeView(View):
+33 -18
View File
@@ -505,23 +505,21 @@ class SeedValidView(View):
def run(self):
from .psbt_views import PSBTOverviewView
SIGN_PSBT = "Sign PSBT (?)"
SIGN_PSBT = "Review PSBT"
SCAN_PSBT = ("Scan a PSBT", FontAwesomeIconConstants.QRCODE)
PASSPHRASE = ("Add Passphrase", FontAwesomeIconConstants.UNLOCK)
SEED_TOOLS = "Seed Options"
button_data = []
# Can we auto-route past this screen entirely?
if self.controller.psbt:
if PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=self.seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
# The Seed we just entered can sign the psbt we have in memory.
SIGN_PSBT = "Sign PSBT"
# # Immediately forward on to the PSBT Overview.
# seed_num = self.controller.storage.finalize_pending_seed()
# self.controller.psbt_seed = self.seed
# return Destination(PSBTOverviewView, clear_history=True)
# Don't auto-route to a signable psbt. Just display it.
if not PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=self.seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
# Seed doesn't match any input fingerprints
# TODO: Is there ever a use-case for letting someone try to sign with a
# seed that doesn't match?
SIGN_PSBT += " (?)"
else:
# Don't auto-route to a signable psbt. Just display the button.
pass
button_data.append(SIGN_PSBT)
else:
button_data.append(SCAN_PSBT)
@@ -542,12 +540,8 @@ class SeedValidView(View):
).display()
if selected_menu_num == RET_CODE__BACK_BUTTON:
# Back button should clear out the pending seed
self.controller.storage.clear_pending_seed()
# BACK means exit/cancel; returning to MainMenuView also automatically wipes
# the back_stack history.
return Destination(MainMenuView)
# Warning message that this will discard the pending seed
return Destination(SeedDiscardView)
elif button_data[selected_menu_num] == SIGN_PSBT:
self.controller.storage.finalize_pending_seed()
@@ -570,6 +564,27 @@ class SeedValidView(View):
class SeedDiscardView(View):
def run(self):
YES = "Yes"
NO = "No"
button_data = [YES, NO]
selected_menu_num = LargeButtonScreen(
title="Discard Seed?",
button_data=button_data,
show_top_nav_left_button=False,
).display()
if button_data[selected_menu_num] == YES:
self.controller.storage.clear_pending_seed()
return Destination(MainMenuView)
elif button_data[selected_menu_num] == NO:
return Destination(SeedValidView)
class SeedAddPassphrasePromptView(View):
def run(self):
YES = "Yes"
@@ -640,7 +655,7 @@ class SeedReviewPassphraseView(View):
fingerprint_with=fingerprint_with,
passphrase=self.seed.passphrase,
button_data=button_data,
show_top_nav_back_button=False,
show_top_nav_left_button=False,
).display()
if selected_menu_num == RET_CODE__BACK_BUTTON:
+5 -5
View File
@@ -115,8 +115,8 @@ class MainMenuView(View):
title="Home",
title_font_size=26,
button_data=[entry[0] for entry in menu_items],
show_top_nav_back_button=False,
show_top_nav_power_button=True,
show_top_nav_left_button=False,
show_top_nav_right_button=True,
)
selected_menu_num = screen.display()
@@ -134,7 +134,7 @@ class PowerOptionsView(View):
button_data = [RESET, POWER_OFF]
selected_menu_num = LargeButtonScreen(
title="Reset / Power",
show_top_nav_back_button=True,
show_top_nav_left_button=True,
button_data=button_data
).display()
@@ -156,7 +156,7 @@ class ResetView(View):
TextTopNavScreen(
title="Resetting",
text="SeedSigner is restarting. All data will be wiped.",
show_top_nav_back_button=False,
show_top_nav_left_button=False,
).display()
@@ -178,7 +178,7 @@ class PowerOffView(View):
TextTopNavScreen(
title="Powering Off",
text="Please wait about 30 seconds before disconnecting power.",
show_top_nav_back_button=False,
show_top_nav_left_button=False,
).display()