This PR introduces a new `showAdaptiveAboutDialog` function, ensuring that the About dialog matches the platformâs design (Material for Android, Fuchsia, Linux, Windows, and, Cupertino for iOS & macOS), providing a more consistent user experience across platforms. Fixes #155269 - [] I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
This commit is contained in:
parent
eb5ef69d6b
commit
5fa5a64842
@ -9,6 +9,7 @@ library;
|
||||
import 'dart:developer' show Flow, Timeline;
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:flutter/cupertino.dart' show CupertinoDialogAction;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/widgets.dart' hide Flow;
|
||||
@ -211,6 +212,62 @@ void showAboutDialog({
|
||||
);
|
||||
}
|
||||
|
||||
/// Displays either a Material or Cupertino [AboutDialog] depending on platform,
|
||||
/// which describes the application and provides a button to show licenses
|
||||
/// for software used by the application.
|
||||
///
|
||||
/// The arguments correspond to the properties on [AboutDialog].
|
||||
///
|
||||
/// If the application has a [Drawer], consider using [AboutListTile] instead
|
||||
/// of calling this directly.
|
||||
///
|
||||
/// If you do not need an about box in your application, you should at least
|
||||
/// provide an affordance to call [showLicensePage].
|
||||
///
|
||||
/// The licenses shown on the [LicensePage] are those returned by the
|
||||
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||
///
|
||||
/// On most platforms this function will act the same as [showDialog], except
|
||||
/// for iOS and macOS, in which case it will act the same as
|
||||
/// [showCupertinoDialog].
|
||||
///
|
||||
/// The [context], [barrierDismissible], [barrierColor], [barrierLabel],
|
||||
/// [useRootNavigator], [routeSettings] and [anchorPoint] arguments are
|
||||
/// passed to [showAdaptiveDialog], the documentation for which discusses how it is used.
|
||||
void showAdaptiveAboutDialog({
|
||||
required BuildContext context,
|
||||
String? applicationName,
|
||||
String? applicationVersion,
|
||||
Widget? applicationIcon,
|
||||
String? applicationLegalese,
|
||||
List<Widget>? children,
|
||||
bool barrierDismissible = true,
|
||||
Color? barrierColor,
|
||||
String? barrierLabel,
|
||||
bool useRootNavigator = true,
|
||||
RouteSettings? routeSettings,
|
||||
Offset? anchorPoint,
|
||||
}) {
|
||||
showAdaptiveDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: barrierDismissible,
|
||||
barrierColor: barrierColor,
|
||||
barrierLabel: barrierLabel,
|
||||
useRootNavigator: useRootNavigator,
|
||||
builder: (BuildContext context) {
|
||||
return AboutDialog.adaptive(
|
||||
applicationName: applicationName,
|
||||
applicationVersion: applicationVersion,
|
||||
applicationIcon: applicationIcon,
|
||||
applicationLegalese: applicationLegalese,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
routeSettings: routeSettings,
|
||||
anchorPoint: anchorPoint,
|
||||
);
|
||||
}
|
||||
|
||||
/// Displays a [LicensePage], which shows licenses for software used by the
|
||||
/// application.
|
||||
///
|
||||
@ -282,6 +339,28 @@ class AboutDialog extends StatelessWidget {
|
||||
this.children,
|
||||
});
|
||||
|
||||
/// Creates an adaptive [AboutDialog] based on whether the target platform is
|
||||
/// iOS or macOS, following Material design's
|
||||
/// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html).
|
||||
///
|
||||
/// Typically passed as a child of [showAdaptiveAboutDialog], which will display
|
||||
/// the [AboutDialog] differently based on platform.
|
||||
///
|
||||
/// This constructor offers the same customization options as the default
|
||||
/// [AboutDialog] constructor, allowing you to specify the application's
|
||||
/// [applicationName], [applicationVersion], [applicationIcon],
|
||||
/// [applicationLegalese], and additional [children] that appear in the dialog.
|
||||
///
|
||||
/// The target platform is based on the current [Theme]: [ThemeData.platform].
|
||||
const factory AboutDialog.adaptive({
|
||||
Key? key,
|
||||
String? applicationName,
|
||||
String? applicationVersion,
|
||||
Widget? applicationIcon,
|
||||
String? applicationLegalese,
|
||||
List<Widget>? children,
|
||||
}) = _AdaptiveAboutDialog;
|
||||
|
||||
/// The name of the application.
|
||||
///
|
||||
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
|
||||
@ -384,6 +463,119 @@ class AboutDialog extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AdaptiveAboutDialog extends AboutDialog {
|
||||
const _AdaptiveAboutDialog({
|
||||
super.key,
|
||||
super.applicationName,
|
||||
super.applicationVersion,
|
||||
super.applicationIcon,
|
||||
super.applicationLegalese,
|
||||
super.children,
|
||||
});
|
||||
|
||||
List<Widget>? _actions(BuildContext context) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
|
||||
switch (themeData.platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
return <Widget>[
|
||||
CupertinoDialogAction(
|
||||
child: Text(themeData.useMaterial3
|
||||
? localizations.viewLicensesButtonLabel
|
||||
: localizations.viewLicensesButtonLabel.toUpperCase()),
|
||||
onPressed: () {
|
||||
showLicensePage(
|
||||
context: context,
|
||||
applicationName: applicationName,
|
||||
applicationVersion: applicationVersion,
|
||||
applicationIcon: applicationIcon,
|
||||
applicationLegalese: applicationLegalese,
|
||||
);
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(themeData.useMaterial3
|
||||
? localizations.closeButtonLabel
|
||||
: localizations.closeButtonLabel.toUpperCase()),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
];
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return <Widget>[
|
||||
TextButton(
|
||||
child: Text(themeData.useMaterial3
|
||||
? localizations.viewLicensesButtonLabel
|
||||
: localizations.viewLicensesButtonLabel.toUpperCase()),
|
||||
onPressed: () {
|
||||
showLicensePage(
|
||||
context: context,
|
||||
applicationName: applicationName,
|
||||
applicationVersion: applicationVersion,
|
||||
applicationIcon: applicationIcon,
|
||||
applicationLegalese: applicationLegalese,
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(themeData.useMaterial3
|
||||
? localizations.closeButtonLabel
|
||||
: localizations.closeButtonLabel.toUpperCase()),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
final String name = applicationName ?? _defaultApplicationName(context);
|
||||
final String version = applicationVersion ?? _defaultApplicationVersion(context);
|
||||
final Widget? icon = applicationIcon ?? _defaultApplicationIcon(context);
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final List<Widget>? actions = _actions(context);
|
||||
|
||||
return AlertDialog.adaptive(
|
||||
content: ListBody(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (icon != null) IconTheme(data: themeData.iconTheme, child: icon),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text(name, style: themeData.textTheme.headlineSmall),
|
||||
Text(version, style: themeData.textTheme.bodyMedium),
|
||||
const SizedBox(height: _textVerticalSeparation),
|
||||
Text(applicationLegalese ?? '', style: themeData.textTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
...?children,
|
||||
],
|
||||
),
|
||||
actions: actions,
|
||||
scrollable: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A page that shows licenses for software used by the application.
|
||||
///
|
||||
/// To show a [LicensePage], use [showLicensePage].
|
||||
|
@ -1841,6 +1841,141 @@ void main() {
|
||||
expect(renderParagraph.text.style!.color, theme.primaryTextTheme.titleLarge!.color);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Adaptive AboutDialog shows correct widget on each platform',(WidgetTester tester) async {
|
||||
for (final TargetPlatform platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(platform: platform),
|
||||
home: const Material(
|
||||
child: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: null,
|
||||
child: Text('Go'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final BuildContext context = tester.element(find.text('Go'));
|
||||
|
||||
showAdaptiveAboutDialog(
|
||||
context: context,
|
||||
applicationIcon: const Icon(Icons.abc),
|
||||
applicationName: 'Test',
|
||||
applicationVersion: '1.0.0',
|
||||
applicationLegalese: 'Application Legalese',
|
||||
children: <Widget>[
|
||||
const Text('Test1'),
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
expect(find.byType(CupertinoDialogAction), findsWidgets);
|
||||
}
|
||||
|
||||
for (final TargetPlatform platform in <TargetPlatform>[
|
||||
TargetPlatform.android,
|
||||
TargetPlatform.fuchsia,
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.windows,
|
||||
]) {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(platform: platform),
|
||||
home: const Material(
|
||||
child: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: null,
|
||||
child: Text('Go'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final BuildContext context = tester.element(find.text('Go'));
|
||||
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationIcon: const Icon(Icons.abc),
|
||||
applicationName: 'Test',
|
||||
applicationVersion: '1.0.0',
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
expect(find.byType(CupertinoDialogAction), findsNothing);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Adaptive AboutDialog closes correctly on each platform', (WidgetTester tester) async {
|
||||
for (final TargetPlatform platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(platform: platform),
|
||||
home: const Material(
|
||||
child: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: null,
|
||||
child: Text('Go'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final BuildContext context = tester.element(find.text('Go'));
|
||||
|
||||
showAdaptiveAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'Test',
|
||||
applicationVersion: '1.0.0',
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
expect(find.byType(CupertinoDialogAction), findsWidgets);
|
||||
|
||||
await tester.tap(find.text('Close'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(CupertinoAlertDialog), findsNothing);
|
||||
}
|
||||
|
||||
for (final TargetPlatform platform in <TargetPlatform>[
|
||||
TargetPlatform.android,
|
||||
TargetPlatform.fuchsia,
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.windows,
|
||||
]) {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(platform: platform),
|
||||
home: const Material(
|
||||
child: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: null,
|
||||
child: Text('Go'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final BuildContext context = tester.element(find.text('Go'));
|
||||
|
||||
showAdaptiveAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'Test',
|
||||
applicationVersion: '1.0.0',
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
expect(find.byType(TextButton), findsWidgets);
|
||||
|
||||
await tester.tap(find.text('Close'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(AlertDialog), findsNothing);
|
||||
}});
|
||||
}
|
||||
|
||||
class FakeLicenseEntry extends LicenseEntry {
|
||||
|
Loading…
x
Reference in New Issue
Block a user