Skip to content

Conversation

FazleRabbbiferdaus172
Copy link
Contributor

@FazleRabbbiferdaus172 FazleRabbbiferdaus172 commented Apr 17, 2025

Description

This PR addresses part of the discussed SoB idea in the seedsigner dev group providing a solution for the issue of the screensaver interrupting the seed QR transcribing process. Views now have a property that indicates whether the screensaver is allowed while the view is running. As a result, the screensaver will not start when users are transcribing the QR. This improves usability, especially when transcribing 21×21 SeedQRs, where the screensaver previously interrupted the flow. A future update may introduce a long timeout wipe or reboot as a fallback for inactivity.

This pull request is categorized as a:

  • New feature
  • Bug fix
  • Code refactor
  • Documentation
  • Other

Checklist

  • I’ve run pytest and made sure all unit tests pass before sumbitting the PR

If you modified or added functionality/workflow, did you add new unit tests?

  • No, I’m a fool
  • Yes
  • N/A

I have tested this PR on the following platforms/os:

Note: Keep your changes limited in scope; if you uncover other issues or improvements along the way, ideally submit those as a separate PR. The more complicated the PR the harder to review, test, and merge.


@property
def active_view(self) -> Optional[View]:
return self.back_stack[-1].view if self.back_stack else None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a slight conceptual mismatch for me for the MainMenuView, perhaps elsewhere, too:

  • MainMenuView is currently running. Literally the active View.
  • BackStack will be empty.
  • So this active_view() will return None, implying that there is no active View.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — there's a mismatch. I initially considered active_view as a separate property, since in most frontend/UI frameworks, the view manager typically keeps track of the current view, while a back stack handles history (like breadcrumbs). That’s why I assumed the controller, acting as a view manager, should explicitly manage active_view.

However, for simplicity, I aligned it with back_stack. But because the controller clears the stack before displaying MainMenuView, active_view() ends up returning None, which feels inconsistent.

To fix this, I’ll return MainMenuView when the stack is empty — that keeps the logic clean without introducing extra state tracking. Let me know if that sounds good; I’m happy to make the change.

Side note: I haven’t worked much with resource-constrained systems like Pi Zero, so open to suggestions if explicitly tracking active_view has performance implications.

@@ -1567,7 +1567,8 @@ def __init__(self, seed_num: int, seedqr_format: str, initial_block_x: int = 0,
self.seedqr_format = seedqr_format
self.seed = self.controller.get_seed(seed_num)
self.initial_block_x = initial_block_x
self.initial_block_y = initial_block_y
self.initial_block_y = initial_block_y
self.allow_screensaver = False
Copy link
Contributor

@kdmukai kdmukai Apr 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the rationale for this setting/override is pretty self-evident already, but I always try to add a comment to remind myself of any unusual setting or decision.

I would explicitly explain why it's important to not let the screensaver run here.

Copy link
Contributor Author

@FazleRabbbiferdaus172 FazleRabbbiferdaus172 Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add a comment .

@kdmukai
Copy link
Contributor

kdmukai commented Apr 19, 2025

I think exposing the Controller.active_view is providing more access than we might actually want.

The real question is just "Hey, Controller, can we run the screensaver on the current View?" I think I'd prefer a Controller function that ONLY answers that very specific question. It would also eliminate almost all of the changes in buttons.py.

@FazleRabbbiferdaus172
Copy link
Contributor Author

I think exposing the Controller.active_view is providing more access than we might actually want.

The real question is just "Hey, Controller, can we run the screensaver on the current View?" I think I'd prefer a Controller function that ONLY answers that very specific question. It would also eliminate almost all of the changes in buttons.py.

Sure, I will implement a method in the controller.

@FazleRabbbiferdaus172 FazleRabbbiferdaus172 force-pushed the improvement/non_emulator/Disable_screensaver branch from 80ccbfa to 393bbd1 Compare April 20, 2025 08:31
@FazleRabbbiferdaus172
Copy link
Contributor Author

Thanks for the review!
I've addressed the feedbacks and pushed new commits

@@ -466,3 +466,21 @@ def handle_exception(self, e) -> Destination:
exception_msg,
]
return Destination(UnhandledExceptionView, view_args={"error": error}, clear_history=True)

@property
def active_view(self) -> View:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in a previous comment, I would not expose this.

I would just do this retrieval within the can_run_screensaver() method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I understand. I explain my reasoning behind why a active_view property might be needed. But python doesn't have access specifier. So, I removed the property and merged the logic with can_run_screensaver

from seedsigner.views import MainMenuView
return self.back_stack[-1].view if self.back_stack else MainMenuView()

