Skip to content

Commit d79295a

Browse files
christopherfujinoJonah Williamsxu-baolingoderbauer
authored
[flutter_releases] Flutter Stable 2.2.2 Framework Cherrypicks (flutter#84364)
* [flutter_tools] throw a tool exit if pub cannot be run (flutter#83293) * Re-add the removed MediaQuery.removePadding of PopupMenuButton (flutter#82986) * import pkg:intl when DateFormat or NumberFormat is used (flutter#83122) Co-authored-by: Jonah Williams <[email protected]> Co-authored-by: xubaolin <[email protected]> Co-authored-by: Michael Goderbauer <[email protected]>
1 parent 02c026b commit d79295a

File tree

7 files changed

+227
-43
lines changed

7 files changed

+227
-43
lines changed

bin/internal/engine.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0fdb562ac8068ce3dda6b69aca3f355f4d1d2718
1+
91c9fc8fe011352879e3bb6660966eafc0847233

packages/flutter/lib/src/material/popup_menu.dart

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -618,8 +618,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
618618
this.itemSizes,
619619
this.selectedItemIndex,
620620
this.textDirection,
621-
this.topPadding,
622-
this.bottomPadding,
621+
this.padding,
623622
);
624623

625624
// Rectangle of underlying button, relative to the overlay's dimensions.
@@ -636,11 +635,8 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
636635
// Whether to prefer going to the left or to the right.
637636
final TextDirection textDirection;
638637

639-
// Top padding of unsafe area.
640-
final double topPadding;
641-
642-
// Bottom padding of unsafe area.
643-
final double bottomPadding;
638+
// The padding of unsafe area.
639+
EdgeInsets padding;
644640

645641
// We put the child wherever position specifies, so long as it will fit within
646642
// the specified parent size padded (inset) by 8. If necessary, we adjust the
@@ -651,7 +647,8 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
651647
// The menu can be at most the size of the overlay minus 8.0 pixels in each
652648
// direction.
653649
return BoxConstraints.loose(constraints.biggest).deflate(
654-
const EdgeInsets.all(_kMenuScreenPadding) + EdgeInsets.only(top: topPadding, bottom: bottomPadding));
650+
const EdgeInsets.all(_kMenuScreenPadding) + padding,
651+
);
655652
}
656653

657654
@override
@@ -694,14 +691,15 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
694691

695692
// Avoid going outside an area defined as the rectangle 8.0 pixels from the
696693
// edge of the screen in every direction.
697-
if (x < _kMenuScreenPadding)
698-
x = _kMenuScreenPadding;
699-
else if (x + childSize.width > size.width - _kMenuScreenPadding)
700-
x = size.width - childSize.width - _kMenuScreenPadding;
701-
if (y < _kMenuScreenPadding + topPadding)
702-
y = _kMenuScreenPadding + topPadding;
703-
else if (y + childSize.height > size.height - _kMenuScreenPadding - bottomPadding)
704-
y = size.height - bottomPadding - _kMenuScreenPadding - childSize.height ;
694+
if (x < _kMenuScreenPadding + padding.left)
695+
x = _kMenuScreenPadding + padding.left;
696+
else if (x + childSize.width > size.width - _kMenuScreenPadding - padding.right)
697+
x = size.width - childSize.width - _kMenuScreenPadding - padding.right ;
698+
if (y < _kMenuScreenPadding + padding.top)
699+
y = _kMenuScreenPadding + padding.top;
700+
else if (y + childSize.height > size.height - _kMenuScreenPadding - padding.bottom)
701+
y = size.height - padding.bottom - _kMenuScreenPadding - childSize.height ;
702+
705703
return Offset(x, y);
706704
}
707705

@@ -716,8 +714,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
716714
|| selectedItemIndex != oldDelegate.selectedItemIndex
717715
|| textDirection != oldDelegate.textDirection
718716
|| !listEquals(itemSizes, oldDelegate.itemSizes)
719-
|| topPadding != oldDelegate.topPadding
720-
|| bottomPadding != oldDelegate.bottomPadding;
717+
|| padding != oldDelegate.padding;
721718
}
722719
}
723720

@@ -777,22 +774,27 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
777774
}
778775

