未验证 提交 d79295af 编写于 作者: C Christopher Fujino 提交者: GitHub

[flutter_releases] Flutter Stable 2.2.2 Framework Cherrypicks (#84364)

* [flutter_tools] throw a tool exit if pub cannot be run (#83293)
* Re-add the removed MediaQuery.removePadding of PopupMenuButton (#82986)
* import pkg:intl when DateFormat or NumberFormat is used (#83122)
Co-authored-by: NJonah Williams <jonahwilliams@google.com>
Co-authored-by: Nxubaolin <xubaolin@oppo.com>
Co-authored-by: NMichael Goderbauer <goderbauer@google.com>
上级 02c026b0
0fdb562ac8068ce3dda6b69aca3f355f4d1d2718
91c9fc8fe011352879e3bb6660966eafc0847233
......@@ -618,8 +618,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
this.itemSizes,
this.selectedItemIndex,
this.textDirection,
this.topPadding,
this.bottomPadding,
this.padding,
);
// Rectangle of underlying button, relative to the overlay's dimensions.
......@@ -636,11 +635,8 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
// Whether to prefer going to the left or to the right.
final TextDirection textDirection;
// Top padding of unsafe area.
final double topPadding;
// Bottom padding of unsafe area.
final double bottomPadding;
// The padding of unsafe area.
EdgeInsets padding;
// We put the child wherever position specifies, so long as it will fit within
// the specified parent size padded (inset) by 8. If necessary, we adjust the
......@@ -651,7 +647,8 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
// The menu can be at most the size of the overlay minus 8.0 pixels in each
// direction.
return BoxConstraints.loose(constraints.biggest).deflate(
const EdgeInsets.all(_kMenuScreenPadding) + EdgeInsets.only(top: topPadding, bottom: bottomPadding));
const EdgeInsets.all(_kMenuScreenPadding) + padding,
);
}
@override
......@@ -694,14 +691,15 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
// Avoid going outside an area defined as the rectangle 8.0 pixels from the
// edge of the screen in every direction.
if (x < _kMenuScreenPadding)
x = _kMenuScreenPadding;
else if (x + childSize.width > size.width - _kMenuScreenPadding)
x = size.width - childSize.width - _kMenuScreenPadding;
if (y < _kMenuScreenPadding + topPadding)
y = _kMenuScreenPadding + topPadding;
else if (y + childSize.height > size.height - _kMenuScreenPadding - bottomPadding)
y = size.height - bottomPadding - _kMenuScreenPadding - childSize.height ;
if (x < _kMenuScreenPadding + padding.left)
x = _kMenuScreenPadding + padding.left;
else if (x + childSize.width > size.width - _kMenuScreenPadding - padding.right)
x = size.width - childSize.width - _kMenuScreenPadding - padding.right ;
if (y < _kMenuScreenPadding + padding.top)
y = _kMenuScreenPadding + padding.top;
else if (y + childSize.height > size.height - _kMenuScreenPadding - padding.bottom)
y = size.height - padding.bottom - _kMenuScreenPadding - childSize.height ;
return Offset(x, y);
}
......@@ -716,8 +714,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|| selectedItemIndex != oldDelegate.selectedItemIndex
|| textDirection != oldDelegate.textDirection
|| !listEquals(itemSizes, oldDelegate.itemSizes)
|| topPadding != oldDelegate.topPadding
|| bottomPadding != oldDelegate.bottomPadding;
|| padding != oldDelegate.padding;
}
}
......@@ -777,22 +774,27 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
}
final Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
return Builder(
builder: (BuildContext context) {
final MediaQueryData mediaQuery = MediaQuery.of(context);
return CustomSingleChildLayout(
delegate: _PopupMenuRouteLayout(
position,
itemSizes,
selectedItemIndex,
Directionality.of(context),
mediaQuery.padding.top,
mediaQuery.padding.bottom,
),
child: capturedThemes.wrap(menu),
);
},
final MediaQueryData mediaQuery = MediaQuery.of(context);
return MediaQuery.removePadding(
context: context,
removeTop: true,
removeBottom: true,
removeLeft: true,
removeRight: true,
child: Builder(
builder: (BuildContext context) {
return CustomSingleChildLayout(
delegate: _PopupMenuRouteLayout(
position,
itemSizes,
selectedItemIndex,
Directionality.of(context),
mediaQuery.padding,
),
child: capturedThemes.wrap(menu),
);
},
),
);
}
}
......
......@@ -2058,6 +2058,79 @@ void main() {
expect(popupMenu, Offset(button.dx - 8.0, button.dy + 8.0));
});
// Regression test for https://github.com/flutter/flutter/issues/82874
testWidgets('PopupMenu position test when have unsafe area - left/right padding', (WidgetTester tester) async {
final GlobalKey buttonKey = GlobalKey();
const EdgeInsets padding = EdgeInsets.only(left: 300.0, top: 32.0, right: 310.0, bottom: 64.0);
EdgeInsets? mediaQueryPadding;
Widget buildFrame(double width, double height) {
return MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: const MediaQueryData(
padding: padding,
),
child: child!,
);
},
home: Scaffold(
appBar: AppBar(
title: const Text('PopupMenu Test'),
actions: <Widget>[PopupMenuButton<int>(
child: SizedBox(
key: buttonKey,
height: height,
width: width,
child: const ColoredBox(
color: Colors.pink,
),
),
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<int>>[
PopupMenuItem<int>(
value: 1,
child: Builder(
builder: (BuildContext context) {
mediaQueryPadding = MediaQuery.of(context).padding;
return Text('-1-' * 500); // A long long text string.
},
),
),
const PopupMenuItem<int>(value: 2, child: Text('-2-')),
];
},
)],
),
body: const SizedBox.shrink(),
),
);
}
await tester.pumpWidget(buildFrame(20.0, 20.0));
await tester.tap(find.byKey(buttonKey));
await tester.pumpAndSettle();
final Offset button = tester.getTopRight(find.byKey(buttonKey));
expect(button, Offset(800.0 - padding.right, padding.top)); // The topPadding is 32.0.
final Offset popupMenuTopRight = tester.getTopRight(find.byType(SingleChildScrollView));
// The menu should be positioned directly next to the top of the button.
// The 8.0 pixels is [_kMenuScreenPadding].
expect(popupMenuTopRight, Offset(800.0 - padding.right - 8.0, padding.top + 8.0));
final Offset popupMenuTopLeft = tester.getTopLeft(find.byType(SingleChildScrollView));
expect(popupMenuTopLeft, Offset(padding.left + 8.0, padding.top + 8.0));
final Offset popupMenuBottomLeft = tester.getBottomLeft(find.byType(SingleChildScrollView));
expect(popupMenuBottomLeft, Offset(padding.left + 8.0, 600.0 - padding.bottom - 8.0));
// The `MediaQueryData.padding` should be removed.
expect(mediaQueryPadding, EdgeInsets.zero);
});
group('feedback', () {
late FeedbackTester feedback;
......
......@@ -151,7 +151,8 @@ class _DefaultPub implements Pub {
_processUtils = ProcessUtils(
logger: logger,
processManager: processManager,
);
),
_processManager = processManager;
final FileSystem _fileSystem;
final Logger _logger;
......@@ -159,6 +160,7 @@ class _DefaultPub implements Pub {
final Platform _platform;
final BotDetector _botDetector;
final Usage _usage;
final ProcessManager _processManager;
@override
Future<void> get({
......@@ -393,11 +395,15 @@ class _DefaultPub implements Pub {
'cache',
'dart-sdk',
'bin',
if (_platform.isWindows)
'pub.bat'
else
'pub'
'pub',
]);
if (!_processManager.canRun(sdkPath)) {
throwToolExit(
'Your Flutter SDK download may be corrupt or missing permissions to run. '
'Try re-downloading the Flutter SDK into a directory that has read/write '
'permissions for the current user.'
);
}
return <String>[sdkPath, ...arguments];
}
......
......@@ -1029,7 +1029,7 @@ class LocalizationsGenerator {
.replaceAll('@(class)', '$className${locale.camelCase()}')
.replaceAll('@(localeName)', locale.toString())
.replaceAll('@(methods)', methods.join('\n\n'))
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '');
.replaceAll('@(requiresIntlImport)', _requiresIntlImport() ? "import 'package:intl/intl.dart' as intl;" : '');
}
String _generateSubclass(
......@@ -1170,12 +1170,12 @@ class LocalizationsGenerator {
.replaceAll('@(messageClassImports)', sortedClassImports.join('\n'))
.replaceAll('@(delegateClass)', delegateClass)
.replaceAll('@(requiresFoundationImport)', _useDeferredLoading ? '' : "import 'package:flutter/foundation.dart';")
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '')
.replaceAll('@(requiresIntlImport)', _requiresIntlImport() ? "import 'package:intl/intl.dart' as intl;" : '')
.replaceAll('@(canBeNullable)', _usesNullableGetter ? '?' : '')
.replaceAll('@(needsNullCheck)', _usesNullableGetter ? '' : '!');
}
bool _containsPluralMessage() => _allMessages.any((Message message) => message.isPlural);
bool _requiresIntlImport() => _allMessages.any((Message message) => message.isPlural || message.placeholdersRequireFormatting);
void writeOutputFiles(Logger logger, { bool isFromYaml = false }) {
// First, generate the string contents of all necessary files.
......
......@@ -26,6 +26,31 @@ void main() {
Cache.flutterRoot = '';
});
testWithoutContext('Throws a tool exit if pub cannot be run', () async {
final FakeProcessManager processManager = FakeProcessManager.any();
final BufferLogger logger = BufferLogger.test();
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
processManager.excludedExecutables.add('bin/cache/dart-sdk/bin/pub');
fileSystem.file('pubspec.yaml').createSync();
final Pub pub = Pub(
fileSystem: fileSystem,
logger: logger,
processManager: processManager,
usage: TestUsage(),
platform: FakePlatform(
environment: const <String, String>{},
),
botDetector: const BotDetectorAlwaysNo(),
);
await expectLater(() => pub.get(
context: PubContext.pubGet,
checkUpToDate: true,
), throwsToolExit(message: 'Your Flutter SDK download may be corrupt or missing permissions to run'));
});
testWithoutContext('checkUpToDate skips pub get if the package config is newer than the pubspec '
'and the current framework version is the same as the last version', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
......@@ -716,6 +741,9 @@ class MockProcessManager implements ProcessManager {
));
}
@override
bool canRun(dynamic executable, {String workingDirectory}) => true;
@override
dynamic noSuchMethod(Invocation invocation) => null;
}
......@@ -1568,6 +1568,42 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
});
group('DateTime tests', () {
testUsingContext('imports package:intl', () {
const String singleDateMessageArbFileString = '''
{
"@@locale": "en",
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"description": "The first day of spring",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "yMd"
}
}
}
}''';
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(singleDateMessageArbFileString);
LocalizationsGenerator(
fs,
)
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString)
..loadResources()
..writeOutputFiles(BufferLogger.test());
final String localizationsFile = fs.file(
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'),
).readAsStringSync();
expect(localizationsFile, contains(intlImportDartCode));
});
testUsingContext('throws an exception when improperly formatted date is passed in', () {
const String singleDateMessageArbFileString = '''
{
......@@ -1644,6 +1680,45 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
fail('Improper date formatting should throw an exception');
});
});
group('NumberFormat tests', () {
testUsingContext('imports package:intl', () {
const String singleDateMessageArbFileString = '''
{
"courseCompletion": "You have completed {progress} of the course.",
"@courseCompletion": {
"description": "The amount of progress the student has made in their class.",
"placeholders": {
"progress": {
"type": "double",
"format": "percentPattern"
}
}
}
}''';
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(
singleDateMessageArbFileString);
LocalizationsGenerator(
fs,
)
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString)
..loadResources()
..writeOutputFiles(BufferLogger.test());
final String localizationsFile = fs.file(
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'),
).readAsStringSync();
expect(localizationsFile, contains(intlImportDartCode));
});
testUsingContext('throws an exception when improperly formatted number is passed in', () {
const String singleDateMessageArbFileString = '''
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册