def can_run_screensaver(self) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make this a property.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: Saying that you "can do" something is more often about ability ("can you lift that weight?") whereas a phrase like "is allowed" has a different connotation (e.g. "can you light a match at a gas station?" Well, yes, it's physically possible but is it allowed?).

So if I were to read controller.can_run_screensaver == False it feels like: Car.can_start_engine == False, that means something's broken on my car.

I'm not sure what a better name would be...

screensaver_start_is_allowed?

(I admit that I do fuss quite a bit about how we name things)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My name preference would be something like is_screensaver_startable or is_screensaver_start_allowed. Following the naming convention of is_screensaver_running.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty bad at naming stuff. Also I have to admit that I am not satisfied with the name I have chosen either.

As newtonick suggested, is_screensaver_start_allowed seems a good choice.

@kdmukai
Copy link
Contributor

kdmukai commented Apr 22, 2025

From the PR description:

This PR addresses part of the discussed behavior.

What discussion? PRs should ideally be completely self-contained (fully explain the problem and/or rationale for the change) or at least point to where some deeper discussion occurred (though even then the PR description shouldn't assume that the reader is already familiar with the prior discussion).

@kdmukai
Copy link
Contributor

kdmukai commented Apr 22, 2025

It might be a slightly difficult test to write (anything is possible with enough Mocking), but you should be able to write a test that at least verifies the result of calling Controller.is_screensaver_start_allowed when running the SeedQR transcription screen.

Leveraging the flow-based test structure should get you most of the way there. There's at least one test somewhere in there that runs a flow-based test sequence and then verifies the status of the Controller or something like that.

If that ends up being too difficult, at least a # TODO comment should be placed somewhere and log the task as a new Github Issue.

@FazleRabbbiferdaus172
Copy link
Contributor Author

I will update the pr description and will try to add the test cases.

@FazleRabbbiferdaus172
Copy link
Contributor Author

I have pushed a commit that tests the sreensaver start status when SeedTranscribeSeedQRZoomedInView is the current view in the flow. But I have questions regarding back_stack destination is handled, initial_destination.

Questions

  1. Why is the initial_destination not included in the stack? pop_prev_from_back_stack pops twice. So, the back_stack should always hold the current running view as it's top element. But initial_destination is never in the back_stack. Logical difference arises when the flow starts from any view other than MainMenuView. is_screensaver_start_allowed will fail in that case as the implementation depends on back_stack and defaults to MainManuView when stack is empty.
  2. Is it logical to always consider MainManuView as the destination when the next_destination.View_cls is None?
    If is start controller with a initial_destination and then move to another view and then hit the back_button it should return to initial_destination. Current implementation instead return to MainMenuview.

@FazleRabbbiferdaus172 FazleRabbbiferdaus172 force-pushed the improvement/non_emulator/Disable_screensaver branch from e62be31 to c85de09 Compare July 26, 2025 15:22
@kdmukai
Copy link
Contributor

kdmukai commented Jul 29, 2025

  • Why is the initial_destination not included in the stack? pop_prev_from_back_stack pops twice. So, the back_stack should always hold the current running view as it's top element. But initial_destination is never in the back_stack. Logical difference arises when the flow starts from any view other than MainMenuView. is_screensaver_start_allowed will fail in that case as the implementation depends on back_stack and defaults to MainManuView when stack is empty.

initial_destination is only used by the test suite and it's a non-issue if its value for a particular flow test isn't preserved in the BackStack.

That being said, I probably could have streamlined the logic to add the current View to the BackStack just AFTER the View is run. In which case the current View wouldn't be at the top of the stack.

Adding it to the BackStack BEFORE the View is run leads to the need to do the double pop when the BACK arrow is returned.

I can't recall if there was a specific reason for doing it this way or maybe I just got myself into a sub-optimal solution while being confused by the loop + stack logic.

  • Is it logical to always consider MainManuView as the destination when the next_destination.View_cls is None?
    If is start controller with a initial_destination and then move to another view and then hit the back_button it should return to initial_destination. Current implementation instead return to MainMenuview.

Again, it's never a user-facing scenario. Strictly for the test suite.

@@ -476,6 +476,26 @@ def load_right_seed_into_decoder(view: View):
FlowStep(seed_views.SeedOptionsView),
])

# TODO: ideally this test should be part of controller related test? move it there
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to leave this flow test here to explicitly test that the screensaver cannot start during the interactive transcription process.

@kdmukai kdmukai moved this from SoB Needs Code Review to SoB In Progress in @SeedSigner Development Board Jul 29, 2025
@FazleRabbbiferdaus172
Copy link
Contributor Author

  • Why is the initial_destination not included in the stack? pop_prev_from_back_stack pops twice. So, the back_stack should always hold the current running view as it's top element. But initial_destination is never in the back_stack. Logical difference arises when the flow starts from any view other than MainMenuView. is_screensaver_start_allowed will fail in that case as the implementation depends on back_stack and defaults to MainManuView when stack is empty.

initial_destination is only used by the test suite and it's a non-issue if its value for a particular flow test isn't preserved in the BackStack.

That being said, I probably could have streamlined the logic to add the current View to the BackStack just AFTER the View is run. In which case the current View wouldn't be at the top of the stack.

Adding it to the BackStack BEFORE the View is run leads to the need to do the double pop when the BACK arrow is returned.

I can't recall if there was a specific reason for doing it this way or maybe I just got myself into a sub-optimal solution while being confused by the loop + stack logic.

  • Is it logical to always consider MainManuView as the destination when the next_destination.View_cls is None?
    If is start controller with a initial_destination and then move to another view and then hit the back_button it should return to initial_destination. Current implementation instead return to MainMenuview.

Again, it's never a user-facing scenario. Strictly for the test suite.

purpose of the questions were to determine if i should leave "is_screensaver_start_allowed" method and the "test" as it is or consider the initial_destination in the method. As per my understanding current implementation is ok as initial_destination is only for test suite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: SoB In Progress
Development

Successfully merging this pull request may close these issues.

3 participants