Skip to content

Commit fba1233

Browse files
committed
dialog [nfc]: showSuggestedActionDialog returns DialogStatus with result
With this API, we can use showSuggestedActionDialog in an "early return" style -- await the user's choice, and early return if it was "Cancel". That's particularly helpful when the confirmation dialog belongs in an `if` block. We'll use this for the upcoming "edit message" feature (zulip#126), where we plan to offer a confirmation dialog before entering an edit-message session, but only if the compose box has text for a new message in it (which would be discarded if the user wants to go ahead).
1 parent 56a275e commit fba1233

File tree

4 files changed

+66
-28
lines changed

4 files changed

+66
-28
lines changed

lib/widgets/app.dart

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,16 +308,17 @@ class ChooseAccountPage extends StatelessWidget {
308308
trailing: MenuAnchor(
309309
menuChildren: [
310310
MenuItemButton(
311-
onPressed: () {
312-
showSuggestedActionDialog(context: context,
311+
onPressed: () async {
312+
final dialog = showSuggestedActionDialog(context: context,
313313
title: zulipLocalizations.logOutConfirmationDialogTitle,
314314
message: zulipLocalizations.logOutConfirmationDialogMessage,
315315
// TODO(#1032) "destructive" style for action button
316-
actionButtonText: zulipLocalizations.logOutConfirmationDialogConfirmButton,
317-
onActionButtonPress: () {
318-
// TODO error handling if db write fails?
319-
logOutAccount(GlobalStoreWidget.of(context), accountId);
320-
});
316+
actionButtonText: zulipLocalizations.logOutConfirmationDialogConfirmButton);
317+
if (await dialog.closed == SuggestedActionDialogResult.doAction) {
318+
if (!context.mounted) return;
319+
// TODO error handling if db write fails?
320+
unawaited(logOutAccount(GlobalStoreWidget.of(context), accountId));
321+
}
321322
},
322323
child: Text(zulipLocalizations.chooseAccountPageLogOutButton)),
323324
],

lib/widgets/compose_box.dart

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:async';
12
import 'dart:math';
23

34
import 'package:app_settings/app_settings.dart';
@@ -907,13 +908,13 @@ Future<Iterable<_File>> _getFilePickerFiles(BuildContext context, FileType type)
907908
// If the user hasn't checked "Don't ask again", they can always dismiss
908909
// our prompt and retry, and the permissions request will reappear,
909910
// letting them grant permissions and complete the upload.
910-
showSuggestedActionDialog(context: context,
911+
final dialog = showSuggestedActionDialog(context: context,
911912
title: zulipLocalizations.permissionsNeededTitle,
912913
message: zulipLocalizations.permissionsDeniedReadExternalStorage,
913-
actionButtonText: zulipLocalizations.permissionsNeededOpenSettings,
914-
onActionButtonPress: () {
915-
AppSettings.openAppSettings();
916-
});
914+
actionButtonText: zulipLocalizations.permissionsNeededOpenSettings);
915+
if (await dialog.closed == SuggestedActionDialogResult.doAction) {
916+
unawaited(AppSettings.openAppSettings());
917+
}
917918
} else {
918919
showErrorDialog(context: context,
919920
title: zulipLocalizations.errorDialogTitle,
@@ -1008,13 +1009,13 @@ class _AttachFromCameraButton extends _AttachUploadsButton {
10081009
// permission-request alert once, the first time the app wants to
10091010
// use a protected resource. After that, the only way the user can
10101011
// grant it is in Settings.
1011-
showSuggestedActionDialog(context: context,
1012+
final dialog = showSuggestedActionDialog(context: context,
10121013
title: zulipLocalizations.permissionsNeededTitle,
10131014
message: zulipLocalizations.permissionsDeniedCameraAccess,
1014-
actionButtonText: zulipLocalizations.permissionsNeededOpenSettings,
1015-
onActionButtonPress: () {
1016-
AppSettings.openAppSettings();
1017-
});
1015+
actionButtonText: zulipLocalizations.permissionsNeededOpenSettings);
1016+
if (await dialog.closed == SuggestedActionDialogResult.doAction) {
1017+
unawaited(AppSettings.openAppSettings());
1018+
}
10181019
} else {
10191020
showErrorDialog(context: context,
10201021
title: zulipLocalizations.errorDialogTitle,

lib/widgets/dialog.dart

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ Widget _dialogActionText(String text) {
2121
///
2222
/// See also:
2323
/// * [showDialog], whose return value this class is intended to wrap.
24-
class DialogStatus {
24+
class DialogStatus<T> {
2525
const DialogStatus(this.closed);
2626

2727
/// Resolves when the dialog is closed.
28-
final Future<void> closed;
28+
final Future<T?> closed;
2929
}
3030

3131
/// Displays an [AlertDialog] with a dismiss button
@@ -37,7 +37,7 @@ class DialogStatus {
3737
// [showDialog]'s return value, a [Future], inside [DialogStatus]
3838
// whose documentation can be accessed. This helps avoid confusion when
3939
// intepreting the meaning of the [Future].
40-
DialogStatus showErrorDialog({
40+
DialogStatus<void> showErrorDialog({
4141
required BuildContext context,
4242
required String title,
4343
String? message,
@@ -61,28 +61,32 @@ DialogStatus showErrorDialog({
6161
return DialogStatus(future);
6262
}
6363

64-
void showSuggestedActionDialog({
64+
DialogStatus<SuggestedActionDialogResult> showSuggestedActionDialog({
6565
required BuildContext context,
6666
required String title,
6767
required String message,
6868
required String? actionButtonText,
69-
required VoidCallback onActionButtonPress,
7069
}) {
7170
final zulipLocalizations = ZulipLocalizations.of(context);
72-
showDialog<void>(
71+
final future = showDialog<SuggestedActionDialogResult>(
7372
context: context,
7473
builder: (BuildContext context) => AlertDialog(
7574
title: Text(title),
7675
content: SingleChildScrollView(child: Text(message)),
7776
actions: [
7877
TextButton(
79-
onPressed: () => Navigator.pop(context),
78+
onPressed: () => Navigator.pop<SuggestedActionDialogResult>(context,
79+
SuggestedActionDialogResult.cancel),
8080
child: _dialogActionText(zulipLocalizations.dialogCancel)),
8181
TextButton(
82-
onPressed: () {
83-
onActionButtonPress();
84-
Navigator.pop(context);
85-
},
82+
onPressed: () => Navigator.pop<SuggestedActionDialogResult>(context,
83+
SuggestedActionDialogResult.doAction),
8684
child: _dialogActionText(actionButtonText ?? zulipLocalizations.dialogContinue)),
8785
]));
86+
return DialogStatus(future);
87+
}
88+
89+
enum SuggestedActionDialogResult {
90+
cancel,
91+
doAction,
8892
}

test/widgets/dialog_test.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,36 @@ void main() {
2626
mode: LaunchMode.inAppBrowserView));
2727
});
2828
});
29+
30+
group('showSuggestedActionDialog', () {
31+
testWidgets('tap action button', (tester) async {
32+
addTearDown(testBinding.reset);
33+
await tester.pumpWidget(TestZulipApp());
34+
await tester.pump();
35+
final element = tester.element(find.byType(Placeholder));
36+
37+
final dialog = showSuggestedActionDialog(context: element,
38+
title: 'Continue?',
39+
message: 'Do the thing?',
40+
actionButtonText: 'Sure',);
41+
await tester.pump();
42+
await tester.tap(find.text('Sure'));
43+
await check(dialog.closed).completes((it) => it.equals(SuggestedActionDialogResult.doAction));
44+
});
45+
46+
testWidgets('tap cancel', (tester) async {
47+
addTearDown(testBinding.reset);
48+
await tester.pumpWidget(TestZulipApp());
49+
await tester.pump();
50+
final element = tester.element(find.byType(Placeholder));
51+
52+
final dialog = showSuggestedActionDialog(context: element,
53+
title: 'Continue?',
54+
message: 'Do the thing?',
55+
actionButtonText: 'Sure',);
56+
await tester.pump();
57+
await tester.tap(find.text('Cancel'));
58+
await check(dialog.closed).completes((it) => it.equals(SuggestedActionDialogResult.cancel));
59+
});
60+
});
2961
}

0 commit comments

Comments
 (0)