mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-06 07:28:24 +00:00
Left nav top btn flexibility; Discard Seed dialogue; PSBT w/self-xfer and change fixes
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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__()
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user