-
Notifications
You must be signed in to change notification settings - Fork 315
dialog: Use Cupertino-flavored alert dialogs on iOS #1017
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,20 +1,32 @@ | ||||||||||||
import 'package:flutter/cupertino.dart'; | ||||||||||||
import 'package:flutter/foundation.dart'; | ||||||||||||
import 'package:flutter/material.dart'; | ||||||||||||
|
||||||||||||
import '../generated/l10n/zulip_localizations.dart'; | ||||||||||||
import 'actions.dart'; | ||||||||||||
|
||||||||||||
Widget _dialogActionText(String text) { | ||||||||||||
return Text( | ||||||||||||
text, | ||||||||||||
|
||||||||||||
// As suggested by | ||||||||||||
// https://api.flutter.dev/flutter/material/AlertDialog/actions.html : | ||||||||||||
// > It is recommended to set the Text.textAlign to TextAlign.end | ||||||||||||
// > for the Text within the TextButton, so that buttons whose | ||||||||||||
// > labels wrap to an extra line align with the overall | ||||||||||||
// > OverflowBar's alignment within the dialog. | ||||||||||||
textAlign: TextAlign.end, | ||||||||||||
); | ||||||||||||
/// A platform-appropriate action for [AlertDialog.adaptive]'s [actions] param. | ||||||||||||
Widget _adaptiveAction({required VoidCallback onPressed, required String text}) { | ||||||||||||
switch (defaultTargetPlatform) { | ||||||||||||
case TargetPlatform.android: | ||||||||||||
case TargetPlatform.fuchsia: | ||||||||||||
case TargetPlatform.linux: | ||||||||||||
case TargetPlatform.windows: | ||||||||||||
return TextButton( | ||||||||||||
onPressed: onPressed, | ||||||||||||
child: Text( | ||||||||||||
text, | ||||||||||||
// As suggested by | ||||||||||||
// https://api.flutter.dev/flutter/material/AlertDialog/actions.html : | ||||||||||||
// > It is recommended to set the Text.textAlign to TextAlign.end | ||||||||||||
// > for the Text within the TextButton, so that buttons whose | ||||||||||||
// > labels wrap to an extra line align with the overall | ||||||||||||
// > OverflowBar's alignment within the dialog. | ||||||||||||
textAlign: TextAlign.end)); | ||||||||||||
case TargetPlatform.iOS: | ||||||||||||
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: a blank line to separate these cases helps make the structure easier to see:
Suggested change
|
||||||||||||
case TargetPlatform.macOS: | ||||||||||||
return CupertinoDialogAction(onPressed: onPressed, child: Text(text)); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Tracks the status of a dialog, in being still open or already closed. | ||||||||||||
|
@@ -46,17 +58,18 @@ DialogStatus showErrorDialog({ | |||||||||||
final zulipLocalizations = ZulipLocalizations.of(context); | ||||||||||||
final future = showDialog<void>( | ||||||||||||
context: context, | ||||||||||||
builder: (BuildContext context) => AlertDialog( | ||||||||||||
builder: (BuildContext context) => AlertDialog.adaptive( | ||||||||||||
title: Text(title), | ||||||||||||
content: message != null ? SingleChildScrollView(child: Text(message)) : null, | ||||||||||||
actions: [ | ||||||||||||
if (learnMoreButtonUrl != null) | ||||||||||||
TextButton( | ||||||||||||
_adaptiveAction( | ||||||||||||
onPressed: () => PlatformActions.launchUrl(context, learnMoreButtonUrl), | ||||||||||||
child: _dialogActionText(zulipLocalizations.errorDialogLearnMore)), | ||||||||||||
TextButton( | ||||||||||||
text: zulipLocalizations.errorDialogLearnMore, | ||||||||||||
), | ||||||||||||
Comment on lines
+68
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: preserve formatting
Suggested change
|
||||||||||||
_adaptiveAction( | ||||||||||||
onPressed: () => Navigator.pop(context), | ||||||||||||
child: _dialogActionText(zulipLocalizations.errorDialogContinue)), | ||||||||||||
text: zulipLocalizations.errorDialogContinue), | ||||||||||||
])); | ||||||||||||
return DialogStatus(future); | ||||||||||||
} | ||||||||||||
|
@@ -71,18 +84,18 @@ void showSuggestedActionDialog({ | |||||||||||
final zulipLocalizations = ZulipLocalizations.of(context); | ||||||||||||
showDialog<void>( | ||||||||||||
context: context, | ||||||||||||
builder: (BuildContext context) => AlertDialog( | ||||||||||||
builder: (BuildContext context) => AlertDialog.adaptive( | ||||||||||||
title: Text(title), | ||||||||||||
content: SingleChildScrollView(child: Text(message)), | ||||||||||||
actions: [ | ||||||||||||
TextButton( | ||||||||||||
_adaptiveAction( | ||||||||||||
onPressed: () => Navigator.pop(context), | ||||||||||||
child: _dialogActionText(zulipLocalizations.dialogCancel)), | ||||||||||||
TextButton( | ||||||||||||
text: zulipLocalizations.dialogCancel), | ||||||||||||
_adaptiveAction( | ||||||||||||
onPressed: () { | ||||||||||||
onActionButtonPress(); | ||||||||||||
Navigator.pop(context); | ||||||||||||
}, | ||||||||||||
child: _dialogActionText(actionButtonText ?? zulipLocalizations.dialogContinue)), | ||||||||||||
text: actionButtonText ?? zulipLocalizations.dialogContinue), | ||||||||||||
])); | ||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,15 @@ | ||
import 'package:checks/checks.dart'; | ||
import 'package:flutter/cupertino.dart'; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_checks/flutter_checks.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:url_launcher/url_launcher.dart'; | ||
import 'package:zulip/widgets/dialog.dart'; | ||
|
||
/// In a widget test, check that showErrorDialog was called with the right text. | ||
import '../model/binding.dart'; | ||
|
||
/// In a widget test, check that [showErrorDialog] was called with the right text. | ||
/// | ||
/// Checks for an error dialog matching an expected title | ||
/// and, optionally, matching an expected message. Fails if none is found. | ||
|
@@ -14,25 +19,55 @@ import 'package:zulip/widgets/dialog.dart'; | |
Widget checkErrorDialog(WidgetTester tester, { | ||
required String expectedTitle, | ||
String? expectedMessage, | ||
Uri? expectedLearnMoreButtonUrl, | ||
}) { | ||
final dialog = tester.widget<AlertDialog>(find.byType(AlertDialog)); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle))); | ||
if (expectedMessage != null) { | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage))); | ||
} | ||
switch (defaultTargetPlatform) { | ||
case TargetPlatform.android: | ||
case TargetPlatform.fuchsia: | ||
case TargetPlatform.linux: | ||
case TargetPlatform.windows: | ||
final dialog = tester.widget<AlertDialog>(find.bySubtype<AlertDialog>()); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle))); | ||
if (expectedMessage != null) { | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage))); | ||
} | ||
if (expectedLearnMoreButtonUrl != null) { | ||
check(testBinding.takeLaunchUrlCalls()).single.equals(( | ||
url: expectedLearnMoreButtonUrl, | ||
mode: LaunchMode.inAppBrowserView)); | ||
} | ||
Comment on lines
+36
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking for this button is useful, but this check doesn't work in this context. The check will work only if the caller has already gone and tapped on the button. There's nothing in this function's dartdoc that says the caller should do that, and it's not something a reader would naturally assume they need to do. If a caller is going and tapping on the button themself, then the caller can also easily go and do this check — the check isn't using any specific knowledge about how the error dialogs work. It looks like there's one call site that passes this option. So let's move this check to that call site. |
||
|
||
return tester.widget(find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(TextButton, 'OK'))); | ||
|
||
// TODO check "Learn more" button? | ||
case TargetPlatform.iOS: | ||
case TargetPlatform.macOS: | ||
final dialog = tester.widget<CupertinoAlertDialog>(find.byType(CupertinoAlertDialog)); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle))); | ||
if (expectedMessage != null) { | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage))); | ||
} | ||
if (expectedLearnMoreButtonUrl != null) { | ||
check(testBinding.takeLaunchUrlCalls()).single.equals(( | ||
url: expectedLearnMoreButtonUrl, | ||
mode: LaunchMode.externalApplication)); | ||
} | ||
|
||
return tester.widget( | ||
find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(TextButton, 'OK'))); | ||
return tester.widget(find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(CupertinoDialogAction, 'OK'))); | ||
} | ||
} | ||
|
||
// TODO(#996) update this to check for per-platform flavors of alert dialog | ||
void checkNoErrorDialog(WidgetTester tester) { | ||
check(find.byType(AlertDialog)).findsNothing(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, in #1017 (comment) I meant those two checks were redundant with each other. If there's no widget that's of a subtype of AlertDialog, there's certainly none that's of the exact type AlertDialog. |
||
/// Checks that there is no dialog. | ||
/// Fails if one is found. | ||
void checkNoDialog(WidgetTester tester) { | ||
check(find.byType(Dialog)).findsNothing(); | ||
check(find.bySubtype<AlertDialog>()).findsNothing(); | ||
check(find.byType(CupertinoAlertDialog)).findsNothing(); | ||
} | ||
|
||
/// In a widget test, check that [showSuggestedActionDialog] was called | ||
|
@@ -49,19 +84,35 @@ void checkNoErrorDialog(WidgetTester tester) { | |
required String expectedMessage, | ||
String? expectedActionButtonText, | ||
}) { | ||
final dialog = tester.widget<AlertDialog>(find.byType(AlertDialog)); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle))); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage))); | ||
switch (defaultTargetPlatform) { | ||
case TargetPlatform.android: | ||
case TargetPlatform.fuchsia: | ||
case TargetPlatform.linux: | ||
case TargetPlatform.windows: | ||
final dialog = tester.widget<AlertDialog>(find.bySubtype<AlertDialog>()); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle))); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage))); | ||
|
||
final actionButton = tester.widget( | ||
find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(TextButton, expectedActionButtonText ?? 'Continue'))); | ||
final actionButton = tester.widget(find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(TextButton, expectedActionButtonText ?? 'Continue'))); | ||
final cancelButton = tester.widget(find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(TextButton, 'Cancel'))); | ||
return (actionButton, cancelButton); | ||
|
||
final cancelButton = tester.widget( | ||
find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(TextButton, 'Cancel'))); | ||
case TargetPlatform.iOS: | ||
case TargetPlatform.macOS: | ||
final dialog = tester.widget<CupertinoAlertDialog>(find.byType(CupertinoAlertDialog)); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle))); | ||
tester.widget(find.descendant(matchRoot: true, | ||
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage))); | ||
|
||
return (actionButton, cancelButton); | ||
final actionButton = tester.widget(find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(CupertinoDialogAction, expectedActionButtonText ?? 'Continue'))); | ||
final cancelButton = tester.widget(find.descendant(of: find.byWidget(dialog), | ||
matching: find.widgetWithText(CupertinoDialogAction, 'Cancel'))); | ||
return (actionButton, cancelButton); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.