Skip to content

Commit b78ea35

Browse files
chrisbobbegnprice
authored andcommitted
dialog: Use "default" and "destructive" styles in iOS alert buttons
Fixes #1032. See Apple's HIG document: https://developer.apple.com/design/human-interface-guidelines/alerts And note that Android doesn't vary the button styles in these ways; see the description of #1032.
1 parent b505be2 commit b78ea35

File tree

8 files changed

+39
-7
lines changed

8 files changed

+39
-7
lines changed

lib/widgets/action_sheet.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ class UnsubscribeButton extends ActionSheetMenuItemButton {
643643
final dialog = showSuggestedActionDialog(context: pageContext,
644644
title: zulipLocalizations.unsubscribeConfirmationDialogTitle(subscription.name),
645645
message: zulipLocalizations.unsubscribeConfirmationDialogMessageMaybeCannotResubscribe,
646-
// TODO(#1032) "destructive" style for action button
646+
destructiveActionButton: true,
647647
actionButtonText: zulipLocalizations.unsubscribeConfirmationDialogConfirmButton);
648648
if (await dialog.result != true) return;
649649
if (!pageContext.mounted) return;

lib/widgets/app.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ class ChooseAccountPage extends StatelessWidget {
347347
final dialog = showSuggestedActionDialog(context: context,
348348
title: zulipLocalizations.logOutConfirmationDialogTitle,
349349
message: zulipLocalizations.logOutConfirmationDialogMessage,
350-
// TODO(#1032) "destructive" style for action button
350+
destructiveActionButton: true,
351351
actionButtonText: zulipLocalizations.logOutConfirmationDialogConfirmButton);
352352
if (await dialog.result == true) {
353353
if (!context.mounted) return;

lib/widgets/compose_box.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2024,7 +2024,7 @@ class _ComposeBoxState extends State<ComposeBox> with PerAccountStoreAwareStateM
20242024
final dialog = showSuggestedActionDialog(context: context,
20252025
title: zulipLocalizations.discardDraftConfirmationDialogTitle,
20262026
message: dialogMessage,
2027-
// TODO(#1032) "destructive" style for action button
2027+
destructiveActionButton: true,
20282028
actionButtonText: zulipLocalizations.discardDraftConfirmationDialogConfirmButton);
20292029
if (await dialog.result != true) return true;
20302030
}

lib/widgets/dialog.dart

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ import 'content.dart';
1010
import 'store.dart';
1111

1212
/// A platform-appropriate action for [AlertDialog.adaptive]'s [actions] param.
13-
Widget _adaptiveAction({required VoidCallback onPressed, required String text}) {
13+
///
14+
/// [isDefaultAction] and [isDestructiveAction] are ignored on Android
15+
/// because Material Design doesn't specify corresponding styles.
16+
Widget _adaptiveAction({
17+
required VoidCallback onPressed,
18+
required bool isDefaultAction,
19+
bool isDestructiveAction = false,
20+
required String text,
21+
}) {
1422
switch (defaultTargetPlatform) {
1523
case TargetPlatform.android:
1624
case TargetPlatform.fuchsia:
@@ -30,7 +38,11 @@ Widget _adaptiveAction({required VoidCallback onPressed, required String text})
3038

3139
case TargetPlatform.iOS:
3240
case TargetPlatform.macOS:
33-
return CupertinoDialogAction(onPressed: onPressed, child: Text(text));
41+
return CupertinoDialogAction(
42+
onPressed: onPressed,
43+
isDefaultAction: isDefaultAction,
44+
isDestructiveAction: isDestructiveAction,
45+
child: Text(text));
3446
}
3547
}
3648

@@ -112,9 +124,11 @@ DialogStatus<void> showErrorDialog({
112124
if (learnMoreButtonUrl != null)
113125
_adaptiveAction(
114126
onPressed: () => PlatformActions.launchUrl(context, learnMoreButtonUrl),
127+
isDefaultAction: false,
115128
text: zulipLocalizations.errorDialogLearnMore),
116129
_adaptiveAction(
117130
onPressed: () => Navigator.pop(context),
131+
isDefaultAction: true,
118132
text: zulipLocalizations.errorDialogContinue),
119133
]));
120134
return DialogStatus(future);
@@ -133,6 +147,7 @@ DialogStatus<bool> showSuggestedActionDialog({
133147
required String title,
134148
required String message,
135149
required String? actionButtonText,
150+
bool destructiveActionButton = false,
136151
}) {
137152
final zulipLocalizations = ZulipLocalizations.of(context);
138153
final future = showDialog<bool>(
@@ -143,9 +158,12 @@ DialogStatus<bool> showSuggestedActionDialog({
143158
actions: [
144159
_adaptiveAction(
145160
onPressed: () => Navigator.pop<bool>(context, null),
161+
isDefaultAction: false,
146162
text: zulipLocalizations.dialogCancel),
147163
_adaptiveAction(
148164
onPressed: () => Navigator.pop<bool>(context, true),
165+
isDefaultAction: true,
166+
isDestructiveAction: destructiveActionButton,
149167
text: actionButtonText ?? zulipLocalizations.dialogContinue),
150168
]));
151169
return DialogStatus(future);
@@ -213,6 +231,7 @@ class UpgradeWelcomeDialog extends StatelessWidget {
213231
actions: [
214232
_adaptiveAction(
215233
onPressed: () => Navigator.pop(context),
234+
isDefaultAction: true,
216235
text: zulipLocalizations.upgradeWelcomeDialogDismiss)
217236
]);
218237
}

test/widgets/action_sheet_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ void main() {
576576
final (unsubscribeButton, cancelButton) = checkSuggestedActionDialog(tester,
577577
expectedTitle: 'Unsubscribe from ${channel.name}?',
578578
expectedMessage: 'Once you leave this channel, you might not be able to rejoin.',
579+
expectDestructiveActionButton: true,
579580
expectedActionButtonText: 'Unsubscribe');
580581
await tester.tap(find.byWidget(unsubscribeButton));
581582
await tester.pump(Duration.zero);

test/widgets/app_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ void main() {
382382
return checkSuggestedActionDialog(tester,
383383
expectedTitle: 'Log out?',
384384
expectedMessage: 'To use this account in the future, you will have to re-enter the URL for your organization and your account information.',
385+
expectDestructiveActionButton: true,
385386
expectedActionButtonText: 'Log out');
386387
}
387388

test/widgets/compose_box_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,7 @@ void main() {
16451645
final (actionButton, cancelButton) = checkSuggestedActionDialog(tester,
16461646
expectedTitle: 'Discard the message you’re writing?',
16471647
expectedMessage: expectedMessage,
1648+
expectDestructiveActionButton: true,
16481649
expectedActionButtonText: 'Discard');
16491650
if (shouldContinue) {
16501651
await tester.tap(find.byWidget(actionButton));

test/widgets/dialog_checks.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,18 @@ void checkNoDialog(WidgetTester tester) {
100100
/// Checks for a suggested-action dialog matching an expected title and message.
101101
/// Fails if none is found.
102102
///
103+
/// Use [expectDestructiveActionButton] to check whether
104+
/// the button is "destructive" (see [showSuggestedActionDialog]).
105+
/// This has no effect on Android because the "destructive" style is iOS-only.
106+
///
103107
/// On success, returns a Record with the widget's action button first
104108
/// and its cancel button second.
105109
/// Tap the action button by calling `tester.tap(find.byWidget(actionButton))`.
106110
(Widget, Widget) checkSuggestedActionDialog(WidgetTester tester, {
107111
required String expectedTitle,
108112
required String expectedMessage,
109113
String? expectedActionButtonText,
114+
bool expectDestructiveActionButton = false,
110115
}) {
111116
switch (defaultTargetPlatform) {
112117
case TargetPlatform.android:
@@ -133,8 +138,13 @@ void checkNoDialog(WidgetTester tester) {
133138
tester.widget(find.descendant(matchRoot: true,
134139
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));
135140

136-
final actionButton = tester.widget(find.descendant(of: find.byWidget(dialog),
137-
matching: find.widgetWithText(CupertinoDialogAction, expectedActionButtonText ?? 'Continue')));
141+
final actionButton = tester.widget<CupertinoDialogAction>(
142+
find.descendant(
143+
of: find.byWidget(dialog),
144+
matching: find.widgetWithText(
145+
CupertinoDialogAction,
146+
expectedActionButtonText ?? 'Continue')));
147+
check(actionButton.isDestructiveAction).equals(expectDestructiveActionButton);
138148
final cancelButton = tester.widget(find.descendant(of: find.byWidget(dialog),
139149
matching: find.widgetWithText(CupertinoDialogAction, 'Cancel')));
140150
return (actionButton, cancelButton);

0 commit comments

Comments
 (0)