Flutter stable framework 1.20.3 cherrypicks (#64984)
* Update engine hash to 1.20.3
* Re-enable image_list test with updated certificate(good for 3650 days). (#64519)
* [Material] Relanding fix to ensure time picker input mode lays out correctly in RTL (#64097)
* allow null in compute for weak mode (#63515)
* [flutter_tools] fix recursive asset variant issue (#61129)
* [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: Alexander Aprelev <aam@google.com>
Co-authored-by: Rami <2364772+rami-a@users.noreply.github.com>
Co-authored-by: Alexandre Ardhuin <alexandre.ardhuin@gmail.com>
Co-authored-by: Jonah Williams <jonahwilliams@google.com>
Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 08f6890..c87a848 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-9d5b21729ff53dbf8eadd8bc97e0e30d77abec95
+d1bc06f032f9d6c148ea6b96b48261d6f545004f
diff --git a/examples/image_list/lib/main.dart b/examples/image_list/lib/main.dart
index f5d0431..83c524c 100644
--- a/examples/image_list/lib/main.dart
+++ b/examples/image_list/lib/main.dart
@@ -16,28 +16,36 @@
///
/// This is used in [$FH/flutter/devicelab/bin/tasks/image_list_reported_duration.dart] test.
///
+///
+/// To generate new certificate:
+///
+/// $ openssl req -new -out image_list.csr
+/// Generating a 2048 bit RSA private key
+/// Enter PEM pass phrase: <random string>
+/// ...
+/// Common Name (eg, fully qualified host name) []:localhost
+///
+/// Copy content of the privateKey below into image_list.key file, then
+/// $ openssl x509 -req -sha256 -days 3650 -in image_list.csr -signkey image_list.key -out image_list.crt
+///
+/// Copy content of the image_list.crt into certificate string below.
String certificate = '''
-----BEGIN CERTIFICATE-----
-MIIDlTCCAn2gAwIBAgIUNfw2s1D9qwYw8bMEZ1rdNVQXi9YwDQYJKoZIhvcNAQEL
-BQAwcjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRIwEAYDVQQHDAlS
-b2NoZXN0ZXIxEjAQBgNVBAoMCWxvY2FsaG9zdDEUMBIGA1UECwwLRGV2ZWxvcG1l
-bnQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xOTA4MjIyMzMwMjdaFw0yMDA4MjEy
-MzMwMjdaMHIxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazESMBAGA1UE
-BwwJUm9jaGVzdGVyMRIwEAYDVQQKDAlsb2NhbGhvc3QxFDASBgNVBAsMC0RldmVs
-b3BtZW50MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
-DwAwggEKAoIBAQCi/fmozdYuCIZbJS7y4zYPp2NRboLXrpUcUzzvzz+24k/TYUPN
-eRvf6wiNXHvr1ijMg1j3wQP72RphxI7cY7XCwzRiM5QeQy3EtRz4ETYBzOev3mHL
-LEgZ9RnSq/u42siSS9CNjoz97liPyQUq8h37/09qhYG0hR/2pRN+YB9g7sNYoGe2
-B7zkh3azRS0/LtgltXwHUId7QzJc15W9Q7adsNVTpOCo7dOj2KWz6sEtFGkYfwLV
-5uiTslRdWCCOUD9iZjCtlPqALkGnWyhNiFJESLbVNC6MURyMngcALW0JTMwc2oDj
-MxtdNkMl0cdzPlhXMDKIKpY9bWbRKUUdsfOnAgMBAAGjIzAhMB8GA1UdEQQYMBaC
-CWxvY2FsaG9zdIIJMTI3LjAuMC4xMA0GCSqGSIb3DQEBCwUAA4IBAQAOYegYQ05v
-S/220FvmwH2X+/r0rcjJ5ZyOGq/MTNMhqXZKHgcNELpOLpDMZVvseXHeUKDrqZFi
-4aemMT5zbuYy3yRkQ/X39GEsksi2/Ii6Xk2zw6IJGL6hvneJEbUP3LN1POy2JLJ/
-Wt7Uda8CWa+H9+0sognT6+/86Q2fWMYnFFnAQk5wW4pYDlgInupoXzjcG0kmPgOO
-ijFsQ67xa0nUn1Llviy3pHX50IU2C+cwlWKNgxfmj6HKG9XIlu8gC/R+Ruf4aSUZ
-QT170jY8Lf6PzqLmbIW86tcKftbkP5RiEp8ESg9jDdNjhwZjxlF13aAVE8uJxP0j
-I12tWIG+68AM
+MIICpDCCAYwCCQD1kfAz8IhbazANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
+b2NhbGhvc3QwHhcNMjAwODI0MjE1MTUwWhcNMzAwODIyMjE1MTUwWjAUMRIwEAYD
+VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi
+/fmozdYuCIZbJS7y4zYPp2NRboLXrpUcUzzvzz+24k/TYUPNeRvf6wiNXHvr1ijM
+g1j3wQP72RphxI7cY7XCwzRiM5QeQy3EtRz4ETYBzOev3mHLLEgZ9RnSq/u42siS
+S9CNjoz97liPyQUq8h37/09qhYG0hR/2pRN+YB9g7sNYoGe2B7zkh3azRS0/Ltgl
+tXwHUId7QzJc15W9Q7adsNVTpOCo7dOj2KWz6sEtFGkYfwLV5uiTslRdWCCOUD9i
+ZjCtlPqALkGnWyhNiFJESLbVNC6MURyMngcALW0JTMwc2oDjMxtdNkMl0cdzPlhX
+MDKIKpY9bWbRKUUdsfOnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHZo/Io7hE9P
+jDhSSP+4iSwx6xjnkRjBHReoea+XwSCv1s9Alwe1Nub6u5jUBhCpGCyU4diKFV1W
+zhunXKY+zRGGtr09nYoN9UVizS5fAFb+h2x3Tw8lsxs4JpPQeWTbGK9Ci+jyfuZu
+xPvdU8I8oxiTRPoWa1KpPm6UVvcrjyftvbqJ4l7cZ8KZN4JNSZlphX8lIM14xR4H
+12sFFTcYWPNDTqO1A9MSflG4OkG59LDHV36JAEqB61pP8hipowVp48+rzD2DVpqb
+r/Mw+0x0HENUTMVExSA5rj/3fxNMggUSl2YsujVJjkb1LiQNPORX7rBndcjknAMt
+TvaTkrwwZA4=
-----END CERTIFICATE-----
''';
diff --git a/packages/flutter/lib/src/foundation/_isolates_io.dart b/packages/flutter/lib/src/foundation/_isolates_io.dart
index e612530..da39776 100644
--- a/packages/flutter/lib/src/foundation/_isolates_io.dart
+++ b/packages/flutter/lib/src/foundation/_isolates_io.dart
@@ -52,7 +52,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 f8797d4..17f248c 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -1416,58 +1416,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(
@@ -1491,6 +1502,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 3919bbc..b483ea6 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -760,6 +760,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 48c4d0b..a7141b1 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/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index 0473f18..7cb9b48 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -18,6 +18,7 @@
import 'devfs.dart';
import 'flutter_manifest.dart';
import 'globals.dart' as globals;
+import 'project.dart';
const AssetBundleFactory _kManifestFactory = _ManifestAssetBundleFactory();
@@ -127,22 +128,18 @@
bool reportLicensedPackages = false,
}) async {
assetDirPath ??= getAssetBuildDirectory();
- FlutterManifest flutterManifest;
+ FlutterProject flutterProject;
try {
- flutterManifest = FlutterManifest.createFromPath(
- manifestPath,
- logger: globals.logger,
- fileSystem: globals.fs,
- );
+ flutterProject = FlutterProject.fromDirectory(globals.fs.file(manifestPath).parent);
} on Exception catch (e) {
globals.printStatus('Error detected in pubspec.yaml:', emphasis: true);
globals.printError('$e');
return 1;
}
- if (flutterManifest == null) {
+ if (flutterProject == null) {
return 1;
}
-
+ final FlutterManifest flutterManifest = flutterProject.manifest;
// If the last build time isn't set before this early return, empty pubspecs will
// hang on hot reload, as the incremental dill files will never be copied to the
// device.
@@ -168,7 +165,18 @@
flutterManifest,
wildcardDirectories,
assetBasePath,
- excludeDirs: <String>[assetDirPath, getBuildDirectory()],
+ excludeDirs: <String>[
+ assetDirPath,
+ getBuildDirectory(),
+ if (flutterProject.ios.existsSync())
+ flutterProject.ios.hostAppRoot.path,
+ if (flutterProject.macos.existsSync())
+ flutterProject.macos.managedDirectory.path,
+ if (flutterProject.windows.existsSync())
+ flutterProject.windows.managedDirectory.path,
+ if (flutterProject.linux.existsSync())
+ flutterProject.linux.managedDirectory.path,
+ ],
);
if (assetVariants == null) {
@@ -594,11 +602,12 @@
// variantsFor('assets/foo') => ['/assets/var1/foo', '/assets/var2/foo']
// variantsFor('assets/bar') => []
class _AssetDirectoryCache {
- _AssetDirectoryCache(Iterable<String> excluded) {
- _excluded = excluded.map<String>((String path) => globals.fs.path.absolute(path) + globals.fs.path.separator);
- }
+ _AssetDirectoryCache(Iterable<String> excluded)
+ : _excluded = excluded
+ .map<String>(globals.fs.path.absolute)
+ .toList();
- Iterable<String> _excluded;
+ final List<String> _excluded;
final Map<String, Map<String, List<String>>> _cache = <String, Map<String, List<String>>>{};
List<String> variantsFor(String assetPath) {
@@ -613,7 +622,9 @@
final List<String> paths = <String>[];
for (final FileSystemEntity entity in globals.fs.directory(directory).listSync(recursive: true)) {
final String path = entity.path;
- if (globals.fs.isFileSync(path) && !_excluded.any((String exclude) => path.startsWith(exclude))) {
+ if (globals.fs.isFileSync(path)
+ && assetPath != path
+ && !_excluded.any((String exclude) => globals.fs.path.isWithin(exclude, path))) {
paths.add(path);
}
}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index dd714b2..43a3748 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -124,7 +124,7 @@
client: globals.httpClientFactory?.call() ?? HttpClient(),
),
DevFSConfig: () => DevFSConfig(),
- DeviceManager: () => DeviceManager(),
+ DeviceManager: () => FlutterDeviceManager(),
Doctor: () => Doctor(logger: globals.logger),
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
EmulatorManager: () => EmulatorManager(
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index db157cd..2b9912a 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -67,46 +67,11 @@
}
/// A class to get all available devices.
-class DeviceManager {
+abstract class DeviceManager {
/// Constructing DeviceManagers is cheap; they only do expensive work if some
/// of their methods are called.
- List<DeviceDiscovery> get deviceDiscoverers => _deviceDiscoverers;
- final List<DeviceDiscovery> _deviceDiscoverers = List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[
- AndroidDevices(
- logger: globals.logger,
- androidSdk: globals.androidSdk,
- androidWorkflow: androidWorkflow,
- processManager: globals.processManager,
- ),
- IOSDevices(
- platform: globals.platform,
- xcdevice: globals.xcdevice,
- iosWorkflow: globals.iosWorkflow,
- logger: globals.logger,
- ),
- IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
- FuchsiaDevices(
- fuchsiaSdk: fuchsiaSdk,
- logger: globals.logger,
- fuchsiaWorkflow: fuchsiaWorkflow,
- platform: globals.platform,
- ),
- FlutterTesterDevices(),
- MacOSDevices(),
- LinuxDevices(
- platform: globals.platform,
- featureFlags: featureFlags,
- ),
- WindowsDevices(),
- WebDevices(
- featureFlags: featureFlags,
- fileSystem: globals.fs,
- platform: globals.platform,
- processManager: globals.processManager,
- logger: globals.logger,
- ),
- ]);
+ List<DeviceDiscovery> get deviceDiscoverers;
String _specifiedDeviceId;
@@ -303,6 +268,45 @@
}
}
+class FlutterDeviceManager extends DeviceManager {
+ @override
+ final List<DeviceDiscovery> deviceDiscoverers = <DeviceDiscovery>[
+ AndroidDevices(
+ logger: globals.logger,
+ androidSdk: globals.androidSdk,
+ androidWorkflow: androidWorkflow,
+ processManager: globals.processManager,
+ ),
+ IOSDevices(
+ platform: globals.platform,
+ xcdevice: globals.xcdevice,
+ iosWorkflow: globals.iosWorkflow,
+ logger: globals.logger,
+ ),
+ IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
+ FuchsiaDevices(
+ fuchsiaSdk: fuchsiaSdk,
+ logger: globals.logger,
+ fuchsiaWorkflow: fuchsiaWorkflow,
+ platform: globals.platform,
+ ),
+ FlutterTesterDevices(),
+ MacOSDevices(),
+ LinuxDevices(
+ platform: globals.platform,
+ featureFlags: featureFlags,
+ ),
+ WindowsDevices(),
+ WebDevices(
+ featureFlags: featureFlags,
+ fileSystem: globals.fs,
+ platform: globals.platform,
+ processManager: globals.processManager,
+ logger: globals.logger,
+ ),
+ ];
+}
+
/// An abstract class to discover and enumerate a specific type of devices.
abstract class DeviceDiscovery {
bool get supportsPlatform;
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
index 0e6dd41..21d09e2 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
@@ -176,6 +176,9 @@
Future<List<String>> getDeviceDiagnostics() => Future<List<String>>.value(
<String>['Cannot connect to device ABC']
);
+
+ @override
+ List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
}
class NoDevicesManager extends DeviceManager {
@@ -185,6 +188,9 @@
@override
Future<List<Device>> refreshAllConnectedDevices({Duration timeout}) =>
getAllConnectedDevices();
+
+@override
+ List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
}
class MockCache extends Mock implements Cache {}
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 9a9bb99..dc5419c 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -1109,7 +1109,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 {
@@ -1126,7 +1126,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 {
@@ -1140,7 +1140,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 {
@@ -1154,7 +1154,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 {
@@ -1168,7 +1168,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 {
@@ -1182,7 +1182,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 fd4b612..232676e 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';
@@ -38,10 +39,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;
}
diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
index 155a6f5..3a9f3ed 100644
--- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
+++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
@@ -437,6 +437,39 @@
ProcessManager: () => FakeProcessManager.any(),
Platform: () => FakePlatform(operatingSystem: 'linux'),
});
+
+ testUsingContext('does not include assets in project directories as asset variants', () async {
+ globals.fs.file('.packages').writeAsStringSync(r'''
+example:lib/
+''');
+ globals.fs.file('pubspec.yaml')
+ ..createSync()
+ ..writeAsStringSync(r'''
+name: example
+
+flutter:
+ assets:
+ - foo.txt
+''');
+ globals.fs.file('assets/foo.txt').createSync(recursive: true);
+
+ // Potential build artifacts outisde of build directory.
+ globals.fs.file('linux/flutter/foo.txt').createSync(recursive: true);
+ globals.fs.file('windows/flutter/foo.txt').createSync(recursive: true);
+ globals.fs.file('windows/CMakeLists.txt').createSync();
+ globals.fs.file('macos/Flutter/foo.txt').createSync(recursive: true);
+ globals.fs.file('ios/foo.txt').createSync(recursive: true);
+ globals.fs.file('build/foo.txt').createSync(recursive: true);
+
+ final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
+
+ expect(await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages'), 0);
+ expect(bundle.entries.length, 4);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem.test(),
+ ProcessManager: () => FakeProcessManager.any(),
+ Platform: () => FakePlatform(operatingSystem: 'linux'),
+ });
}
class MockDirectory extends Mock implements Directory {}