Flutter 1.21.0-9.2.pre cherry picks (#64737)
* update engine hash
* allow null in compute for weak mode (#63515)
* [Material] Relanding fix to ensure time picker input mode lays out correctly in RTL (#64097)
* pin customer-testing
* [flutter_tool] Handle Windows line endings in packages_test.dart (#63806)
* [flutter_tool] Fix some create_test.dart tests on Windows (#63796)
Co-authored-by: Alexandre Ardhuin <alexandre.ardhuin@gmail.com>
Co-authored-by: Rami <2364772+rami-a@users.noreply.github.com>
Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>
diff --git a/.cirrus.yml b/.cirrus.yml
index c3020f3..ba7ca8f 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -277,6 +277,7 @@
script:
- rm -rf bin/cache/pkg/tests
- git clone https://github.com/flutter/tests.git bin/cache/pkg/tests
+ - (cd bin/cache/pkg/tests; git checkout c72bcde)
- dart --enable-asserts dev/customer_testing/run_tests.dart --skip-on-fetch-failure --skip-template bin/cache/pkg/tests/registry/*.test
# firebase_test_lab_tests are linux-only
@@ -458,6 +459,9 @@
script:
- CMD /S /C "IF EXIST "bin\cache\pkg\tests\" RMDIR /S /Q bin\cache\pkg\tests"
- git clone https://github.com/flutter/tests.git bin\cache\pkg\tests
+ - cd bin\cache\pkg\tests
+ - git checkout c72bcde
+ - cd ..\..\..\..
- dart --enable-asserts dev\customer_testing\run_tests.dart --skip-on-fetch-failure --skip-template bin/cache/pkg/tests/registry/*.test
# MACOS SHARDS
@@ -568,6 +572,7 @@
- ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976
- rm -rf bin/cache/pkg/tests
- git clone https://github.com/flutter/tests.git bin/cache/pkg/tests
+ - (cd bin/cache/pkg/tests; git checkout c72bcde)
- dart --enable-asserts dev/customer_testing/run_tests.dart --skip-on-fetch-failure --skip-template bin/cache/pkg/tests/registry/*.test
- name: deploy_gallery-macos # linux- and macos- only
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 52c3615..803fb59 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-267070c17a6956de1a03dbe09cda56f0c485f41b
+20a953183580250aac2e15d36007664118bda5ab
diff --git a/packages/flutter/lib/src/foundation/_isolates_io.dart b/packages/flutter/lib/src/foundation/_isolates_io.dart
index 3760c43..1de7de1 100644
--- a/packages/flutter/lib/src/foundation/_isolates_io.dart
+++ b/packages/flutter/lib/src/foundation/_isolates_io.dart
@@ -50,7 +50,7 @@
}
});
resultPort.listen((dynamic resultData) {
- assert(resultData is R);
+ assert(resultData == null || resultData is R);
if (!result.isCompleted)
result.complete(resultData as R);
});
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index fc72ea3..8e1941a 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -1414,58 +1414,69 @@
),
const SizedBox(width: 12.0),
],
- Expanded(child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: <Widget>[
- const SizedBox(height: 8.0),
- _HourMinuteTextField(
- selectedTime: _selectedTime,
- isHour: true,
- style: hourMinuteStyle,
- validator: _validateHour,
- onSavedSubmitted: _handleHourSavedSubmitted,
- onChanged: _handleHourChanged,
- ),
- const SizedBox(height: 8.0),
- if (!hourHasError && !minuteHasError)
- ExcludeSemantics(
- child: Text(
- MaterialLocalizations.of(context).timePickerHourLabel,
- style: theme.textTheme.caption,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
+ Expanded(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ // Hour/minutes should not change positions in RTL locales.
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: <Widget>[
+ const SizedBox(height: 8.0),
+ _HourTextField(
+ selectedTime: _selectedTime,
+ style: hourMinuteStyle,
+ validator: _validateHour,
+ onSavedSubmitted: _handleHourSavedSubmitted,
+ onChanged: _handleHourChanged,
+ ),
+ const SizedBox(height: 8.0),
+ if (!hourHasError && !minuteHasError)
+ ExcludeSemantics(
+ child: Text(
+ MaterialLocalizations.of(context).timePickerHourLabel,
+ style: theme.textTheme.caption,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ],
),
),
- ],
- )),
- Container(
- margin: const EdgeInsets.only(top: 8.0),
- height: _kTimePickerHeaderControlHeight,
- child: _StringFragment(timeOfDayFormat: timeOfDayFormat),
+ Container(
+ margin: const EdgeInsets.only(top: 8.0),
+ height: _kTimePickerHeaderControlHeight,
+ child: _StringFragment(timeOfDayFormat: timeOfDayFormat),
+ ),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: <Widget>[
+ const SizedBox(height: 8.0),
+ _MinuteTextField(
+ selectedTime: _selectedTime,
+ style: hourMinuteStyle,
+ validator: _validateMinute,
+ onSavedSubmitted: _handleMinuteSavedSubmitted,
+ ),
+ const SizedBox(height: 8.0),
+ if (!hourHasError && !minuteHasError)
+ ExcludeSemantics(
+ child: Text(
+ MaterialLocalizations.of(context).timePickerMinuteLabel,
+ style: theme.textTheme.caption,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
),
- Expanded(child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: <Widget>[
- const SizedBox(height: 8.0),
- _HourMinuteTextField(
- selectedTime: _selectedTime,
- isHour: false,
- style: hourMinuteStyle,
- validator: _validateMinute,
- onSavedSubmitted: _handleMinuteSavedSubmitted,
- ),
- const SizedBox(height: 8.0),
- if (!hourHasError && !minuteHasError)
- ExcludeSemantics(
- child: Text(
- MaterialLocalizations.of(context).timePickerMinuteLabel,
- style: theme.textTheme.caption,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ],
- )),
if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
const SizedBox(width: 12.0),
_DayPeriodControl(
@@ -1489,6 +1500,61 @@
}
}
+class _HourTextField extends StatelessWidget {
+ const _HourTextField({
+ Key key,
+ @required this.selectedTime,
+ @required this.style,
+ @required this.validator,
+ @required this.onSavedSubmitted,
+ @required this.onChanged,
+ }) : super(key: key);
+
+ final TimeOfDay selectedTime;
+ final TextStyle style;
+ final FormFieldValidator<String> validator;
+ final ValueChanged<String> onSavedSubmitted;
+ final ValueChanged<String> onChanged;
+
+ @override
+ Widget build(BuildContext context) {
+ return _HourMinuteTextField(
+ selectedTime: selectedTime,
+ isHour: true,
+ style: style,
+ validator: validator,
+ onSavedSubmitted: onSavedSubmitted,
+ onChanged: onChanged,
+ );
+ }
+}
+
+class _MinuteTextField extends StatelessWidget {
+ const _MinuteTextField({
+ Key key,
+ @required this.selectedTime,
+ @required this.style,
+ @required this.validator,
+ @required this.onSavedSubmitted,
+ }) : super(key: key);
+
+ final TimeOfDay selectedTime;
+ final TextStyle style;
+ final FormFieldValidator<String> validator;
+ final ValueChanged<String> onSavedSubmitted;
+
+ @override
+ Widget build(BuildContext context) {
+ return _HourMinuteTextField(
+ selectedTime: selectedTime,
+ isHour: false,
+ style: style,
+ validator: validator,
+ onSavedSubmitted: onSavedSubmitted,
+ );
+ }
+}
+
class _HourMinuteTextField extends StatefulWidget {
const _HourMinuteTextField({
Key key,
diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart
index 74e0211..945067f 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -806,6 +806,16 @@
await finishPicker(tester);
expect(result, equals(const TimeOfDay(hour: 8, minute: 15)));
});
+
+ // Fixes regression that was reverted in https://github.com/flutter/flutter/pull/64094#pullrequestreview-469836378.
+ testWidgets('Ensure hour/minute fields are top-aligned with the separator', (WidgetTester tester) async {
+ await startPicker(tester, (TimeOfDay time) { }, entryMode: TimePickerEntryMode.input);
+ final double hourFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField')).dy;
+ final double minuteFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField')).dy;
+ final double separatorTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment')).dy;
+ expect(hourFieldTop, separatorTop);
+ expect(minuteFieldTop, separatorTop);
+ });
}
final Finder findDialPaint = find.descendant(
diff --git a/packages/flutter_localizations/test/material/time_picker_test.dart b/packages/flutter_localizations/test/material/time_picker_test.dart
index 870d06c..2d07994 100644
--- a/packages/flutter_localizations/test/material/time_picker_test.dart
+++ b/packages/flutter_localizations/test/material/time_picker_test.dart
@@ -8,10 +8,16 @@
import 'package:flutter_test/flutter_test.dart';
class _TimePickerLauncher extends StatelessWidget {
- const _TimePickerLauncher({ Key key, this.onChanged, this.locale }) : super(key: key);
+ const _TimePickerLauncher({
+ Key key,
+ this.onChanged,
+ this.locale,
+ this.entryMode = TimePickerEntryMode.dial,
+ }) : super(key: key);
final ValueChanged<TimeOfDay> onChanged;
final Locale locale;
+ final TimePickerEntryMode entryMode;
@override
Widget build(BuildContext context) {
@@ -28,6 +34,7 @@
onPressed: () async {
onChanged(await showTimePicker(
context: context,
+ initialEntryMode: entryMode,
initialTime: const TimeOfDay(hour: 7, minute: 0),
));
},
@@ -207,6 +214,73 @@
tester.binding.window.devicePixelRatioTestValue = null;
});
+ testWidgets('can localize input mode in all known formats', (WidgetTester tester) async {
+ final Finder stringFragmentTextFinder = find.descendant(
+ of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
+ matching: find.byType(Text),
+ ).first;
+ final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField');
+ final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField');
+ final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
+
+ // TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
+ final List<Locale> locales = <Locale>[
+ const Locale('en', 'US'), //'h:mm a'
+ const Locale('en', 'GB'), //'HH:mm'
+ const Locale('es', 'ES'), //'H:mm'
+ const Locale('fr', 'CA'), //'HH \'h\' mm'
+ const Locale('zh', 'ZH'), //'ah:mm'
+ const Locale('fa', 'IR'), //'H:mm' but RTL
+ ];
+
+ for (final Locale locale in locales) {
+ await tester.pumpWidget(_TimePickerLauncher(onChanged: (TimeOfDay time) { }, locale: locale, entryMode: TimePickerEntryMode.input));
+ await tester.tap(find.text('X'));
+ await tester.pumpAndSettle(const Duration(seconds: 1));
+
+ final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
+ final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
+ final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
+ final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
+
+ if (locale == const Locale('en', 'US')) {
+ final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
+ expect(stringFragmentText.data, ':');
+ expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+ expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+ expect(minuteLeftOffset, lessThan(dayPeriodLeftOffset));
+ } else if (locale == const Locale('en', 'GB')) {
+ expect(stringFragmentText.data, ':');
+ expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+ expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+ expect(dayPeriodControlFinder, findsNothing);
+ } else if (locale == const Locale('es', 'ES')) {
+ expect(stringFragmentText.data, ':');
+ expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+ expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+ expect(dayPeriodControlFinder, findsNothing);
+ } else if (locale == const Locale('fr', 'CA')) {
+ expect(stringFragmentText.data, 'h');
+ expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+ expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+ expect(dayPeriodControlFinder, findsNothing);
+ } else if (locale == const Locale('zh', 'ZH')) {
+ final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
+ expect(stringFragmentText.data, ':');
+ expect(dayPeriodLeftOffset, lessThan(hourLeftOffset));
+ expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+ expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+ } else if (locale == const Locale('fa', 'IR')) {
+ // Even though this is an RTL locale, the hours and minutes positions should remain the same.
+ expect(stringFragmentText.data, ':');
+ expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+ expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+ expect(dayPeriodControlFinder, findsNothing);
+ }
+ await finishPicker(tester);
+ }
+ });
+
testWidgets('uses single-ring 24-hour dial for all formats', (WidgetTester tester) async {
const List<Locale> locales = <Locale>[
Locale('en', 'US'), // h
diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
index e872457..bd7f4c1 100755
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -1121,7 +1121,7 @@
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
- expect(metadata, contains('project_type: app\n'));
+ expect(LineSplitter.split(metadata), contains('project_type: app'));
});
testUsingContext('can re-gen default template over existing app project with no metadta and detect the type', () async {
@@ -1138,7 +1138,7 @@
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
- expect(metadata, contains('project_type: app\n'));
+ expect(LineSplitter.split(metadata), contains('project_type: app'));
});
testUsingContext('can re-gen app template over existing app project and detect the type', () async {
@@ -1152,7 +1152,7 @@
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
- expect(metadata, contains('project_type: app\n'));
+ expect(LineSplitter.split(metadata), contains('project_type: app'));
});
testUsingContext('can re-gen template over existing module project and detect the type', () async {
@@ -1166,7 +1166,7 @@
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
- expect(metadata, contains('project_type: module\n'));
+ expect(LineSplitter.split(metadata), contains('project_type: module'));
});
testUsingContext('can re-gen default template over existing plugin project and detect the type', () async {
@@ -1180,7 +1180,7 @@
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
- expect(metadata, contains('project_type: plugin'));
+ expect(LineSplitter.split(metadata), contains('project_type: plugin'));
});
testUsingContext('can re-gen default template over existing package project and detect the type', () async {
@@ -1194,7 +1194,7 @@
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
- expect(metadata, contains('project_type: package'));
+ expect(LineSplitter.split(metadata), contains('project_type: package'));
});
testUsingContext('can re-gen module .android/ folder, reusing custom org', () async {
diff --git a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
index b4b08a8..d2623ad 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:convert';
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/base/bot_detector.dart';
@@ -37,10 +38,14 @@
final String projectPath = await createProject(tempDir, arguments: arguments);
final File pubspec = globals.fs.file(globals.fs.path.join(projectPath, 'pubspec.yaml'));
String content = await pubspec.readAsString();
- content = content.replaceFirst(
- '\ndependencies:\n',
- '\ndependencies:\n $plugin:\n',
- );
+ final List<String> contentLines = LineSplitter.split(content).toList();
+ final int depsIndex = contentLines.indexOf('dependencies:');
+ expect(depsIndex, isNot(-1));
+ contentLines.replaceRange(depsIndex, depsIndex + 1, <String>[
+ 'dependencies:',
+ ' $plugin:',
+ ]);
+ content = contentLines.join('\n');
await pubspec.writeAsString(content, flush: true);
return projectPath;
}