Skip to content

On launch, go to the last visited account #1784

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

sm-sayedi
Copy link
Collaborator

@sm-sayedi sm-sayedi commented Aug 5, 2025

Fixes: #524

@sm-sayedi sm-sayedi force-pushed the 524-launch-last-account branch 9 times, most recently from db70e92 to 52a8954 Compare August 9, 2025 19:37
@sm-sayedi sm-sayedi marked this pull request as ready for review August 9, 2025 19:41
@sm-sayedi sm-sayedi added the maintainer review PR ready for review by Zulip maintainers label Aug 9, 2025
@sm-sayedi sm-sayedi requested a review from chrisbobbe August 9, 2025 19:41
@sm-sayedi sm-sayedi force-pushed the 524-launch-last-account branch 2 times, most recently from fa65075 to e68d2fb Compare August 10, 2025 04:25
Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks! Comments below.

@@ -73,6 +73,16 @@ class BoolGlobalSettings extends Table {
Set<Column<Object>>? get primaryKey => {name};
}

@DataClassName('IntGlobalSettingRow')
class IntGlobalSettings extends Table {
Copy link
Collaborator

@chrisbobbe chrisbobbe Aug 8, 2025

Choose a reason for hiding this comment

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

The commit 5cfd4ae, which added BoolGlobalSettings, included a lot of useful material that I think should be easy to propagate to this new thing:

(EDIT: and I see some of these have actually been addressed in the latest revision; I'll strike out those parts. 🙂)

  • The commit message points to why the new thing is helpful, in fact as soon as the summary line
  • An implementation comment in GlobalSettings saying "consider whether [etc.] can do the job instead", which helps us get the most usefulness from the thing. Let's change that comment so that it also mentions the int global settings
  • Dartdocs on BoolGlobalSettings and its fields. (Let's also add bidirectional "See also:" notes in that class's dartdoc and in IntGlobalSettings's.)
  • Ditto the BoolGlobalSetting enum, and it also got this helpful-looking implementation comment:
      // Former settings which might exist in the database,
      // whose names should therefore not be reused:
      // (this list is empty so far)
    (No need to add a placeholderIgnore member, though; the pseudo-setting we're adding here isn't an "experimental feature" setting and we don't plan to remove it.)
  • Tests

Comment on lines 322 to 339
@override
void didPush(Route<void> route, Route<void>? previousRoute) {
_changeLastVisitedAccountIfNecessary(route);
}

@override
void didPop(Route<void> route, Route<void>? previousRoute) {
_changeLastVisitedAccountIfNecessary(previousRoute);
}

@override
void didRemove(Route<void> route, Route<void>? previousRoute) {
_changeLastVisitedAccountIfNecessary(previousRoute);
}

@override
void didReplace({Route<void>? newRoute, Route<void>? oldRoute}) {
_changeLastVisitedAccountIfNecessary(newRoute);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we just use didChangeTop instead of all these, and inline _changeLastVisitedAccountIfNecessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, that's interesting. I think it can replace them!

Comment on lines -340 to +364
unawaited(logOutAccount(GlobalStoreWidget.of(context), accountId));
unawaited(Future(() async {
if (!context.mounted) return;
await logOutAccount(GlobalStoreWidget.of(context), accountId);
if (!context.mounted) return;
await removeLastVisitedAccountIfNecessary(
GlobalStoreWidget.of(context), accountId);
}));
Copy link
Collaborator

Choose a reason for hiding this comment

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

The if (!context.mounted) return;—really the fact that we clear lastVisitedAccountId in a separate database transaction from removing the Account in logOutAccount—means it's possible to enter a state where lastVisitedAccountId is an ID of an account that doesn't actually exist. That seems problematic when, later, we pass lastVisitedAccountId to HomePage.buildRoute, which is UI code to show a page for the account. From reading code, I think the behavior might not be worse than showing the page for a frame then immediately hiding it…but even that seems glitchy and worth avoiding.

I see two options:

  • Clear lastVisitedAccountId in the same DB transaction as the one in doRemoveAccount
  • Don't bother clearing lastVisitedAccountId on logout, but make its interface clear (in dartdoc) that it might point to an account that doesn't exist because it was logged out. Then, before passing the value to HomePage.buildRoute, check that it refers to an account that actually exists. If not, just do the same as we do when no account has ever been visited, i.e., open the choose-account page.

Curious for @gnprice's thoughts on this too :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Went with the second option for the new revision until Greg shares his thoughts!

Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like the new revision implements something that's not really either of those options. :) To do the second option completely, let's remove the removeLastVisitedAccountIfNecessary method and the "very-rare-edge-case" branding, and just say that lastVisitedAccountId will commonly refer to a nonexistent account, and handling that possibility is the responsibility of any code that reads it.

Comment on lines 349 to 350
final (actionButton, _) = await prepare(tester,
accounts: [eg.selfAccount], logoutAccount: eg.selfAccount);
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about using [eg.selfAccount] and eg.selfAccount by default?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Made them the defaults in the new revision. But wouldn't it be good to have them specified explicitly in each test case? That way, the reader can easily confirm the lines in these test cases where it looks for the accounts list (without looking inside prepare and seeing what the default is):

testWidgets('user confirms logging out', (tester) async {
  final (actionButton, _) = await prepare(tester);
  // ...
  check(testBinding.globalStore).accounts.isEmpty();
}

testWidgets('user cancels logging out', (tester) async {
  final (_, cancelButton) = await prepare(tester);
  // ...
  check(testBinding.globalStore).accounts.deepEquals([eg.selfAccount]);
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see; sure, that's reasonable, SGTM.

This will result in better organization when more tests are added for
another type of settings (IntGlobalSettings) in the following commits.
Also make it explicitly specify which account to log out in the list of
accounts.
@sm-sayedi sm-sayedi force-pushed the 524-launch-last-account branch from e68d2fb to e29b7f0 Compare August 13, 2025 07:53
@sm-sayedi
Copy link
Collaborator Author

Thanks @chrisbobbe for the review. New changes pushed, PTAL.

@sm-sayedi sm-sayedi requested a review from chrisbobbe August 13, 2025 07:56
@sm-sayedi sm-sayedi force-pushed the 524-launch-last-account branch from e29b7f0 to 1b742f3 Compare August 13, 2025 18:33
@sm-sayedi
Copy link
Collaborator Author

sm-sayedi commented Aug 13, 2025

Pushed a new revision, making a small change to the newly-merged share feature, with the content being shared to the last visited account if present, or otherwise to the first one.

Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks! Comments below.

Comment on lines -340 to +364
unawaited(logOutAccount(GlobalStoreWidget.of(context), accountId));
unawaited(Future(() async {
if (!context.mounted) return;
await logOutAccount(GlobalStoreWidget.of(context), accountId);
if (!context.mounted) return;
await removeLastVisitedAccountIfNecessary(
GlobalStoreWidget.of(context), accountId);
}));
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like the new revision implements something that's not really either of those options. :) To do the second option completely, let's remove the removeLastVisitedAccountIfNecessary method and the "very-rare-edge-case" branding, and just say that lastVisitedAccountId will commonly refer to a nonexistent account, and handling that possibility is the responsibility of any code that reads it.

Comment on lines 349 to 350
final (actionButton, _) = await prepare(tester,
accounts: [eg.selfAccount], logoutAccount: eg.selfAccount);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see; sure, that's reasonable, SGTM.

Comment on lines +195 to +196
await testBinding.globalStore.settings
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the purpose of this setup; what goes wrong if we don't include it? IIUC these tests are about what happens when you tap on a notification, and that shouldn't be affected by the last-visited account, right? (It should just open whichever account the notification is for.)

]);
});

testWidgets('with last account visited, go to home page for last account', (tester) async {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I would order the most common, "happy-path" test before the others

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintainer review PR ready for review by Zulip maintainers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

On launch go to last account used, not just first in list
2 participants