|
4 | 4 |
|
5 | 5 | import 'dart:ui';
|
6 | 6 |
|
| 7 | +import 'package:flutter/cupertino.dart'; |
7 | 8 | import 'package:flutter/foundation.dart' show clampDouble;
|
8 |
| -import 'package:flutter/widgets.dart'; |
9 | 9 |
|
10 | 10 | import 'color_scheme.dart';
|
11 | 11 | import 'colors.dart';
|
@@ -395,6 +395,69 @@ class AlertDialog extends StatelessWidget {
|
395 | 395 | this.scrollable = false,
|
396 | 396 | });
|
397 | 397 |
|
| 398 | + /// Creates an adaptive [AlertDialog] based on whether the target platform is |
| 399 | + /// iOS or macOS, following Material design's |
| 400 | + /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html). |
| 401 | + /// |
| 402 | + /// On iOS and macOS, this constructor creates a [CupertinoAlertDialog]. On |
| 403 | + /// other platforms, this creates a Material design [AlertDialog]. |
| 404 | + /// |
| 405 | + /// Typically passed as a child of [showAdaptiveDialog], which will display |
| 406 | + /// the alert differently based on platform. |
| 407 | + /// |
| 408 | + /// If a [CupertinoAlertDialog] is created only these parameters are used: |
| 409 | + /// [title], [content], [actions], [scrollController], |
| 410 | + /// [actionScrollController], [insetAnimationDuration], and |
| 411 | + /// [insetAnimationCurve]. If a material [AlertDialog] is created, |
| 412 | + /// [scrollController], [actionScrollController], [insetAnimationDuration], |
| 413 | + /// and [insetAnimationCurve] are ignored. |
| 414 | + /// |
| 415 | + /// The target platform is based on the current [Theme]: [ThemeData.platform]. |
| 416 | + /// |
| 417 | + /// {@tool dartpad} |
| 418 | + /// This demo shows a [TextButton] which when pressed, calls [showAdaptiveDialog]. |
| 419 | + /// When called, this method displays an adaptive dialog above the current |
| 420 | + /// contents of the app, with different behaviors depending on target platform. |
| 421 | + /// |
| 422 | + /// [CupertinoDialogAction] is conditionally used as the child to show more |
| 423 | + /// platform specific design. |
| 424 | + /// |
| 425 | + /// ** See code in examples/api/lib/material/dialog/adaptive_alert_dialog.0.dart ** |
| 426 | + /// {@end-tool} |
| 427 | + const factory AlertDialog.adaptive({ |
| 428 | + Key? key, |
| 429 | + Widget? icon, |
| 430 | + EdgeInsetsGeometry? iconPadding, |
| 431 | + Color? iconColor, |
| 432 | + Widget? title, |
| 433 | + EdgeInsetsGeometry? titlePadding, |
| 434 | + TextStyle? titleTextStyle, |
| 435 | + Widget? content, |
| 436 | + EdgeInsetsGeometry? contentPadding, |
| 437 | + TextStyle? contentTextStyle, |
| 438 | + List<Widget>? actions, |
| 439 | + EdgeInsetsGeometry? actionsPadding, |
| 440 | + MainAxisAlignment? actionsAlignment, |
| 441 | + OverflowBarAlignment? actionsOverflowAlignment, |
| 442 | + VerticalDirection? actionsOverflowDirection, |
| 443 | + double? actionsOverflowButtonSpacing, |
| 444 | + EdgeInsetsGeometry? buttonPadding, |
| 445 | + Color? backgroundColor, |
| 446 | + double? elevation, |
| 447 | + Color? shadowColor, |
| 448 | + Color? surfaceTintColor, |
| 449 | + String? semanticLabel, |
| 450 | + EdgeInsets insetPadding, |
| 451 | + Clip clipBehavior, |
| 452 | + ShapeBorder? shape, |
| 453 | + AlignmentGeometry? alignment, |
| 454 | + bool scrollable, |
| 455 | + ScrollController? scrollController, |
| 456 | + ScrollController? actionScrollController, |
| 457 | + Duration insetAnimationDuration, |
| 458 | + Curve insetAnimationCurve, |
| 459 | + }) = _AdaptiveAlertDialog; |
| 460 | + |
398 | 461 | /// An optional icon to display at the top of the dialog.
|
399 | 462 | ///
|
400 | 463 | /// Typically, an [Icon] widget. Providing an icon centers the [title]'s text.
|
@@ -638,6 +701,7 @@ class AlertDialog extends StatelessWidget {
|
638 | 701 | Widget build(BuildContext context) {
|
639 | 702 | assert(debugCheckHasMaterialLocalizations(context));
|
640 | 703 | final ThemeData theme = Theme.of(context);
|
| 704 | + |
641 | 705 | final DialogTheme dialogTheme = DialogTheme.of(context);
|
642 | 706 | final DialogTheme defaults = theme.useMaterial3 ? _DialogDefaultsM3(context) : _DialogDefaultsM2(context);
|
643 | 707 |
|
@@ -823,6 +887,71 @@ class AlertDialog extends StatelessWidget {
|
823 | 887 | }
|
824 | 888 | }
|
825 | 889 |
|
| 890 | +class _AdaptiveAlertDialog extends AlertDialog { |
| 891 | + const _AdaptiveAlertDialog({ |
| 892 | + super.key, |
| 893 | + super.icon, |
| 894 | + super.iconPadding, |
| 895 | + super.iconColor, |
| 896 | + super.title, |
| 897 | + super.titlePadding, |
| 898 | + super.titleTextStyle, |
| 899 | + super.content, |
| 900 | + super.contentPadding, |
| 901 | + super.contentTextStyle, |
| 902 | + super.actions, |
| 903 | + super.actionsPadding, |
| 904 | + super.actionsAlignment, |
| 905 | + super.actionsOverflowAlignment, |
| 906 | + super.actionsOverflowDirection, |
| 907 | + super.actionsOverflowButtonSpacing, |
| 908 | + super.buttonPadding, |
| 909 | + super.backgroundColor, |
| 910 | + super.elevation, |
| 911 | + super.shadowColor, |
| 912 | + super.surfaceTintColor, |
| 913 | + super.semanticLabel, |
| 914 | + super.insetPadding = _defaultInsetPadding, |
| 915 | + super.clipBehavior = Clip.none, |
| 916 | + super.shape, |
| 917 | + super.alignment, |
| 918 | + super.scrollable = false, |
| 919 | + this.scrollController, |
| 920 | + this.actionScrollController, |
| 921 | + this.insetAnimationDuration = const Duration(milliseconds: 100), |
| 922 | + this.insetAnimationCurve = Curves.decelerate, |
| 923 | + }); |
| 924 | + |
| 925 | + final ScrollController? scrollController; |
| 926 | + final ScrollController? actionScrollController; |
| 927 | + final Duration insetAnimationDuration; |
| 928 | + final Curve insetAnimationCurve; |
| 929 | + |
| 930 | + @override |
| 931 | + Widget build(BuildContext context) { |
| 932 | + final ThemeData theme = Theme.of(context); |
| 933 | + switch(theme.platform) { |
| 934 | + case TargetPlatform.android: |
| 935 | + case TargetPlatform.fuchsia: |
| 936 | + case TargetPlatform.linux: |
| 937 | + case TargetPlatform.windows: |
| 938 | + break; |
| 939 | + case TargetPlatform.iOS: |
| 940 | + case TargetPlatform.macOS: |
| 941 | + return CupertinoAlertDialog( |
| 942 | + title: title, |
| 943 | + content: content, |
| 944 | + actions: actions ?? <Widget>[], |
| 945 | + scrollController: scrollController, |
| 946 | + actionScrollController: actionScrollController, |
| 947 | + insetAnimationDuration: insetAnimationDuration, |
| 948 | + insetAnimationCurve: insetAnimationCurve, |
| 949 | + ); |
| 950 | + } |
| 951 | + return super.build(context); |
| 952 | + } |
| 953 | +} |
| 954 | + |
826 | 955 | /// An option used in a [SimpleDialog].
|
827 | 956 | ///
|
828 | 957 | /// A simple dialog offers the user a choice between several options. This
|
@@ -1308,6 +1437,58 @@ Future<T?> showDialog<T>({
|
1308 | 1437 | ));
|
1309 | 1438 | }
|
1310 | 1439 |
|
| 1440 | +/// Displays either a Material or Cupertino dialog depending on platform. |
| 1441 | +/// |
| 1442 | +/// On most platforms this function will act the same as [showDialog], except |
| 1443 | +/// for iOS and macOS, in which case it will act the same as |
| 1444 | +/// [showCupertinoDialog]. |
| 1445 | +/// |
| 1446 | +/// On Cupertino platforms, [barrierColor], [useSafeArea], and |
| 1447 | +/// [traversalEdgeBehavior] are ignored. |
| 1448 | +Future<T?> showAdaptiveDialog<T>({ |
| 1449 | + required BuildContext context, |
| 1450 | + required WidgetBuilder builder, |
| 1451 | + bool? barrierDismissible, |
| 1452 | + Color? barrierColor = Colors.black54, |
| 1453 | + String? barrierLabel, |
| 1454 | + bool useSafeArea = true, |
| 1455 | + bool useRootNavigator = true, |
| 1456 | + RouteSettings? routeSettings, |
| 1457 | + Offset? anchorPoint, |
| 1458 | + TraversalEdgeBehavior? traversalEdgeBehavior, |
| 1459 | +}) { |
| 1460 | + final ThemeData theme = Theme.of(context); |
| 1461 | + switch (theme.platform) { |
| 1462 | + case TargetPlatform.android: |
| 1463 | + case TargetPlatform.fuchsia: |
| 1464 | + case TargetPlatform.linux: |
| 1465 | + case TargetPlatform.windows: |
| 1466 | + return showDialog<T>( |
| 1467 | + context: context, |
| 1468 | + builder: builder, |
| 1469 | + barrierDismissible: barrierDismissible ?? true, |
| 1470 | + barrierColor: barrierColor, |
| 1471 | + barrierLabel: barrierLabel, |
| 1472 | + useSafeArea: useSafeArea, |
| 1473 | + useRootNavigator: useRootNavigator, |
| 1474 | + routeSettings: routeSettings, |
| 1475 | + anchorPoint: anchorPoint, |
| 1476 | + traversalEdgeBehavior: traversalEdgeBehavior, |
| 1477 | + ); |
| 1478 | + case TargetPlatform.iOS: |
| 1479 | + case TargetPlatform.macOS: |
| 1480 | + return showCupertinoDialog<T>( |
| 1481 | + context: context, |
| 1482 | + builder: builder, |
| 1483 | + barrierDismissible: barrierDismissible ?? false, |
| 1484 | + barrierLabel: barrierLabel, |
| 1485 | + useRootNavigator: useRootNavigator, |
| 1486 | + anchorPoint: anchorPoint, |
| 1487 | + routeSettings: routeSettings, |
| 1488 | + ); |
| 1489 | + } |
| 1490 | +} |
| 1491 | + |
1311 | 1492 | bool _debugIsActive(BuildContext context) {
|
1312 | 1493 | if (context is Element && !context.debugIsActive) {
|
1313 | 1494 | throw FlutterError.fromParts(<DiagnosticsNode>[
|
|
0 commit comments