-
Notifications
You must be signed in to change notification settings - Fork 267
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
login: Support logging out of an account (take 2) #1010
Conversation
lib/widgets/app.dart
Outdated
return Card( | ||
clipBehavior: Clip.hardEdge, | ||
child: ListTile( | ||
title: title, | ||
subtitle: subtitle, | ||
trailing: PopupMenuButton<AccountItemOverflowMenuItem>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the PopupMenuButton doc recommends using the newer MenuAnchor instead. What if we used that — how would it differ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not much different, but I'm happy to use the more modern thing. 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, SGTM
9e006cc
to
c0f4c78
Compare
Greg spotted this opportunity to be a bit more modern: zulip#1010 (comment)
c0f4c78
to
3fe188e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Generally this looks good. Comments below from a full review.
@alya see question below about wording for a confirmation modal.
Then one other thing this should have before we merge it is some tests, specifically for the last commit. This is kind of tricky code, and it's the sort of feature that we infrequently exercise but that is important to people when they do use it — so that makes tests extra useful.
return IconButton( | ||
tooltip: materialLocalizations.showMenuTooltip, // "Show menu" | ||
onPressed: () { | ||
if (controller.isOpen) { | ||
controller.close(); | ||
} else { | ||
controller.open(); | ||
} | ||
}, | ||
icon: Icon(Icons.adaptive.more, color: designVariables.icon)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems annoying that with the new API we have to recite all this standard stuff. But so be it. If we find this coming up more, we can abstract it out.
lib/widgets/app.dart
Outdated
MenuItemButton( | ||
onPressed: () => unawaited(_logOutAccount(context, accountId)), | ||
child: Text(zulipLocalizations.chooseAccountPageLogOutButton)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before actually logging the user out, let's have a confirmation modal. This is something that would be pretty annoying to do by accident.
Some draft text for the modal:
Log out?
This will sign you out on this device from this Zulip account.
(buttons:) Log out / Cancel
with red "destructive action" styling on the "Log out" button.
Or I guess here's what we have in zulip-mobile:
showConfirmationDialog({
destructive: true,
title: 'Remove account',
message: {
text: 'This will make the mobile app on this device forget {email} on {realmUrl}.',
values: { realmUrl: realm.toString(), email },
so we could always just copy that.
@alya, do you have feedback on the possible confirmation text?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
red "destructive action" styling
Sure. I've just filed #1032 for this; how about I leave this part as a TODO(#1032)
in my next revision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, SGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, maybe we should try to explain the impact on the user a bit more? Something like this, assuming it's accurate?
Log out?
To use this account in the future, you will have to re-enter the URL for your organization and your account information.
(buttons:) Log out / Cancel
lib/widgets/app.dart
Outdated
Future<void> _logOutAccount(BuildContext context, int accountId) async { | ||
final globalStore = GlobalStoreWidget.of(context); | ||
|
||
final account = globalStore.getAccount(accountId); | ||
if (account == null) return; // TODO(log) | ||
|
||
// Unawaited, to not block removing the account on this request. | ||
unawaited(_unregisterToken(globalStore, account)); | ||
|
||
await globalStore.removeAccount(accountId); | ||
} | ||
|
||
Future<void> _unregisterToken(GlobalStore globalStore, Account account) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a code-organization perspective, this class and this file don't feel like the right home for these two methods — they're not really part of this UI, but rather the logic for an action the user might take, where this page just happens to be the initial home of the only place to reach that action in the UI. (It might not always be — for example in a future settings page, we might have a button to log out.)
Probably widgets/actions.dart would be good.
I also considered widgets/login.dart , since it's logically related to the things that happen there. But it doesn't really fit with any of the particular UI widgets there.
Ultimately they likely belong as methods on GlobalStore. But I wouldn't want to put them in store.dart
, which is too crowded as it is — they'd be defined elsewhere and mixed in, akin to ChannelStore
and its various accessor methods. So I think actions.dart
is a fine home for them at least until then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think
actions.dart
is a fine home for them at least until then.
Cool, yeah, that does look like a good place for it.
lib/widgets/app.dart
Outdated
await NotificationService.unregisterToken(connection, token: token); | ||
} catch (e) { | ||
// TODO retry? handle failures? | ||
} finally { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely post-launch to do anything about that — zulip-mobile doesn't do anything about failures here either.
Greg spotted this opportunity to be a bit more modern: zulip#1010 (comment)
3fe188e
to
c5c1105
Compare
Thanks for the review! Revision pushed, this time with a bunch of tests. 🙂 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the revision! Generally this all looks good; just small comments below.
Would you also post a screenshot of the new confirmation dialog?
test/widgets/actions_test.dart
Outdated
|
||
final future = logOutAccount(context, eg.selfAccount.id); | ||
// Unregister-token request and account removal dispatched together | ||
assertSingleUnregisterRequest(newConnection); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this function wraps a check
call, so use the verb "check" to name it:
assertSingleUnregisterRequest(newConnection); | |
checkSingleUnregisterRequest(newConnection); |
test/widgets/actions_test.dart
Outdated
// No unexpected requests made on either connection | ||
check(connection.takeRequests()).isEmpty(); | ||
check(newConnection.takeRequests()).isEmpty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these are redundant with the check in send
— requests that are unexpected won't have a prepared response, so they'll throw, and the test framework will catch the unhandled exception and report.
test/widgets/actions_test.dart
Outdated
await future; | ||
assertSingleUnregisterRequest(newConnection, expectedToken: '123'); | ||
check(newConnection.isOpen).isFalse(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}); | |
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS})); |
That way we exercise both the Android and iOS cases in unregisterToken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I think adding this to just this test is enough.)
@@ -30,19 +41,247 @@ void main() { | |||
|
|||
Future<void> prepare(WidgetTester tester, { | |||
UnreadMessagesSnapshot? unreadMsgs, | |||
String? ackedPushToken = '123', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change can be squashed into the commit after it, which starts using this feature.
(It doesn't touch the same code as the later commit; and it doesn't really need its own commit message, in that it's self-explanatory given the context of that other commit.)
Greg spotted this opportunity to be a bit more modern: zulip#1010 (comment)
c5c1105
to
9f6a033
Compare
Greg spotted this opportunity to be a bit more modern: zulip#1010 (comment)
So far this function just has two callers in uncommon paths involving app permissions. For those, and for the new caller we're about to add -- logging out an account -- it's unhelpful for the dialog to linger unchanged after the action button is tapped. Better to make it go away, preventing confusion about whether the action was actually done.
Like we added `checkErrorDialog` in 02fd562.
Without this line, if a test `globalStore.add`s an account with a non-null `ackedPushToken`, the field will be null in app code or test code that retrieves the account from `globalStore`. Thankfully no tests have been doing that, but it'll be convenient to do that soon, for testing the unregister-token part of logging out.
9f6a033
to
0344686
Compare
Thanks! Looks good; merging. |
These are the three last commits from #948, following up on the just-merged #1007 to replace #948: #948 (review)
The screenshots in #948's description still match this code. As I said on #948, there might be some improvements to make to the UI, perhaps as a followup: #948 (comment)
Fixes: #463