779776
final Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
780-
781-
return Builder(
782-
builder: (BuildContext context) {
783-
final MediaQueryData mediaQuery = MediaQuery.of(context);
784-
return CustomSingleChildLayout(
785-
delegate: _PopupMenuRouteLayout(
786-
position,
787-
itemSizes,
788-
selectedItemIndex,
789-
Directionality.of(context),
790-
mediaQuery.padding.top,
791-
mediaQuery.padding.bottom,
792-
),
793-
child: capturedThemes.wrap(menu),
794-
);
795-
},
777+
final MediaQueryData mediaQuery = MediaQuery.of(context);
778+
return MediaQuery.removePadding(
779+
context: context,
780+
removeTop: true,
781+
removeBottom: true,
782+
removeLeft: true,
783+
removeRight: true,
784+
child: Builder(
785+
builder: (BuildContext context) {
786+
return CustomSingleChildLayout(
787+
delegate: _PopupMenuRouteLayout(
788+
position,
789+
itemSizes,
790+
selectedItemIndex,
791+
Directionality.of(context),
792+
mediaQuery.padding,
793+
),
794+
child: capturedThemes.wrap(menu),
795+
);
796+
},
797+
),
796798
);
797799
}
798800
}

packages/flutter/test/material/popup_menu_test.dart

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,79 @@ void main() {
20582058
expect(popupMenu, Offset(button.dx - 8.0, button.dy + 8.0));
20592059
});
20602060

