mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-09-14 04:45:08 +00:00
Merge pull request #600 from alvroble/microsd_toast_timer
[Enhancement] MicroSD toast timer settings
This commit is contained in:
@@ -11,6 +11,7 @@ from seedsigner.models.psbt_parser import PSBTParser
|
||||
from seedsigner.models.seed import Seed
|
||||
from seedsigner.models.seed_storage import SeedStorage
|
||||
from seedsigner.models.settings import Settings
|
||||
from seedsigner.models.settings import SettingsConstants
|
||||
from seedsigner.models.singleton import Singleton
|
||||
from seedsigner.models.threads import BaseThread
|
||||
from seedsigner.views.screensaver import ScreensaverScreen
|
||||
@@ -252,7 +253,7 @@ class Controller(Singleton):
|
||||
* initial_destination: The first View to run. If None, the MainMenuView is
|
||||
used. Only used by the test suite.
|
||||
"""
|
||||
from seedsigner.views import MainMenuView, BackStackView
|
||||
from seedsigner.views import MainMenuView, BackStackView, RemoveMicroSDWarningView
|
||||
from seedsigner.views.screensaver import OpeningSplashView
|
||||
from seedsigner.gui.toast import RemoveSDCardToastManagerThread
|
||||
|
||||
@@ -290,7 +291,10 @@ class Controller(Singleton):
|
||||
next_destination = Destination(MainMenuView)
|
||||
|
||||
# Set up our one-time toast notification tip to remove the SD card
|
||||
self.activate_toast(RemoveSDCardToastManagerThread())
|
||||
if self.settings.get_value(SettingsConstants.SETTING__MICROSD_TOAST_TIMER) == SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS:
|
||||
self.activate_toast(RemoveSDCardToastManagerThread())
|
||||
elif self.settings.get_value(SettingsConstants.SETTING__MICROSD_TOAST_TIMER) == SettingsConstants.MICROSD_TOAST_TIMER_FOREVER:
|
||||
next_destination = Destination(RemoveMicroSDWarningView)
|
||||
|
||||
while True:
|
||||
# Destination(None) is a special case; render the Home screen
|
||||
|
||||
@@ -201,11 +201,11 @@ class BaseToastOverlayManagerThread(BaseThread):
|
||||
|
||||
|
||||
class RemoveSDCardToastManagerThread(BaseToastOverlayManagerThread):
|
||||
def __init__(self, activation_delay: int = 3, duration: int = 1e6):
|
||||
def __init__(self, activation_delay: int = 3, duration: int = 5):
|
||||
"""
|
||||
* activation_delay: configurable so the screenshot generator can get the
|
||||
toast to immediately render.
|
||||
* duration: default value is essentially forever. Overrideable for the
|
||||
* duration: default value is 5 seconds. Overrideable for the
|
||||
screenshot generator.
|
||||
"""
|
||||
super().__init__(
|
||||
|
||||
@@ -293,6 +293,15 @@ class SettingsConstants:
|
||||
(CUSTOM_DERIVATION, _mft("Custom Derivation")),
|
||||
]
|
||||
|
||||
MICROSD_TOAST_TIMER_DISABLED = "D"
|
||||
MICROSD_TOAST_TIMER_FIVE_SECONDS = "E"
|
||||
MICROSD_TOAST_TIMER_FOREVER = "inf"
|
||||
ALL_MICROSD_TOAST_TIMERS = [
|
||||
(MICROSD_TOAST_TIMER_DISABLED, "Disabled"),
|
||||
(MICROSD_TOAST_TIMER_FIVE_SECONDS, "5 seconds"),
|
||||
(MICROSD_TOAST_TIMER_FOREVER, "Until SD removed")
|
||||
]
|
||||
|
||||
WORDLIST_LANGUAGE__ENGLISH = "en"
|
||||
WORDLIST_LANGUAGE__CHINESE_SIMPLIFIED = "zh_Hans_CN"
|
||||
WORDLIST_LANGUAGE__CHINESE_TRADITIONAL = "zh_Hant_TW"
|
||||
@@ -339,6 +348,7 @@ class SettingsConstants:
|
||||
SETTING__DIRE_WARNINGS = "dire_warnings"
|
||||
SETTING__QR_BRIGHTNESS_TIPS = "qr_brightness_tips"
|
||||
SETTING__PARTNER_LOGOS = "partner_logos"
|
||||
SETTING__MICROSD_TOAST_TIMER = "microsd_toast_timer"
|
||||
|
||||
SETTING__DEBUG = "debug"
|
||||
|
||||
@@ -665,6 +675,14 @@ class SettingsDefinition:
|
||||
help_text=_mft("Native Segwit only"),
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
default_value=SettingsConstants.OPTION__DISABLED),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__MICROSD_TOAST_TIMER,
|
||||
display_name="MicroSD toast timer",
|
||||
type=SettingsConstants.TYPE__SELECT_1,
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
selection_options=SettingsConstants.ALL_MICROSD_TOAST_TIMERS,
|
||||
default_value=SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__MESSAGE_SIGNING,
|
||||
|
||||
@@ -150,11 +150,15 @@ class SettingsEntryUpdateSelectionView(View):
|
||||
Handles changes to all selection-type settings (Multiselect, SELECT_1,
|
||||
Enabled/Disabled, etc).
|
||||
"""
|
||||
def __init__(self, attr_name: str, parent_initial_scroll: int = 0, selected_button: int = None):
|
||||
def __init__(self, attr_name: str, parent_initial_scroll: int = 0, selected_button: int = None, blocking_view: View = None, unblocking_view: View = None):
|
||||
super().__init__()
|
||||
self.settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
||||
self.selected_button = selected_button
|
||||
self.parent_initial_scroll = parent_initial_scroll
|
||||
# If the setting remains unchanged, navigation should return to blocking_view (if set)
|
||||
self.blocking_view = blocking_view
|
||||
# unblocking_view is an optional target to navigate to once the setting actually changes.
|
||||
self.unblocking_view = unblocking_view
|
||||
|
||||
|
||||
def run(self):
|
||||
@@ -200,6 +204,8 @@ class SettingsEntryUpdateSelectionView(View):
|
||||
)
|
||||
|
||||
if ret_value == RET_CODE__BACK_BUTTON:
|
||||
if self.blocking_view:
|
||||
return Destination(self.blocking_view, clear_history=True)
|
||||
return settings_menu_view_destination
|
||||
|
||||
value = self.settings_entry.get_selection_option_value(ret_value)
|
||||
@@ -219,8 +225,7 @@ class SettingsEntryUpdateSelectionView(View):
|
||||
|
||||
else:
|
||||
# All other types are single selects (e.g. Enabled/Disabled, SELECT_1)
|
||||
if value == initial_value:
|
||||
# No change, return to menu
|
||||
if value == initial_value and not self.blocking_view:
|
||||
return settings_menu_view_destination
|
||||
else:
|
||||
updated_value = value
|
||||
@@ -238,11 +243,21 @@ class SettingsEntryUpdateSelectionView(View):
|
||||
|
||||
if destination:
|
||||
return destination
|
||||
|
||||
# If this selection view was opened from a blocking flow (e.g. RemoveMicroSDWarningView),
|
||||
# prevent navigation away until the setting actually changes. If it hasn't changed,
|
||||
# return to the blocking view so it can re-evaluate the state.
|
||||
if self.blocking_view:
|
||||
current_value = self.settings.get_value(self.settings_entry.attr_name)
|
||||
if current_value == initial_value:
|
||||
return Destination(self.blocking_view, clear_history=True)
|
||||
elif self.unblocking_view:
|
||||
return Destination(self.unblocking_view, clear_history=True)
|
||||
|
||||
# All selects stay in place; re-initialize where in the list we left off
|
||||
self.selected_button = ret_value
|
||||
|
||||
return Destination(SettingsEntryUpdateSelectionView, view_args=dict(attr_name=self.settings_entry.attr_name, parent_initial_scroll=self.parent_initial_scroll, selected_button=self.selected_button), skip_current_view=True)
|
||||
return Destination(SettingsEntryUpdateSelectionView, view_args=dict(attr_name=self.settings_entry.attr_name, parent_initial_scroll=self.parent_initial_scroll, selected_button=self.selected_button, blocking_view=self.blocking_view, unblocking_view=self.unblocking_view), skip_current_view=True)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -407,3 +407,39 @@ class OptionDisabledView(View):
|
||||
return Destination(SettingsEntryUpdateSelectionView, view_args=dict(attr_name=self.settings_attr), clear_history=True)
|
||||
else:
|
||||
return Destination(MainMenuView, clear_history=True)
|
||||
|
||||
|
||||
|
||||
class RemoveMicroSDWarningView(View):
|
||||
CONTINUE = ButtonOption("Continue")
|
||||
SETTINGS = ButtonOption("Settings")
|
||||
|
||||
def run(self):
|
||||
button_data = [self.CONTINUE, self.SETTINGS]
|
||||
selected_menu_num = self.run_screen(
|
||||
WarningScreen,
|
||||
title=_("Action Required"),
|
||||
status_icon_name=SeedSignerIconConstants.MICROSD,
|
||||
status_headline=None,
|
||||
text=_("You must remove the\nMicroSD card to continue."),
|
||||
show_back_button=False,
|
||||
button_data=button_data,
|
||||
)
|
||||
|
||||
if button_data[selected_menu_num] == self.CONTINUE:
|
||||
from seedsigner.hardware.microsd import MicroSD
|
||||
if not MicroSD.get_instance().is_inserted:
|
||||
return Destination(MainMenuView, clear_history=True)
|
||||
else:
|
||||
return Destination(RemoveMicroSDWarningView, clear_history=True)
|
||||
|
||||
elif button_data[selected_menu_num] == self.SETTINGS:
|
||||
from seedsigner.views.settings_views import SettingsEntryUpdateSelectionView
|
||||
return Destination(
|
||||
SettingsEntryUpdateSelectionView,
|
||||
view_args=dict(
|
||||
attr_name=SettingsConstants.SETTING__MICROSD_TOAST_TIMER,
|
||||
blocking_view=RemoveMicroSDWarningView,
|
||||
unblocking_view=MainMenuView
|
||||
)
|
||||
)
|
||||
@@ -39,7 +39,7 @@ 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 (MainMenuView, PowerOptionsView, RestartView, NotYetImplementedView, UnhandledExceptionView,
|
||||
from seedsigner.views import (MainMenuView, PowerOptionsView, RestartView, RemoveMicroSDWarningView, NotYetImplementedView, UnhandledExceptionView,
|
||||
psbt_views, seed_views, settings_views, tools_views, scan_views)
|
||||
from seedsigner.views.screensaver import OpeningSplashView
|
||||
from seedsigner.views.view import NetworkMismatchErrorView, OptionDisabledView, PowerOffView
|
||||
@@ -291,6 +291,7 @@ def generate_screenshots(locale):
|
||||
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SDCardStateChangeToast_removed', toast_thread=SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__REMOVED, activation_delay=0, duration=0)),
|
||||
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SDCardStateChangeToast_inserted', toast_thread=SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__INSERTED, activation_delay=0, duration=0)),
|
||||
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_RemoveSDCardToast', toast_thread=RemoveSDCardToastManagerThread(activation_delay=0, duration=0)),
|
||||
ScreenshotConfig(RemoveMicroSDWarningView),
|
||||
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_DefaultToast', toast_thread=DefaultToast("This is a default text toast!", activation_delay=0, duration=0)),
|
||||
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_InfoToast', toast_thread=InfoToast("This is an info toast!", activation_delay=0, duration=0)),
|
||||
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SuccessToast', toast_thread=SuccessToast("This is a success toast!", activation_delay=0, duration=0)),
|
||||
|
||||
@@ -110,6 +110,7 @@ class TestController(BaseTest):
|
||||
assert controller.settings.get_value(SettingsConstants.SETTING__DIRE_WARNINGS) == SettingsConstants.OPTION__ENABLED
|
||||
assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS) == SettingsConstants.OPTION__ENABLED
|
||||
assert controller.settings.get_value(SettingsConstants.SETTING__PARTNER_LOGOS) == SettingsConstants.OPTION__ENABLED
|
||||
assert controller.settings.get_value(SettingsConstants.SETTING__MICROSD_TOAST_TIMER) == SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS
|
||||
|
||||
# Hidden Settings defaults
|
||||
assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS) == 62
|
||||
|
||||
+49
-1
@@ -9,8 +9,12 @@ from seedsigner.models.seed import Seed
|
||||
from seedsigner.views import scan_views
|
||||
from seedsigner.views.psbt_views import PSBTSelectSeedView
|
||||
from seedsigner.views.seed_views import SeedBackupView, SeedMnemonicEntryView, SeedOptionsView, SeedsMenuView
|
||||
from seedsigner.views.view import Destination, MainMenuView, PowerOptionsView, UnhandledExceptionView, View
|
||||
from seedsigner.views.view import Destination, MainMenuView, PowerOptionsView, UnhandledExceptionView, RemoveMicroSDWarningView, MainMenuView, View
|
||||
from seedsigner.views.tools_views import ToolsMenuView, ToolsCalcFinalWordNumWordsView
|
||||
from seedsigner.views.settings_views import SettingsEntryUpdateSelectionView
|
||||
from seedsigner.models.settings_definition import SettingsDefinition
|
||||
from seedsigner.models.settings import SettingsConstants
|
||||
from seedsigner.hardware.microsd import MicroSD
|
||||
|
||||
|
||||
|
||||
@@ -192,3 +196,47 @@ class TestFlowTest(FlowTest):
|
||||
FlowStep(MainMenuView), # Need a next Destination to force the first step to run
|
||||
])
|
||||
|
||||
def test_remove_microsd_blocking(self):
|
||||
"""
|
||||
Verifies three related behaviors:
|
||||
|
||||
1) If the RemoveMicroSDWarningView launches the SettingsEntryUpdateSelectionView
|
||||
and the user presses Back without changing the tracked setting, the flow
|
||||
returns to RemoveMicroSDWarningView (the blocking condition remains).
|
||||
2) If the user changes the tracked setting while in the settings entry, the
|
||||
flow unblocks and navigates to MainMenuView.
|
||||
3) If the MicroSD is physically removed and the user presses Continue on the
|
||||
warning, the flow proceeds to MainMenuView.
|
||||
"""
|
||||
controller = Controller.get_instance()
|
||||
|
||||
settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__MICROSD_TOAST_TIMER)
|
||||
controller.settings.set_value(settings_entry.attr_name, SettingsConstants.MICROSD_TOAST_TIMER_FOREVER)
|
||||
|
||||
# There are only two ways of exiting RemoveMicroSDWarningView when SETTING__MICROSD_TOAST_TIMER -> MICROSD_TOAST_TIMER_FOREVER
|
||||
self.run_sequence([
|
||||
FlowStep(RemoveMicroSDWarningView, button_data_selection=RemoveMicroSDWarningView.SETTINGS),
|
||||
FlowStep(SettingsEntryUpdateSelectionView, screen_return_value=RET_CODE__BACK_BUTTON),
|
||||
FlowStep(RemoveMicroSDWarningView, button_data_selection=RemoveMicroSDWarningView.SETTINGS),
|
||||
# 1) Modifying the setting
|
||||
FlowStep(SettingsEntryUpdateSelectionView, screen_return_value=0),
|
||||
FlowStep(MainMenuView)
|
||||
])
|
||||
|
||||
self.reset_controller()
|
||||
controller = Controller.get_instance()
|
||||
|
||||
settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__MICROSD_TOAST_TIMER)
|
||||
controller.settings.set_value(settings_entry.attr_name, SettingsConstants.MICROSD_TOAST_TIMER_FOREVER)
|
||||
|
||||
# 2) Removing the MicroSD card and pressing CONTINUE
|
||||
self.mock_microsd.is_inserted = False
|
||||
assert MicroSD.get_instance().is_inserted is False
|
||||
|
||||
self.run_sequence([
|
||||
FlowStep(RemoveMicroSDWarningView, button_data_selection=RemoveMicroSDWarningView.CONTINUE),
|
||||
FlowStep(MainMenuView)
|
||||
])
|
||||
|
||||
# Restore the setting for the controller
|
||||
controller.settings.set_value(settings_entry.attr_name, SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS)
|
||||
Reference in New Issue
Block a user