2061+
// Regression test for https://github.com/flutter/flutter/issues/82874
2062+
testWidgets('PopupMenu position test when have unsafe area - left/right padding', (WidgetTester tester) async {
2063+
final GlobalKey buttonKey = GlobalKey();
2064+
const EdgeInsets padding = EdgeInsets.only(left: 300.0, top: 32.0, right: 310.0, bottom: 64.0);
2065+
EdgeInsets? mediaQueryPadding;
2066+
2067+
Widget buildFrame(double width, double height) {
2068+
return MaterialApp(
2069+
builder: (BuildContext context, Widget? child) {
2070+
return MediaQuery(
2071+
data: const MediaQueryData(
2072+
padding: padding,
2073+
),
2074+
child: child!,
2075+
);
2076+
},
2077+
home: Scaffold(
2078+
appBar: AppBar(
2079+
title: const Text('PopupMenu Test'),
2080+
actions: <Widget>[PopupMenuButton<int>(
2081+
child: SizedBox(
2082+
key: buttonKey,
2083+
height: height,
2084+
width: width,
2085+
child: const ColoredBox(
2086+
color: Colors.pink,
2087+
),
2088+
),
2089+
itemBuilder: (BuildContext context) {
2090+
return <PopupMenuEntry<int>>[
2091+
PopupMenuItem<int>(
2092+
value: 1,
2093+
child: Builder(
2094+
builder: (BuildContext context) {
2095+
mediaQueryPadding = MediaQuery.of(context).padding;
2096+
return Text('-1-' * 500); // A long long text string.
2097+
},
2098+
),
2099+
),
2100+
const PopupMenuItem<int>(value: 2, child: Text('-2-')),
2101+
];
2102+
},
2103+
)],
2104+
),
2105+
body: const SizedBox.shrink(),
2106+
),
2107+
);
2108+
}
2109+
2110+
await tester.pumpWidget(buildFrame(20.0, 20.0));
2111+
2112+
await tester.tap(find.byKey(buttonKey));
2113+
await tester.pumpAndSettle();
2114+
2115+
final Offset button = tester.getTopRight(find.byKey(buttonKey));
2116+
expect(button, Offset(800.0 - padding.right, padding.top)); // The topPadding is 32.0.
2117+
2118+
final Offset popupMenuTopRight = tester.getTopRight(find.byType(SingleChildScrollView));
2119+
2120+
// The menu should be positioned directly next to the top of the button.
2121+
// The 8.0 pixels is [_kMenuScreenPadding].
2122+
expect(popupMenuTopRight, Offset(800.0 - padding.right - 8.0, padding.top + 8.0));
2123+
2124+
final Offset popupMenuTopLeft = tester.getTopLeft(find.byType(SingleChildScrollView));
2125+
expect(popupMenuTopLeft, Offset(padding.left + 8.0, padding.top + 8.0));
2126+
2127+
final Offset popupMenuBottomLeft = tester.getBottomLeft(find.byType(SingleChildScrollView));
2128+
expect(popupMenuBottomLeft, Offset(padding.left + 8.0, 600.0 - padding.bottom - 8.0));
2129+
2130+
// The `MediaQueryData.padding` should be removed.
2131+
expect(mediaQueryPadding, EdgeInsets.zero);
2132+
});
2133+
20612134
group('feedback', () {
20622135
late FeedbackTester feedback;
20632136

packages/flutter_tools/lib/src/dart/pub.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,16 @@ class _DefaultPub implements Pub {
151151
_processUtils = ProcessUtils(
152152
logger: logger,
153153
processManager: processManager,
154-
);
154+
),
155+
_processManager = processManager;
155156

156157
final FileSystem _fileSystem;
157158
final Logger _logger;
158159
final ProcessUtils _processUtils;
159160
final Platform _platform;
160161
final BotDetector _botDetector;
161162
final Usage _usage;
163+
final ProcessManager _processManager;
162164

163165
@override
164166
Future<void> get({
@@ -393,11 +395,15 @@ class _DefaultPub implements Pub {
393395
'cache',
394396
'dart-sdk',
395397
'bin',
396-
if (_platform.isWindows)
397-
'pub.bat'
398-
else
399-
'pub'
398+
'pub',
400399
]);
400+
if (!_processManager.canRun(sdkPath)) {
401+
throwToolExit(
402+
'Your Flutter SDK download may be corrupt or missing permissions to run. '
403+
'Try re-downloading the Flutter SDK into a directory that has read/write '
404+
'permissions for the current user.'
405+
);
406+
}
401407
return <String>[sdkPath, ...arguments];
402408
}
403409

packages/flutter_tools/lib/src/localizations/gen_l10n.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ class LocalizationsGenerator {
10291029
.replaceAll('@(class)', '$className${locale.camelCase()}')
10301030
.replaceAll('@(localeName)', locale.toString())
10311031
.replaceAll('@(methods)', methods.join('\n\n'))
1032-
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '');
1032+
.replaceAll('@(requiresIntlImport)', _requiresIntlImport() ? "import 'package:intl/intl.dart' as intl;" : '');
10331033
}
10341034

10351035
String _generateSubclass(
@@ -1170,12 +1170,12 @@ class LocalizationsGenerator {
11701170
.replaceAll('@(messageClassImports)', sortedClassImports.join('\n'))
11711171
.replaceAll('@(delegateClass)', delegateClass)
11721172
.replaceAll('@(requiresFoundationImport)', _useDeferredLoading ? '' : "import 'package:flutter/foundation.dart';")
1173-
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '')
1173+
.replaceAll('@(requiresIntlImport)', _requiresIntlImport() ? "import 'package:intl/intl.dart' as intl;" : '')
11741174
.replaceAll('@(canBeNullable)', _usesNullableGetter ? '?' : '')
11751175
.replaceAll('@(needsNullCheck)', _usesNullableGetter ? '' : '!');
11761176
}
11771177

1178-
bool _containsPluralMessage() => _allMessages.any((Message message) => message.isPlural);
1178+
bool _requiresIntlImport() => _allMessages.any((Message message) => message.isPlural || message.placeholdersRequireFormatting);
11791179

11801180
void writeOutputFiles(Logger logger, { bool isFromYaml = false }) {
11811181
// First, generate the string contents of all necessary files.

packages/flutter_tools/test/general.shard/dart/pub_get_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@ void main() {
2626
Cache.flutterRoot = '';
2727
});
2828

29+
testWithoutContext('Throws a tool exit if pub cannot be run', () async {
30+
final FakeProcessManager processManager = FakeProcessManager.any();
31+
final BufferLogger logger = BufferLogger.test();
32+
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
33+
processManager.excludedExecutables.add('bin/cache/dart-sdk/bin/pub');
34+
35+
fileSystem.file('pubspec.yaml').createSync();
36+
37+
final Pub pub = Pub(
38+
fileSystem: fileSystem,
39+
logger: logger,
40+
processManager: processManager,
41+
usage: TestUsage(),
42+
platform: FakePlatform(
43+
environment: const <String, String>{},
44+
),
45+
botDetector: const BotDetectorAlwaysNo(),
46+
);
47+
48+
await expectLater(() => pub.get(
49+
context: PubContext.pubGet,
50+
checkUpToDate: true,
51+
), throwsToolExit(message: 'Your Flutter SDK download may be corrupt or missing permissions to run'));
52+
});
53+
2954
testWithoutContext('checkUpToDate skips pub get if the package config is newer than the pubspec '
3055
'and the current framework version is the same as the last version', () async {
3156
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
@@ -716,6 +741,9 @@ class MockProcessManager implements ProcessManager {
716741
));
717742
}
718743

744+
@override
745+
bool canRun(dynamic executable, {String workingDirectory}) => true;
746+
719747
@override
720748
dynamic noSuchMethod(Invocation invocation) => null;
721749
}

packages/flutter_tools/test/general.shard/generate_localizations_test.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,42 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
15681568
});
15691569

15701570
group('DateTime tests', () {
1571+
testUsingContext('imports package:intl', () {
1572+
const String singleDateMessageArbFileString = '''
1573+
{
1574+
"@@locale": "en",
1575+
"springBegins": "Spring begins on {springStartDate}",
1576+
"@springBegins": {
1577+
"description": "The first day of spring",
1578+
"placeholders": {
1579+
"springStartDate": {
1580+
"type": "DateTime",
1581+
"format": "yMd"
1582+
}
1583+
}
1584+
}
1585+
}''';
1586+
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
1587+
..childFile(defaultTemplateArbFileName).writeAsStringSync(singleDateMessageArbFileString);
1588+
1589+
LocalizationsGenerator(
1590+
fs,
1591+
)
1592+
..initialize(
1593+
inputPathString: defaultL10nPathString,
1594+
outputPathString: defaultL10nPathString,
1595+
templateArbFileName: defaultTemplateArbFileName,
1596+
outputFileString: defaultOutputFileString,
1597+
classNameString: defaultClassNameString)
1598+
..loadResources()
1599+
..writeOutputFiles(BufferLogger.test());
1600+
1601+
final String localizationsFile = fs.file(
1602+
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'),
1603+
).readAsStringSync();
1604+
expect(localizationsFile, contains(intlImportDartCode));
1605+
});
1606+
15711607
testUsingContext('throws an exception when improperly formatted date is passed in', () {
15721608
const String singleDateMessageArbFileString = '''
15731609
{
@@ -1644,6 +1680,45 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
16441680

16451681
fail('Improper date formatting should throw an exception');
16461682
});
1683+
});
1684+
1685+
group('NumberFormat tests', () {
1686+
testUsingContext('imports package:intl', () {
1687+
const String singleDateMessageArbFileString = '''
1688+
{
1689+
"courseCompletion": "You have completed {progress} of the course.",
1690+
"@courseCompletion": {
1691+
"description": "The amount of progress the student has made in their class.",
1692+
"placeholders": {
1693+
"progress": {
1694+
"type": "double",
1695+
"format": "percentPattern"
1696+
}
1697+
}
1698+
}
1699+
}''';
1700+
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
1701+
..createSync(recursive: true)
1702+
..childFile(defaultTemplateArbFileName).writeAsStringSync(
1703+
singleDateMessageArbFileString);
1704+
1705+
LocalizationsGenerator(
1706+
fs,
1707+
)
1708+
..initialize(
1709+
inputPathString: defaultL10nPathString,
1710+
outputPathString: defaultL10nPathString,
1711+
templateArbFileName: defaultTemplateArbFileName,
1712+
outputFileString: defaultOutputFileString,
1713+
classNameString: defaultClassNameString)
1714+
..loadResources()
1715+
..writeOutputFiles(BufferLogger.test());
1716+
1717+
final String localizationsFile = fs.file(
1718+
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'),
1719+
).readAsStringSync();
1720+
expect(localizationsFile, contains(intlImportDartCode));
1721+
});
16471722

16481723
testUsingContext('throws an exception when improperly formatted number is passed in', () {
16491724
const String singleDateMessageArbFileString = '''

0 commit comments

Comments
 (0)