mirror of
https://github.com/flutter/flutter
synced 2024-08-28 04:21:14 +00:00
Revert "Speed up first asset load by encoding asset manifest in binary rather than JSON (#113637)" (#116662)
This reverts commit 56cad89b1e
.
This commit is contained in:
parent
352ad3a9ef
commit
7673108d7e
|
@ -3,9 +3,9 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/services.dart' show PlatformAssetBundle, StandardMessageCodec;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart' show PlatformAssetBundle;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../common.dart';
|
||||
|
@ -18,14 +18,16 @@ void main() async {
|
|||
final BenchmarkResultPrinter printer = BenchmarkResultPrinter();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final Stopwatch watch = Stopwatch();
|
||||
final PlatformAssetBundle bundle = PlatformAssetBundle();
|
||||
|
||||
final ByteData assetManifest = await loadAssetManifest();
|
||||
|
||||
final ByteData assetManifestBytes = await bundle.load('money_asset_manifest.json');
|
||||
watch.start();
|
||||
for (int i = 0; i < _kNumIterations; i++) {
|
||||
// This is effectively a test.
|
||||
bundle.clear();
|
||||
final String json = utf8.decode(assetManifestBytes.buffer.asUint8List());
|
||||
// This is a test, so we don't need to worry about this rule.
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
AssetImage.parseAssetManifest(assetManifest);
|
||||
await AssetImage.manifestParser(json);
|
||||
}
|
||||
watch.stop();
|
||||
|
||||
|
@ -38,49 +40,3 @@ void main() async {
|
|||
|
||||
printer.printToStdout();
|
||||
}
|
||||
|
||||
final RegExp _extractRatioRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
|
||||
|
||||
Future<ByteData> loadAssetManifest() async {
|
||||
double parseScale(String key) {
|
||||
final Uri assetUri = Uri.parse(key);
|
||||
String directoryPath = '';
|
||||
if (assetUri.pathSegments.length > 1) {
|
||||
directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
|
||||
}
|
||||
final Match? match = _extractRatioRegExp.firstMatch(directoryPath);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
return double.parse(match.group(1)!);
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> result = <String, dynamic>{};
|
||||
final PlatformAssetBundle bundle = PlatformAssetBundle();
|
||||
|
||||
// For the benchmark, we use the older JSON format and then convert it to the modern binary format.
|
||||
final ByteData jsonAssetManifestBytes = await bundle.load('money_asset_manifest.json');
|
||||
final String jsonAssetManifest = utf8.decode(jsonAssetManifestBytes.buffer.asUint8List());
|
||||
|
||||
final Map<String, dynamic> assetManifest = json.decode(jsonAssetManifest) as Map<String, dynamic>;
|
||||
|
||||
for (final MapEntry<String, dynamic> manifestEntry in assetManifest.entries) {
|
||||
final List<dynamic> resultVariants = <dynamic>[];
|
||||
final List<String> entries = (manifestEntry.value as List<dynamic>).cast<String>();
|
||||
for (final String variant in entries) {
|
||||
if (variant == manifestEntry.key) {
|
||||
// With the newer binary format, don't include the main asset in it's
|
||||
// list of variants. This reduces parsing time at runtime.
|
||||
continue;
|
||||
}
|
||||
final Map<String, dynamic> resultVariant = <String, dynamic>{};
|
||||
final double variantDevicePixelRatio = parseScale(variant);
|
||||
resultVariant['asset'] = variant;
|
||||
resultVariant['dpr'] = variantDevicePixelRatio;
|
||||
resultVariants.add(resultVariant);
|
||||
}
|
||||
result[manifestEntry.key] = resultVariants;
|
||||
}
|
||||
|
||||
return const StandardMessageCodec().encodeMessage(result)!;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gallery/gallery/example_code_parser.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
@ -60,9 +58,4 @@ class TestAssetBundle extends AssetBundle {
|
|||
|
||||
@override
|
||||
String toString() => '$runtimeType@$hashCode()';
|
||||
|
||||
@override
|
||||
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
|
||||
return parser(await load(key));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,7 @@ import 'package:flutter/services.dart';
|
|||
|
||||
import 'image_provider.dart';
|
||||
|
||||
const String _kLegacyAssetManifestFilename = 'AssetManifest.json';
|
||||
const String _kAssetManifestFilename = 'AssetManifest.bin';
|
||||
const String _kAssetManifestFileName = 'AssetManifest.json';
|
||||
|
||||
/// A screen with a device-pixel ratio strictly less than this value is
|
||||
/// considered a low-resolution screen (typically entry-level to mid-range
|
||||
|
@ -285,45 +284,18 @@ class AssetImage extends AssetBundleImageProvider {
|
|||
Completer<AssetBundleImageKey>? completer;
|
||||
Future<AssetBundleImageKey>? result;
|
||||
|
||||
Future<_AssetManifest> loadJsonAssetManifest() {
|
||||
Future<_AssetManifest> parseJson(String data) {
|
||||
final _AssetManifest parsed = _LegacyAssetManifest.fromJsonString(data);
|
||||
return SynchronousFuture<_AssetManifest>(parsed);
|
||||
}
|
||||
return chosenBundle.loadStructuredData(_kLegacyAssetManifestFilename, parseJson);
|
||||
}
|
||||
|
||||
// TODO(andrewkolos): Once google3 and google-fonts-flutter are migrated
|
||||
// away from using AssetManifest.json, remove all references to it.
|
||||
// See https://github.com/flutter/flutter/issues/114913.
|
||||
Future<_AssetManifest>? manifest;
|
||||
|
||||
// Since AssetBundle load calls can be synchronous (e.g. in the case of tests),
|
||||
// it is not sufficient to only use catchError/onError or the onError parameter
|
||||
// of Future.then--we also have to use a synchronous try/catch. Once google3
|
||||
// tooling starts producing AssetManifest.bin, this block can be removed.
|
||||
try {
|
||||
manifest = chosenBundle.loadStructuredBinaryData(_kAssetManifestFilename, _AssetManifestBin.fromStandardMessageCodecMessage);
|
||||
} catch (error) {
|
||||
manifest = loadJsonAssetManifest();
|
||||
}
|
||||
|
||||
manifest
|
||||
// To understand why we use this no-op `then` instead of `catchError`/`onError`,
|
||||
// see https://github.com/flutter/flutter/issues/115601
|
||||
.then((_AssetManifest manifest) => manifest,
|
||||
onError: (Object? error, StackTrace? stack) => loadJsonAssetManifest())
|
||||
.then((_AssetManifest manifest) {
|
||||
final List<_AssetVariant> candidateVariants = manifest.getVariants(keyName);
|
||||
final _AssetVariant chosenVariant = _chooseVariant(
|
||||
chosenBundle.loadStructuredData<Map<String, List<String>>?>(_kAssetManifestFileName, manifestParser).then<void>(
|
||||
(Map<String, List<String>>? manifest) {
|
||||
final String chosenName = _chooseVariant(
|
||||
keyName,
|
||||
configuration,
|
||||
candidateVariants,
|
||||
);
|
||||
manifest == null ? null : manifest[keyName],
|
||||
)!;
|
||||
final double chosenScale = _parseScale(chosenName);
|
||||
final AssetBundleImageKey key = AssetBundleImageKey(
|
||||
bundle: chosenBundle,
|
||||
name: chosenVariant.asset,
|
||||
scale: chosenVariant.devicePixelRatio,
|
||||
name: chosenName,
|
||||
scale: chosenScale,
|
||||
);
|
||||
if (completer != null) {
|
||||
// We already returned from this function, which means we are in the
|
||||
|
@ -337,15 +309,14 @@ class AssetImage extends AssetBundleImageProvider {
|
|||
// ourselves.
|
||||
result = SynchronousFuture<AssetBundleImageKey>(key);
|
||||
}
|
||||
})
|
||||
.onError((Object error, StackTrace stack) {
|
||||
// We had an error. (This guarantees we weren't called synchronously.)
|
||||
// Forward the error to the caller.
|
||||
assert(completer != null);
|
||||
assert(result == null);
|
||||
completer!.completeError(error, stack);
|
||||
});
|
||||
|
||||
},
|
||||
).catchError((Object error, StackTrace stack) {
|
||||
// We had an error. (This guarantees we weren't called synchronously.)
|
||||
// Forward the error to the caller.
|
||||
assert(completer != null);
|
||||
assert(result == null);
|
||||
completer!.completeError(error, stack);
|
||||
});
|
||||
if (result != null) {
|
||||
// The code above ran synchronously, and came up with an answer.
|
||||
// Return the SynchronousFuture that we created above.
|
||||
|
@ -357,29 +328,35 @@ class AssetImage extends AssetBundleImageProvider {
|
|||
return completer.future;
|
||||
}
|
||||
|
||||
/// Parses the asset manifest's file contents into it's Dart representation.
|
||||
/// Parses the asset manifest string into a strongly-typed map.
|
||||
@visibleForTesting
|
||||
// Return type is set to Object?, because the specific type is private.
|
||||
static Object? parseAssetManifest(ByteData bytes) {
|
||||
return _AssetManifestBin.fromStandardMessageCodecMessage(bytes);
|
||||
static Future<Map<String, List<String>>?> manifestParser(String? jsonData) {
|
||||
if (jsonData == null) {
|
||||
return SynchronousFuture<Map<String, List<String>>?>(null);
|
||||
}
|
||||
// TODO(ianh): JSON decoding really shouldn't be on the main thread.
|
||||
final Map<String, dynamic> parsedJson = json.decode(jsonData) as Map<String, dynamic>;
|
||||
final Iterable<String> keys = parsedJson.keys;
|
||||
final Map<String, List<String>> parsedManifest = <String, List<String>> {
|
||||
for (final String key in keys) key: List<String>.from(parsedJson[key] as List<dynamic>),
|
||||
};
|
||||
// TODO(ianh): convert that data structure to the right types.
|
||||
return SynchronousFuture<Map<String, List<String>>?>(parsedManifest);
|
||||
}
|
||||
|
||||
_AssetVariant _chooseVariant(String mainAssetKey, ImageConfiguration config, List<_AssetVariant> candidateVariants) {
|
||||
final _AssetVariant mainAsset = _AssetVariant(asset: mainAssetKey,
|
||||
devicePixelRatio: _naturalResolution);
|
||||
if (config.devicePixelRatio == null || candidateVariants.isEmpty) {
|
||||
return mainAsset;
|
||||
String? _chooseVariant(String main, ImageConfiguration config, List<String>? candidates) {
|
||||
if (config.devicePixelRatio == null || candidates == null || candidates.isEmpty) {
|
||||
return main;
|
||||
}
|
||||
final SplayTreeMap<double, _AssetVariant> candidatesByDevicePixelRatio =
|
||||
SplayTreeMap<double, _AssetVariant>();
|
||||
for (final _AssetVariant candidate in candidateVariants) {
|
||||
candidatesByDevicePixelRatio[candidate.devicePixelRatio] = candidate;
|
||||
// TODO(ianh): Consider moving this parsing logic into _manifestParser.
|
||||
final SplayTreeMap<double, String> mapping = SplayTreeMap<double, String>();
|
||||
for (final String candidate in candidates) {
|
||||
mapping[_parseScale(candidate)] = candidate;
|
||||
}
|
||||
candidatesByDevicePixelRatio.putIfAbsent(_naturalResolution, () => mainAsset);
|
||||
// TODO(ianh): implement support for config.locale, config.textDirection,
|
||||
// config.size, config.platform (then document this over in the Image.asset
|
||||
// docs)
|
||||
return _findBestVariant(candidatesByDevicePixelRatio, config.devicePixelRatio!);
|
||||
return _findBestVariant(mapping, config.devicePixelRatio!);
|
||||
}
|
||||
|
||||
// Returns the "best" asset variant amongst the available `candidates`.
|
||||
|
@ -394,17 +371,17 @@ class AssetImage extends AssetBundleImageProvider {
|
|||
// lowest key higher than `value`.
|
||||
// - If the screen has high device pixel ratio, choose the variant with the
|
||||
// key nearest to `value`.
|
||||
_AssetVariant _findBestVariant(SplayTreeMap<double, _AssetVariant> candidatesByDpr, double value) {
|
||||
if (candidatesByDpr.containsKey(value)) {
|
||||
return candidatesByDpr[value]!;
|
||||
String? _findBestVariant(SplayTreeMap<double, String> candidates, double value) {
|
||||
if (candidates.containsKey(value)) {
|
||||
return candidates[value]!;
|
||||
}
|
||||
final double? lower = candidatesByDpr.lastKeyBefore(value);
|
||||
final double? upper = candidatesByDpr.firstKeyAfter(value);
|
||||
final double? lower = candidates.lastKeyBefore(value);
|
||||
final double? upper = candidates.firstKeyAfter(value);
|
||||
if (lower == null) {
|
||||
return candidatesByDpr[upper]!;
|
||||
return candidates[upper];
|
||||
}
|
||||
if (upper == null) {
|
||||
return candidatesByDpr[lower]!;
|
||||
return candidates[lower];
|
||||
}
|
||||
|
||||
// On screens with low device-pixel ratios the artifacts from upscaling
|
||||
|
@ -412,12 +389,32 @@ class AssetImage extends AssetBundleImageProvider {
|
|||
// ratios because the physical pixels are larger. Choose the higher
|
||||
// resolution image in that case instead of the nearest one.
|
||||
if (value < _kLowDprLimit || value > (lower + upper) / 2) {
|
||||
return candidatesByDpr[upper]!;
|
||||
return candidates[upper];
|
||||
} else {
|
||||
return candidatesByDpr[lower]!;
|
||||
return candidates[lower];
|
||||
}
|
||||
}
|
||||
|
||||
static final RegExp _extractRatioRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
|
||||
|
||||
double _parseScale(String key) {
|
||||
if (key == assetName) {
|
||||
return _naturalResolution;
|
||||
}
|
||||
|
||||
final Uri assetUri = Uri.parse(key);
|
||||
String directoryPath = '';
|
||||
if (assetUri.pathSegments.length > 1) {
|
||||
directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
|
||||
}
|
||||
|
||||
final Match? match = _extractRatioRegExp.firstMatch(directoryPath);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
return double.parse(match.group(1)!);
|
||||
}
|
||||
return _naturalResolution; // i.e. default to 1.0x
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) {
|
||||
|
@ -434,120 +431,3 @@ class AssetImage extends AssetBundleImageProvider {
|
|||
@override
|
||||
String toString() => '${objectRuntimeType(this, 'AssetImage')}(bundle: $bundle, name: "$keyName")';
|
||||
}
|
||||
|
||||
/// Centralizes parsing and typecasting of the contents of the asset manifest file,
|
||||
/// which is generated by the flutter tool at build time.
|
||||
abstract class _AssetManifest {
|
||||
List<_AssetVariant> getVariants(String key);
|
||||
}
|
||||
|
||||
/// Parses the binary asset manifest into a data structure that's easier to work with.
|
||||
///
|
||||
/// The asset manifest is a map of asset files to a list of objects containing
|
||||
/// information about variants of that asset.
|
||||
///
|
||||
/// The entries with each variant object are:
|
||||
/// - "asset": the location of this variant to load it from.
|
||||
/// - "dpr": The device-pixel-ratio that the asset is best-suited for.
|
||||
///
|
||||
/// New fields could be added to this object schema to support new asset variation
|
||||
/// features, such as themes, locale/region support, reading directions, and so on.
|
||||
class _AssetManifestBin implements _AssetManifest {
|
||||
_AssetManifestBin(Map<Object?, Object?> standardMessageData): _data = standardMessageData;
|
||||
|
||||
factory _AssetManifestBin.fromStandardMessageCodecMessage(ByteData message) {
|
||||
final Object? data = const StandardMessageCodec().decodeMessage(message);
|
||||
return _AssetManifestBin(data! as Map<Object?, Object?>);
|
||||
}
|
||||
|
||||
final Map<Object?, Object?> _data;
|
||||
final Map<String, List<_AssetVariant>> _typeCastedData = <String, List<_AssetVariant>>{};
|
||||
|
||||
@override
|
||||
List<_AssetVariant> getVariants(String key) {
|
||||
// We lazily delay typecasting to prevent a performance hiccup when parsing
|
||||
// large asset manifests.
|
||||
if (!_typeCastedData.containsKey(key)) {
|
||||
_typeCastedData[key] = ((_data[key] ?? <Object?>[]) as List<Object?>)
|
||||
.cast<Map<Object?, Object?>>()
|
||||
.map(_AssetVariant.fromManifestData)
|
||||
.toList();
|
||||
}
|
||||
return _typeCastedData[key]!;
|
||||
}
|
||||
}
|
||||
|
||||
class _LegacyAssetManifest implements _AssetManifest {
|
||||
_LegacyAssetManifest({
|
||||
required this.manifest,
|
||||
});
|
||||
|
||||
factory _LegacyAssetManifest.fromJsonString(String jsonString) {
|
||||
List<_AssetVariant> adaptLegacyVariantList(String mainAsset, List<String> variants) {
|
||||
return variants
|
||||
.map((String variant) =>
|
||||
_AssetVariant(asset: variant, devicePixelRatio: _parseScale(mainAsset, variant)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
if (jsonString == null) {
|
||||
return _LegacyAssetManifest(manifest: <String, List<_AssetVariant>>{});
|
||||
}
|
||||
final Map<String, Object?> parsedJson = json.decode(jsonString) as Map<String, dynamic>;
|
||||
final Iterable<String> keys = parsedJson.keys;
|
||||
final Map<String, List<String>> parsedManifest = <String, List<String>> {
|
||||
for (final String key in keys) key: List<String>.from(parsedJson[key]! as List<dynamic>),
|
||||
};
|
||||
final Map<String, List<_AssetVariant>> manifestWithParsedVariants =
|
||||
parsedManifest.map((String asset, List<String> variants) =>
|
||||
MapEntry<String, List<_AssetVariant>>(asset, adaptLegacyVariantList(asset, variants)));
|
||||
|
||||
return _LegacyAssetManifest(manifest: manifestWithParsedVariants);
|
||||
}
|
||||
|
||||
final Map<String, List<_AssetVariant>> manifest;
|
||||
|
||||
static final RegExp _extractRatioRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
|
||||
static const double _naturalResolution = 1.0;
|
||||
|
||||
@override
|
||||
List<_AssetVariant> getVariants(String key) {
|
||||
return manifest[key] ?? const <_AssetVariant>[];
|
||||
}
|
||||
|
||||
static double _parseScale(String mainAsset, String variant) {
|
||||
// The legacy asset manifest includes the main asset within its variant list.
|
||||
if (mainAsset == variant) {
|
||||
return _naturalResolution;
|
||||
}
|
||||
|
||||
final Uri assetUri = Uri.parse(variant);
|
||||
String directoryPath = '';
|
||||
if (assetUri.pathSegments.length > 1) {
|
||||
directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
|
||||
}
|
||||
|
||||
final Match? match = _extractRatioRegExp.firstMatch(directoryPath);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
return double.parse(match.group(1)!);
|
||||
}
|
||||
|
||||
return _naturalResolution; // i.e. default to 1.0x
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetVariant {
|
||||
_AssetVariant({
|
||||
required this.asset,
|
||||
required this.devicePixelRatio,
|
||||
});
|
||||
|
||||
factory _AssetVariant.fromManifestData(Object data) {
|
||||
final Map<Object?, Object?> asStructuredData = data as Map<Object?, Object?>;
|
||||
return _AssetVariant(asset: asStructuredData['asset']! as String,
|
||||
devicePixelRatio: asStructuredData['dpr']! as double);
|
||||
}
|
||||
|
||||
final double devicePixelRatio;
|
||||
final String asset;
|
||||
}
|
||||
|
|
|
@ -96,25 +96,12 @@ abstract class AssetBundle {
|
|||
}
|
||||
|
||||
/// Retrieve a string from the asset bundle, parse it with the given function,
|
||||
/// and return that function's result.
|
||||
/// and return the function's result.
|
||||
///
|
||||
/// Implementations may cache the result, so a particular key should only be
|
||||
/// used with one parser for the lifetime of the asset bundle.
|
||||
Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser);
|
||||
|
||||
/// Retrieve [ByteData] from the asset bundle, parse it with the given function,
|
||||
/// and return that function's result.
|
||||
///
|
||||
/// Implementations may cache the result, so a particular key should only be
|
||||
/// used with one parser for the lifetime of the asset bundle.
|
||||
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
|
||||
final ByteData data = await load(key);
|
||||
if (data == null) {
|
||||
throw FlutterError('Unable to load asset: $key');
|
||||
}
|
||||
return parser(data);
|
||||
}
|
||||
|
||||
/// If this is a caching asset bundle, and the given key describes a cached
|
||||
/// asset, then evict the asset from the cache so that the next time it is
|
||||
/// loaded, the cache will be reread from the asset bundle.
|
||||
|
@ -169,18 +156,6 @@ class NetworkAssetBundle extends AssetBundle {
|
|||
return parser(await loadString(key));
|
||||
}
|
||||
|
||||
/// Retrieve [ByteData] from the asset bundle, parse it with the given function,
|
||||
/// and return the function's result.
|
||||
///
|
||||
/// The result is not cached. The parser is run each time the resource is
|
||||
/// fetched.
|
||||
@override
|
||||
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
|
||||
assert(key != null);
|
||||
assert(parser != null);
|
||||
return parser(await load(key));
|
||||
}
|
||||
|
||||
// TODO(ianh): Once the underlying network logic learns about caching, we
|
||||
// should implement evict().
|
||||
|
||||
|
@ -200,7 +175,6 @@ abstract class CachingAssetBundle extends AssetBundle {
|
|||
// TODO(ianh): Replace this with an intelligent cache, see https://github.com/flutter/flutter/issues/3568
|
||||
final Map<String, Future<String>> _stringCache = <String, Future<String>>{};
|
||||
final Map<String, Future<dynamic>> _structuredDataCache = <String, Future<dynamic>>{};
|
||||
final Map<String, Future<dynamic>> _structuredBinaryDataCache = <String, Future<dynamic>>{};
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, { bool cache = true }) {
|
||||
|
@ -251,69 +225,16 @@ abstract class CachingAssetBundle extends AssetBundle {
|
|||
return completer.future;
|
||||
}
|
||||
|
||||
/// Retrieve bytedata from the asset bundle, parse it with the given function,
|
||||
/// and return the function's result.
|
||||
///
|
||||
/// The result of parsing the bytedata is cached (the bytedata itself is not).
|
||||
/// For any given `key`, the `parser` is only run the first time.
|
||||
///
|
||||
/// Once the value has been parsed, the future returned by this function for
|
||||
/// subsequent calls will be a [SynchronousFuture], which resolves its
|
||||
/// callback synchronously.
|
||||
@override
|
||||
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) {
|
||||
assert(key != null);
|
||||
assert(parser != null);
|
||||
|
||||
if (_structuredBinaryDataCache.containsKey(key)) {
|
||||
return _structuredBinaryDataCache[key]! as Future<T>;
|
||||
}
|
||||
|
||||
// load can return a SynchronousFuture in certain cases, like in the
|
||||
// flutter_test framework. So, we need to support both async and sync flows.
|
||||
Completer<T>? completer; // For async flow.
|
||||
SynchronousFuture<T>? result; // For sync flow.
|
||||
|
||||
load(key)
|
||||
.then<T>(parser)
|
||||
.then<void>((T value) {
|
||||
result = SynchronousFuture<T>(value);
|
||||
if (completer != null) {
|
||||
// The load and parse operation ran asynchronously. We already returned
|
||||
// from the loadStructuredBinaryData function and therefore the caller
|
||||
// was given the future of the completer.
|
||||
completer.complete(value);
|
||||
}
|
||||
}, onError: (Object err, StackTrace? stack) {
|
||||
completer!.completeError(err, stack);
|
||||
});
|
||||
|
||||
if (result != null) {
|
||||
// The above code ran synchronously. We can synchronously return the result.
|
||||
_structuredBinaryDataCache[key] = result!;
|
||||
return result!;
|
||||
}
|
||||
|
||||
// Since the above code is being run asynchronously and thus hasn't run its
|
||||
// `then` handler yet, we'll return a completer that will be completed
|
||||
// when the handler does run.
|
||||
completer = Completer<T>();
|
||||
_structuredBinaryDataCache[key] = completer.future;
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
void evict(String key) {
|
||||
_stringCache.remove(key);
|
||||
_structuredDataCache.remove(key);
|
||||
_structuredBinaryDataCache.remove(key);
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_stringCache.clear();
|
||||
_structuredDataCache.clear();
|
||||
_structuredBinaryDataCache.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -355,7 +276,7 @@ class PlatformAssetBundle extends CachingAssetBundle {
|
|||
bool debugUsePlatformChannel = false;
|
||||
assert(() {
|
||||
// dart:io is safe to use here since we early return for web
|
||||
// above. If that code is changed, this needs to be guarded on
|
||||
// above. If that code is changed, this needs to be gaurded on
|
||||
// web presence. Override how assets are loaded in tests so that
|
||||
// the old loader behavior that allows tests to load assets from
|
||||
// the current package using the package prefix.
|
||||
|
|
|
@ -13,14 +13,18 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
class TestAssetBundle extends CachingAssetBundle {
|
||||
TestAssetBundle(this._assetBundleMap);
|
||||
|
||||
final Map<String, List<Map<dynamic, dynamic>>> _assetBundleMap;
|
||||
final Map<String, List<String>> _assetBundleMap;
|
||||
|
||||
Map<String, int> loadCallCount = <String, int>{};
|
||||
|
||||
String get _assetBundleContents {
|
||||
return json.encode(_assetBundleMap);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
if (key == 'AssetManifest.bin') {
|
||||
return const StandardMessageCodec().encodeMessage(_assetBundleMap)!;
|
||||
if (key == 'AssetManifest.json') {
|
||||
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert(_assetBundleContents)).buffer);
|
||||
}
|
||||
|
||||
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
|
||||
|
@ -38,71 +42,12 @@ class TestAssetBundle extends CachingAssetBundle {
|
|||
}
|
||||
}
|
||||
|
||||
class BundleWithoutAssetManifestBin extends CachingAssetBundle {
|
||||
BundleWithoutAssetManifestBin(this._legacyAssetBundleMap);
|
||||
|
||||
final Map<dynamic, List<String>> _legacyAssetBundleMap;
|
||||
|
||||
Map<String, int> loadCallCount = <String, int>{};
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
|
||||
|
||||
if (key == 'AssetManifest.bin') {
|
||||
throw FlutterError('AssetManifest.bin was not found.');
|
||||
}
|
||||
if (key == 'AssetManifest.json') {
|
||||
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert(json.encode(_legacyAssetBundleMap))).buffer);
|
||||
}
|
||||
switch (key) {
|
||||
case 'assets/image.png':
|
||||
return testByteData(1.0); // see "...with a main asset and a 1.0x asset"
|
||||
case 'assets/2.0x/image.png':
|
||||
return testByteData(1.5);
|
||||
}
|
||||
|
||||
throw FlutterError('Unexpected key: $key');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ui.ImmutableBuffer> loadBuffer(String key) async {
|
||||
final ByteData data = await load(key);
|
||||
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
// TODO(andrewkolos): Once google3 is migrated away from using AssetManifest.json,
|
||||
// remove all references to it. See https://github.com/flutter/flutter/issues/114913.
|
||||
test('AssetBundle falls back to using AssetManifest.json if AssetManifest.bin cannot be found.', () async {
|
||||
const String assetPath = 'assets/image.png';
|
||||
final Map<dynamic, List<String>> assetBundleMap = <dynamic, List<String>>{};
|
||||
assetBundleMap[assetPath] = <String>[];
|
||||
final AssetImage assetImage = AssetImage(assetPath, bundle: BundleWithoutAssetManifestBin(assetBundleMap));
|
||||
final AssetBundleImageKey key = await assetImage.obtainKey(ImageConfiguration.empty);
|
||||
expect(key.name, assetPath);
|
||||
expect(key.scale, 1.0);
|
||||
});
|
||||
|
||||
test('When using AssetManifest.json, on a high DPR device, a high dpr variant is selected.', () async {
|
||||
const String assetPath = 'assets/image.png';
|
||||
const String asset2xPath = 'assets/2.0x/image.png';
|
||||
final Map<dynamic, List<String>> assetBundleMap = <dynamic, List<String>>{};
|
||||
assetBundleMap[assetPath] = <String>[asset2xPath];
|
||||
final AssetImage assetImage = AssetImage(assetPath, bundle: BundleWithoutAssetManifestBin(assetBundleMap));
|
||||
final AssetBundleImageKey key = await assetImage.obtainKey(const ImageConfiguration(devicePixelRatio: 2.0));
|
||||
expect(key.name, asset2xPath);
|
||||
expect(key.scale, 2.0);
|
||||
});
|
||||
|
||||
group('1.0 scale device tests', () {
|
||||
void buildAndTestWithOneAsset(String mainAssetPath) {
|
||||
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||
<String, List<Map<dynamic, dynamic>>>{};
|
||||
final Map<String, List<String>> assetBundleMap = <String, List<String>>{};
|
||||
|
||||
assetBundleMap[mainAssetPath] = <Map<dynamic,dynamic>>[];
|
||||
assetBundleMap[mainAssetPath] = <String>[];
|
||||
|
||||
final AssetImage assetImage = AssetImage(
|
||||
mainAssetPath,
|
||||
|
@ -148,13 +93,10 @@ void main() {
|
|||
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
|
||||
const String variantPath = 'assets/normalFolder/3.0x/normalFile.png';
|
||||
|
||||
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||
<String, List<Map<dynamic, dynamic>>>{};
|
||||
final Map<String, List<String>> assetBundleMap =
|
||||
<String, List<String>>{};
|
||||
|
||||
final Map<dynamic, dynamic> mainAssetVariantManifestEntry = <dynamic, dynamic>{};
|
||||
mainAssetVariantManifestEntry['asset'] = variantPath;
|
||||
mainAssetVariantManifestEntry['dpr'] = 3.0;
|
||||
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[mainAssetVariantManifestEntry];
|
||||
assetBundleMap[mainAssetPath] = <String>[mainAssetPath, variantPath];
|
||||
|
||||
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
||||
|
||||
|
@ -181,10 +123,10 @@ void main() {
|
|||
test('When high-res device and high-res asset not present in bundle then return main variant', () {
|
||||
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
|
||||
|
||||
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||
<String, List<Map<dynamic, dynamic>>>{};
|
||||
final Map<String, List<String>> assetBundleMap =
|
||||
<String, List<String>>{};
|
||||
|
||||
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[];
|
||||
assetBundleMap[mainAssetPath] = <String>[mainAssetPath];
|
||||
|
||||
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
||||
|
||||
|
@ -214,18 +156,16 @@ void main() {
|
|||
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
|
||||
const String variantPath = 'assets/normalFolder/3.0x/normalFile.png';
|
||||
|
||||
|
||||
void buildBundleAndTestVariantLogic(
|
||||
double deviceRatio,
|
||||
double chosenAssetRatio,
|
||||
String expectedAssetPath,
|
||||
) {
|
||||
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||
<String, List<Map<dynamic, dynamic>>>{};
|
||||
final Map<String, List<String>> assetBundleMap =
|
||||
<String, List<String>>{};
|
||||
|
||||
final Map<dynamic, dynamic> mainAssetVariantManifestEntry = <dynamic, dynamic>{};
|
||||
mainAssetVariantManifestEntry['asset'] = variantPath;
|
||||
mainAssetVariantManifestEntry['dpr'] = 3.0;
|
||||
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[mainAssetVariantManifestEntry];
|
||||
assetBundleMap[mainAssetPath] = <String>[mainAssetPath, variantPath];
|
||||
|
||||
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
||||
|
||||
|
|
|
@ -9,14 +9,14 @@ import 'package:flutter/painting.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _TestAssetBundle extends CachingAssetBundle {
|
||||
class TestAssetBundle extends CachingAssetBundle {
|
||||
Map<String, int> loadCallCount = <String, int>{};
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
|
||||
if (key == 'AssetManifest.bin') {
|
||||
return const StandardMessageCodec().encodeMessage(json.decode('{"one":[]}'))!;
|
||||
if (key == 'AssetManifest.json') {
|
||||
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert('{"one": ["one"]}')).buffer);
|
||||
}
|
||||
|
||||
if (key == 'one') {
|
||||
|
@ -30,7 +30,7 @@ void main() {
|
|||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('Caching asset bundle test', () async {
|
||||
final _TestAssetBundle bundle = _TestAssetBundle();
|
||||
final TestAssetBundle bundle = TestAssetBundle();
|
||||
|
||||
final ByteData assetData = await bundle.load('one');
|
||||
expect(assetData.getInt8(0), equals(49));
|
||||
|
@ -53,7 +53,7 @@ void main() {
|
|||
|
||||
test('AssetImage.obtainKey succeeds with ImageConfiguration.empty', () async {
|
||||
// This is a regression test for https://github.com/flutter/flutter/issues/12392
|
||||
final AssetImage assetImage = AssetImage('one', bundle: _TestAssetBundle());
|
||||
final AssetImage assetImage = AssetImage('one', bundle: TestAssetBundle());
|
||||
final AssetBundleImageKey key = await assetImage.obtainKey(ImageConfiguration.empty);
|
||||
expect(key.name, 'one');
|
||||
expect(key.scale, 1.0);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
@TestOn('!chrome')
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui show Image;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -17,32 +16,27 @@ import '../image_data.dart';
|
|||
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
|
||||
double scaleOf(ByteData data) => data.getFloat64(0);
|
||||
|
||||
final Map<dynamic, dynamic> testManifest = json.decode('''
|
||||
const String testManifest = '''
|
||||
{
|
||||
"assets/image.png" : [
|
||||
{"asset": "assets/1.5x/image.png", "dpr": 1.5},
|
||||
{"asset": "assets/2.0x/image.png", "dpr": 2.0},
|
||||
{"asset": "assets/3.0x/image.png", "dpr": 3.0},
|
||||
{"asset": "assets/4.0x/image.png", "dpr": 4.0}
|
||||
"assets/image.png",
|
||||
"assets/1.5x/image.png",
|
||||
"assets/2.0x/image.png",
|
||||
"assets/3.0x/image.png",
|
||||
"assets/4.0x/image.png"
|
||||
]
|
||||
}
|
||||
''') as Map<dynamic, dynamic>;
|
||||
''';
|
||||
|
||||
class TestAssetBundle extends CachingAssetBundle {
|
||||
TestAssetBundle({ this.manifest = testManifest });
|
||||
|
||||
TestAssetBundle({ required Map<dynamic, dynamic> manifest }) {
|
||||
this.manifest = const StandardMessageCodec().encodeMessage(manifest)!;
|
||||
}
|
||||
|
||||
late final ByteData manifest;
|
||||
final String manifest;
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) {
|
||||
late ByteData data;
|
||||
switch (key) {
|
||||
case 'AssetManifest.bin':
|
||||
data = manifest;
|
||||
break;
|
||||
case 'assets/image.png':
|
||||
data = testByteData(1.0);
|
||||
break;
|
||||
|
@ -65,6 +59,14 @@ class TestAssetBundle extends CachingAssetBundle {
|
|||
return SynchronousFuture<ByteData>(data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, { bool cache = true }) {
|
||||
if (key == 'AssetManifest.json') {
|
||||
return SynchronousFuture<String>(manifest);
|
||||
}
|
||||
return SynchronousFuture<String>('');
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '${describeIdentity(this)}()';
|
||||
}
|
||||
|
@ -104,7 +106,7 @@ Widget buildImageAtRatio(String imageName, Key key, double ratio, bool inferSize
|
|||
devicePixelRatio: ratio,
|
||||
),
|
||||
child: DefaultAssetBundle(
|
||||
bundle: bundle ?? TestAssetBundle(manifest: testManifest),
|
||||
bundle: bundle ?? TestAssetBundle(),
|
||||
child: Center(
|
||||
child: inferSize ?
|
||||
Image(
|
||||
|
@ -257,21 +259,46 @@ void main() {
|
|||
expect(getRenderImage(tester, key).scale, 4.0);
|
||||
});
|
||||
|
||||
testWidgets('Image for device pixel ratio 1.0, with no main asset', (WidgetTester tester) async {
|
||||
const String manifest = '''
|
||||
{
|
||||
"assets/image.png" : [
|
||||
"assets/1.5x/image.png",
|
||||
"assets/2.0x/image.png",
|
||||
"assets/3.0x/image.png",
|
||||
"assets/4.0x/image.png"
|
||||
]
|
||||
}
|
||||
''';
|
||||
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
|
||||
|
||||
const double ratio = 1.0;
|
||||
Key key = GlobalKey();
|
||||
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images, bundle));
|
||||
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
||||
expect(getRenderImage(tester, key).scale, 1.5);
|
||||
key = GlobalKey();
|
||||
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images, bundle));
|
||||
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
||||
expect(getRenderImage(tester, key).scale, 1.5);
|
||||
});
|
||||
|
||||
testWidgets('Image for device pixel ratio 1.0, with a main asset and a 1.0x asset', (WidgetTester tester) async {
|
||||
// If both a main asset and a 1.0x asset are specified, then prefer
|
||||
// the 1.0x asset.
|
||||
|
||||
final Map<dynamic, dynamic> manifest = json.decode('''
|
||||
const String manifest = '''
|
||||
{
|
||||
"assets/image.png" : [
|
||||
{"asset": "assets/1.0x/image.png", "dpr":1.0},
|
||||
{"asset": "assets/1.5x/image.png", "dpr":1.5},
|
||||
{"asset": "assets/2.0x/image.png", "dpr":2.0},
|
||||
{"asset": "assets/3.0x/image.png", "dpr":3.0},
|
||||
{"asset": "assets/4.0x/image.png", "dpr":4.0}
|
||||
"assets/image.png",
|
||||
"assets/1.0x/image.png",
|
||||
"assets/1.5x/image.png",
|
||||
"assets/2.0x/image.png",
|
||||
"assets/3.0x/image.png",
|
||||
"assets/4.0x/image.png"
|
||||
]
|
||||
}
|
||||
''') as Map<dynamic, dynamic>;
|
||||
''';
|
||||
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
|
||||
|
||||
const double ratio = 1.0;
|
||||
|
@ -310,13 +337,14 @@ void main() {
|
|||
// if higher resolution assets are not available we will pick the best
|
||||
// available.
|
||||
testWidgets('Low-resolution assets', (WidgetTester tester) async {
|
||||
final AssetBundle bundle = TestAssetBundle(manifest: json.decode('''
|
||||
final AssetBundle bundle = TestAssetBundle(manifest: '''
|
||||
{
|
||||
"assets/image.png" : [
|
||||
{"asset": "assets/1.5x/image.png", "dpr": 1.5}
|
||||
"assets/image.png",
|
||||
"assets/1.5x/image.png"
|
||||
]
|
||||
}
|
||||
''') as Map<dynamic, dynamic>);
|
||||
''');
|
||||
|
||||
Future<void> testRatio({required double ratio, required double expectedScale}) async {
|
||||
Key key = GlobalKey();
|
||||
|
|
|
@ -2,11 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:standard_message_codec/standard_message_codec.dart';
|
||||
|
||||
import 'base/context.dart';
|
||||
import 'base/deferred_component.dart';
|
||||
|
@ -139,9 +136,6 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
_splitDeferredAssets = splitDeferredAssets,
|
||||
_licenseCollector = LicenseCollector(fileSystem: fileSystem);
|
||||
|
||||
// We assume the main asset is designed for a device pixel ratio of 1.0
|
||||
static const double _defaultResolution = 1.0;
|
||||
|
||||
final Logger _logger;
|
||||
final FileSystem _fileSystem;
|
||||
final LicenseCollector _licenseCollector;
|
||||
|
@ -167,8 +161,7 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
|
||||
DateTime? _lastBuildTimestamp;
|
||||
|
||||
static const String _kAssetManifestBinFileName = 'AssetManifest.bin';
|
||||
static const String _kAssetManifestJsonFileName = 'AssetManifest.json';
|
||||
static const String _kAssetManifestJson = 'AssetManifest.json';
|
||||
static const String _kNoticeFile = 'NOTICES';
|
||||
// Comically, this can't be name with the more common .gz file extension
|
||||
// because when it's part of an AAR and brought into another APK via gradle,
|
||||
|
@ -236,13 +229,8 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
// device.
|
||||
_lastBuildTimestamp = DateTime.now();
|
||||
if (flutterManifest.isEmpty) {
|
||||
entries[_kAssetManifestJsonFileName] = DevFSStringContent('{}');
|
||||
entryKinds[_kAssetManifestJsonFileName] = AssetKind.regular;
|
||||
final ByteData emptyAssetManifest =
|
||||
const StandardMessageCodec().encodeMessage(<dynamic, dynamic>{})!;
|
||||
entries[_kAssetManifestBinFileName] =
|
||||
DevFSByteContent(emptyAssetManifest.buffer.asUint8List(0, emptyAssetManifest.lengthInBytes));
|
||||
entryKinds[_kAssetManifestBinFileName] = AssetKind.regular;
|
||||
entries[_kAssetManifestJson] = DevFSStringContent('{}');
|
||||
entryKinds[_kAssetManifestJson] = AssetKind.regular;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -439,11 +427,7 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
_wildcardDirectories[uri] ??= _fileSystem.directory(uri);
|
||||
}
|
||||
|
||||
final Map<String, List<String>> assetManifest =
|
||||
_createAssetManifest(assetVariants, deferredComponentsAssetVariants);
|
||||
final DevFSStringContent assetManifestJson = DevFSStringContent(json.encode(assetManifest));
|
||||
final DevFSByteContent assetManifestBinary = _createAssetManifestBinary(assetManifest);
|
||||
|
||||
final DevFSStringContent assetManifest = _createAssetManifest(assetVariants, deferredComponentsAssetVariants);
|
||||
final DevFSStringContent fontManifest = DevFSStringContent(json.encode(fonts));
|
||||
final LicenseResult licenseResult = _licenseCollector.obtainLicenses(packageConfig, additionalLicenseFiles);
|
||||
if (licenseResult.errorMessages.isNotEmpty) {
|
||||
|
@ -467,8 +451,7 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
_fileSystem.file('DOES_NOT_EXIST_RERUN_FOR_WILDCARD$suffix').absolute);
|
||||
}
|
||||
|
||||
_setIfChanged(_kAssetManifestJsonFileName, assetManifestJson, AssetKind.regular);
|
||||
_setIfChanged(_kAssetManifestBinFileName, assetManifestBinary, AssetKind.regular);
|
||||
_setIfChanged(_kAssetManifestJson, assetManifest, AssetKind.regular);
|
||||
_setIfChanged(kFontManifestJson, fontManifest, AssetKind.regular);
|
||||
_setLicenseIfChanged(licenseResult.combinedLicenses, targetPlatform);
|
||||
return 0;
|
||||
|
@ -477,31 +460,17 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
@override
|
||||
List<File> additionalDependencies = <File>[];
|
||||
|
||||
void _setIfChanged(String key, DevFSContent content, AssetKind assetKind) {
|
||||
bool areEqual(List<int> o1, List<int> o2) {
|
||||
if (o1.length != o2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < o1.length; index++) {
|
||||
if (o1[index] != o2[index]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
final DevFSContent? oldContent = entries[key];
|
||||
// In the case that the content is unchanged, we want to avoid an overwrite
|
||||
// as the isModified property may be reset to true,
|
||||
if (oldContent is DevFSByteContent && content is DevFSByteContent &&
|
||||
areEqual(oldContent.bytes, content.bytes)) {
|
||||
void _setIfChanged(String key, DevFSStringContent content, AssetKind assetKind) {
|
||||
if (!entries.containsKey(key)) {
|
||||
entries[key] = content;
|
||||
entryKinds[key] = assetKind;
|
||||
return;
|
||||
}
|
||||
|
||||
entries[key] = content;
|
||||
entryKinds[key] = assetKind;
|
||||
final DevFSStringContent? oldContent = entries[key] as DevFSStringContent?;
|
||||
if (oldContent?.string != content.string) {
|
||||
entries[key] = content;
|
||||
entryKinds[key] = assetKind;
|
||||
}
|
||||
}
|
||||
|
||||
void _setLicenseIfChanged(
|
||||
|
@ -653,14 +622,14 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
return deferredComponentsAssetVariants;
|
||||
}
|
||||
|
||||
Map<String, List<String>> _createAssetManifest(
|
||||
DevFSStringContent _createAssetManifest(
|
||||
Map<_Asset, List<_Asset>> assetVariants,
|
||||
Map<String, Map<_Asset, List<_Asset>>> deferredComponentsAssetVariants
|
||||
) {
|
||||
final Map<String, List<String>> manifest = <String, List<String>>{};
|
||||
final Map<_Asset, List<String>> entries = <_Asset, List<String>>{};
|
||||
final Map<String, List<String>> jsonObject = <String, List<String>>{};
|
||||
final Map<_Asset, List<String>> jsonEntries = <_Asset, List<String>>{};
|
||||
assetVariants.forEach((_Asset main, List<_Asset> variants) {
|
||||
entries[main] = <String>[
|
||||
jsonEntries[main] = <String>[
|
||||
for (final _Asset variant in variants)
|
||||
variant.entryUri.path,
|
||||
];
|
||||
|
@ -668,69 +637,26 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
if (deferredComponentsAssetVariants != null) {
|
||||
for (final Map<_Asset, List<_Asset>> componentAssets in deferredComponentsAssetVariants.values) {
|
||||
componentAssets.forEach((_Asset main, List<_Asset> variants) {
|
||||
entries[main] = <String>[
|
||||
jsonEntries[main] = <String>[
|
||||
for (final _Asset variant in variants)
|
||||
variant.entryUri.path,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
final List<_Asset> sortedKeys = entries.keys.toList()
|
||||
final List<_Asset> sortedKeys = jsonEntries.keys.toList()
|
||||
..sort((_Asset left, _Asset right) => left.entryUri.path.compareTo(right.entryUri.path));
|
||||
for (final _Asset main in sortedKeys) {
|
||||
final String decodedEntryPath = Uri.decodeFull(main.entryUri.path);
|
||||
final List<String> rawEntryVariantsPaths = entries[main]!;
|
||||
final List<String> rawEntryVariantsPaths = jsonEntries[main]!;
|
||||
final List<String> decodedEntryVariantPaths = rawEntryVariantsPaths
|
||||
.map((String value) => Uri.decodeFull(value))
|
||||
.toList();
|
||||
manifest[decodedEntryPath] = decodedEntryVariantPaths;
|
||||
jsonObject[decodedEntryPath] = decodedEntryVariantPaths;
|
||||
}
|
||||
return manifest;
|
||||
return DevFSStringContent(json.encode(jsonObject));
|
||||
}
|
||||
|
||||
DevFSByteContent _createAssetManifestBinary(
|
||||
Map<String, List<String>> assetManifest
|
||||
) {
|
||||
double parseScale(String key) {
|
||||
final Uri assetUri = Uri.parse(key);
|
||||
String directoryPath = '';
|
||||
if (assetUri.pathSegments.length > 1) {
|
||||
directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
|
||||
}
|
||||
|
||||
final Match? match = _extractRatioRegExp.firstMatch(directoryPath);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
return double.parse(match.group(1)!);
|
||||
}
|
||||
return _defaultResolution;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> result = <String, dynamic>{};
|
||||
|
||||
for (final MapEntry<String, dynamic> manifestEntry in assetManifest.entries) {
|
||||
final List<dynamic> resultVariants = <dynamic>[];
|
||||
final List<String> entries = (manifestEntry.value as List<dynamic>).cast<String>();
|
||||
for (final String variant in entries) {
|
||||
if (variant == manifestEntry.key) {
|
||||
// With the newer binary format, don't include the main asset in it's
|
||||
// list of variants. This reduces parsing time at runtime.
|
||||
continue;
|
||||
}
|
||||
final Map<String, dynamic> resultVariant = <String, dynamic>{};
|
||||
final double variantDevicePixelRatio = parseScale(variant);
|
||||
resultVariant['asset'] = variant;
|
||||
resultVariant['dpr'] = variantDevicePixelRatio;
|
||||
resultVariants.add(resultVariant);
|
||||
}
|
||||
result[manifestEntry.key] = resultVariants;
|
||||
}
|
||||
|
||||
final ByteData message = const StandardMessageCodec().encodeMessage(result)!;
|
||||
return DevFSByteContent(message.buffer.asUint8List(0, message.lengthInBytes));
|
||||
}
|
||||
|
||||
static final RegExp _extractRatioRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
|
||||
|
||||
/// Prefixes family names and asset paths of fonts included from packages with
|
||||
/// 'packages/<package_name>'
|
||||
List<Font> _parsePackageFonts(
|
||||
|
|
|
@ -57,8 +57,6 @@ dependencies:
|
|||
|
||||
vm_service: 9.4.0
|
||||
|
||||
standard_message_codec: 0.0.1+3
|
||||
|
||||
_fe_analyzer_shared: 50.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
analyzer: 5.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
@ -90,6 +88,7 @@ dependencies:
|
|||
watcher: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
dev_dependencies:
|
||||
collection: 1.17.0
|
||||
file_testing: 3.0.0
|
||||
pubspec_parse: 1.2.1
|
||||
|
||||
|
@ -98,10 +97,9 @@ dev_dependencies:
|
|||
json_annotation: 4.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_preamble: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
test: 1.22.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
collection: 1.17.0
|
||||
|
||||
dartdoc:
|
||||
# Exclude this package from the hosted API docs.
|
||||
nodoc: true
|
||||
|
||||
# PUBSPEC CHECKSUM: 408d
|
||||
# PUBSPEC CHECKSUM: 65eb
|
||||
|
|
|
@ -220,7 +220,7 @@ loading-units-spelled-wrong:
|
|||
expect(logger.statusText, contains('Errors checking the following files:'));
|
||||
expect(logger.statusText, contains("Invalid loading units yaml file, 'loading-units' entry did not exist."));
|
||||
|
||||
expect(logger.statusText, isNot(contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n')));
|
||||
expect(logger.statusText.contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), false);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: not a list', () async {
|
||||
|
@ -382,7 +382,7 @@ loading-units:
|
|||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText, isNot(contains('Errors checking the following files:')));
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), false);
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping modifies strings file', () async {
|
||||
|
@ -448,10 +448,9 @@ loading-units:
|
|||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
final String manifestOutputString = manifestOutput.readAsStringSync();
|
||||
expect(manifestOutputString, contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'));
|
||||
expect(manifestOutputString, isNot(contains('android:value="invalidmapping"')));
|
||||
expect(manifestOutputString, contains("<!-- Don't delete the meta-data below."));
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('android:value="invalidmapping"'), false);
|
||||
expect(manifestOutput.readAsStringSync().contains("<!-- Don't delete the meta-data below."), true);
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping adds mapping when no existing mapping', () async {
|
||||
|
@ -696,8 +695,8 @@ loading-units:
|
|||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync(), contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'));
|
||||
expect(manifestOutput.readAsStringSync(), isNot(contains(RegExp(r'android:value[\s\n]*=[\s\n]*"invalidmapping"'))));
|
||||
expect(manifestOutput.readAsStringSync(), contains("<!-- Don't delete the meta-data below."));
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains(RegExp(r'android:value[\s\n]*=[\s\n]*"invalidmapping"')), false);
|
||||
expect(manifestOutput.readAsStringSync().contains("<!-- Don't delete the meta-data below."), true);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -111,9 +111,8 @@ $fontsSection
|
|||
|
||||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, containsAll(
|
||||
<String>['AssetManifest.bin', 'AssetManifest.json', 'FontManifest.json', 'NOTICES.Z']
|
||||
));
|
||||
expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
|
||||
expect(bundle.entries.containsKey('FontManifest.json'), isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,6 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
|
@ -14,7 +13,6 @@ import 'package:flutter_tools/src/build_info.dart';
|
|||
import 'package:flutter_tools/src/bundle_builder.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:standard_message_codec/standard_message_codec.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
|
@ -50,16 +48,11 @@ void main() {
|
|||
|
||||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json', 'AssetManifest.bin']));
|
||||
const String expectedJsonAssetManifest = '{}';
|
||||
expect(bundle.entries.length, 1);
|
||||
const String expectedAssetManifest = '{}';
|
||||
expect(
|
||||
utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()),
|
||||
expectedJsonAssetManifest,
|
||||
);
|
||||
const String expectedBinAssetManifest = '{}';
|
||||
expect(
|
||||
await _extractBinAssetManifestFromBundleAsJson(bundle),
|
||||
expectedBinAssetManifest
|
||||
expectedAssetManifest,
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
|
@ -79,8 +72,12 @@ flutter:
|
|||
''');
|
||||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json',
|
||||
'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
expect(bundle.entries.length, 4);
|
||||
expect(bundle.needsBuild(), false);
|
||||
|
||||
// Simulate modifying the files by updating the filestat time manually.
|
||||
|
@ -90,9 +87,13 @@ flutter:
|
|||
|
||||
expect(bundle.needsBuild(), true);
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json',
|
||||
'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt',
|
||||
'assets/foo/fizz.txt']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
// - assets/foo/fizz.txt
|
||||
expect(bundle.entries.length, 5);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
|
@ -111,8 +112,12 @@ flutter:
|
|||
globals.fs.file('.packages').createSync();
|
||||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json',
|
||||
'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
expect(bundle.entries.length, 4);
|
||||
expect(bundle.needsBuild(), false);
|
||||
|
||||
// Delete the wildcard directory and update pubspec file.
|
||||
|
@ -133,8 +138,12 @@ name: example''')
|
|||
// supporting file deletion.
|
||||
expect(bundle.needsBuild(), true);
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json',
|
||||
'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
expect(bundle.entries.length, 4);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
|
@ -157,8 +166,12 @@ flutter:
|
|||
globals.fs.file('.packages').createSync();
|
||||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json',
|
||||
'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
expect(bundle.entries.length, 4);
|
||||
expect(bundle.needsBuild(), false);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
|
@ -190,8 +203,12 @@ flutter:
|
|||
splitDeferredAssets: true,
|
||||
).createBundle();
|
||||
await bundle.build(packagesPath: '.packages', deferredComponentsEnabled: true);
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json',
|
||||
'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
expect(bundle.entries.length, 4);
|
||||
expect(bundle.deferredComponentsEntries.length, 1);
|
||||
expect(bundle.deferredComponentsEntries['component1']!.length, 2);
|
||||
expect(bundle.needsBuild(), false);
|
||||
|
@ -220,9 +237,12 @@ flutter:
|
|||
''');
|
||||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo/bar.txt',
|
||||
'assets/bar/barbie.txt', 'assets/wild/dash.txt', 'AssetManifest.json',
|
||||
'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
expect(bundle.entries.length, 6);
|
||||
expect(bundle.deferredComponentsEntries.isEmpty, true);
|
||||
expect(bundle.needsBuild(), false);
|
||||
}, overrides: <Type, Generator>{
|
||||
|
@ -255,11 +275,14 @@ flutter:
|
|||
splitDeferredAssets: true,
|
||||
).createBundle();
|
||||
await bundle.build(packagesPath: '.packages', deferredComponentsEnabled: true);
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo/bar.txt',
|
||||
'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z']));
|
||||
expect(bundle.deferredComponentsEntries.keys, unorderedEquals(<String>['component1']));
|
||||
expect(bundle.deferredComponentsEntries['component1']!.keys,
|
||||
unorderedEquals(<String>['assets/bar/barbie.txt', 'assets/wild/dash.txt']));
|
||||
// Expected assets:
|
||||
// - asset manifest
|
||||
// - font manifest
|
||||
// - license file
|
||||
// - assets/foo/bar.txt
|
||||
expect(bundle.entries.length, 4);
|
||||
expect(bundle.deferredComponentsEntries.length, 1);
|
||||
expect(bundle.deferredComponentsEntries['component1']!.length, 2);
|
||||
expect(bundle.needsBuild(), false);
|
||||
|
||||
// Simulate modifying the files by updating the filestat time manually.
|
||||
|
@ -270,13 +293,9 @@ flutter:
|
|||
expect(bundle.needsBuild(), true);
|
||||
await bundle.build(packagesPath: '.packages', deferredComponentsEnabled: true);
|
||||
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo/bar.txt',
|
||||
'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z']));
|
||||
expect(bundle.entries.length, 4);
|
||||
expect(bundle.deferredComponentsEntries.length, 1);
|
||||
expect(bundle.deferredComponentsEntries['component1']!.keys,
|
||||
unorderedEquals(<String>['assets/bar/barbie.txt', 'assets/wild/dash.txt',
|
||||
'assets/wild/fizz.txt']));
|
||||
|
||||
expect(bundle.deferredComponentsEntries['component1']!.length, 3);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
|
@ -316,8 +335,7 @@ assets:
|
|||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
|
||||
final DevFSContent? assetManifestBin = bundle.entries['AssetManifest.bin'];
|
||||
final DevFSStringContent? assetManifestJson = bundle.entries['AssetManifest.json']
|
||||
final DevFSStringContent? assetManifest = bundle.entries['AssetManifest.json']
|
||||
as DevFSStringContent?;
|
||||
final DevFSStringContent? fontManifest = bundle.entries['FontManifest.json']
|
||||
as DevFSStringContent?;
|
||||
|
@ -326,8 +344,7 @@ assets:
|
|||
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
|
||||
expect(assetManifestBin, bundle.entries['AssetManifest.bin']);
|
||||
expect(assetManifestJson, bundle.entries['AssetManifest.json']);
|
||||
expect(assetManifest, bundle.entries['AssetManifest.json']);
|
||||
expect(fontManifest, bundle.entries['FontManifest.json']);
|
||||
expect(license, bundle.entries['NOTICES']);
|
||||
}, overrides: <Type, Generator>{
|
||||
|
@ -622,8 +639,7 @@ flutter:
|
|||
|
||||
await bundle.build(packagesPath: '.packages');
|
||||
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['packages/foo/bar/fizz.txt',
|
||||
'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z']));
|
||||
expect(bundle.entries, hasLength(4));
|
||||
expect(bundle.needsBuild(), false);
|
||||
|
||||
// Does not track dependency's wildcard directories.
|
||||
|
@ -723,7 +739,6 @@ flutter:
|
|||
expect(await bundle.build(packagesPath: '.packages'), 0);
|
||||
expect((bundle.entries['FontManifest.json']! as DevFSStringContent).string, '[]');
|
||||
expect((bundle.entries['AssetManifest.json']! as DevFSStringContent).string, '{}');
|
||||
expect(await _extractBinAssetManifestFromBundleAsJson(bundle), '{}');
|
||||
expect(testLogger.errorText, contains(
|
||||
'package:foo has `uses-material-design: true` set'
|
||||
));
|
||||
|
@ -759,8 +774,7 @@ flutter:
|
|||
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
|
||||
|
||||
expect(await bundle.build(packagesPath: '.packages'), 0);
|
||||
expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo.txt',
|
||||
'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z']));
|
||||
expect(bundle.entries.length, 4);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
|
@ -798,17 +812,9 @@ flutter:
|
|||
// The assets from deferred components and regular assets
|
||||
// are both included in alphabetical order
|
||||
expect((bundle.entries['AssetManifest.json']! as DevFSStringContent).string, '{"assets/apple.jpg":["assets/apple.jpg"],"assets/bar.jpg":["assets/bar.jpg"],"assets/foo.jpg":["assets/foo.jpg"],"assets/zebra.jpg":["assets/zebra.jpg"]}');
|
||||
expect(await _extractBinAssetManifestFromBundleAsJson(bundle), '{"assets/apple.jpg":[],"assets/bar.jpg":[],"assets/foo.jpg":[],"assets/zebra.jpg":[]}');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Platform: () => FakePlatform(),
|
||||
});
|
||||
}
|
||||
|
||||
Future<String> _extractBinAssetManifestFromBundleAsJson(AssetBundle bundle) async {
|
||||
final List<int> manifestBytes = await bundle.entries['AssetManifest.bin']!.contentsAsBytes();
|
||||
return json.encode(const StandardMessageCodec().decodeMessage(
|
||||
ByteData.sublistView(Uint8List.fromList(manifestBytes))
|
||||
));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
|
@ -16,418 +15,209 @@ import 'package:flutter_tools/src/base/user_messages.dart';
|
|||
import 'package:flutter_tools/src/cache.dart';
|
||||
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:standard_message_codec/standard_message_codec.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
// TODO(andrewkolos): Delete this group once we stop producing AssetManifest.json
|
||||
// as part of build.
|
||||
group('Legacy asset manifest (AssetManifest.json)', () {
|
||||
Future<Map<String, List<String>>> extractAssetManifestFromBundle(ManifestAssetBundle bundle) async {
|
||||
final String manifestJson = utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes());
|
||||
final Map<String, dynamic> parsedJson = json.decode(manifestJson) as Map<String, dynamic>;
|
||||
final Iterable<String> keys = parsedJson.keys;
|
||||
final Map<String, List<String>> parsedManifest = <String, List<String>> {
|
||||
for (final String key in keys) key: List<String>.from(parsedJson[key] as List<dynamic>),
|
||||
};
|
||||
return parsedManifest;
|
||||
}
|
||||
Future<Map<String, List<String>>> extractAssetManifestFromBundle(ManifestAssetBundle bundle) async {
|
||||
final String manifestJson = utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes());
|
||||
final Map<String, dynamic> parsedJson = json.decode(manifestJson) as Map<String, dynamic>;
|
||||
final Iterable<String> keys = parsedJson.keys;
|
||||
final Map<String, List<String>> parsedManifest = <String, List<String>> {
|
||||
for (final String key in keys) key: List<String>.from(parsedJson[key] as List<dynamic>),
|
||||
};
|
||||
return parsedManifest;
|
||||
}
|
||||
|
||||
group('AssetBundle asset variants (with Unix-style paths)', () {
|
||||
late Platform platform;
|
||||
late FileSystem fs;
|
||||
group('AssetBundle asset variants (with Unix-style paths)', () {
|
||||
late Platform platform;
|
||||
late FileSystem fs;
|
||||
|
||||
setUp(() {
|
||||
platform = FakePlatform();
|
||||
fs = MemoryFileSystem.test();
|
||||
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||
platform: platform,
|
||||
fileSystem: fs,
|
||||
userMessages: UserMessages()
|
||||
);
|
||||
setUp(() {
|
||||
platform = FakePlatform();
|
||||
fs = MemoryFileSystem.test();
|
||||
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||
platform: platform,
|
||||
fileSystem: fs,
|
||||
userMessages: UserMessages()
|
||||
);
|
||||
|
||||
fs.file('.packages').createSync();
|
||||
fs.file('.packages').createSync();
|
||||
|
||||
fs.file('pubspec.yaml').writeAsStringSync(
|
||||
'''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
fs.file('pubspec.yaml').writeAsStringSync(
|
||||
'''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
'''
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('Only images in folders named with device pixel ratios (e.g. 2x, 3.0x) should be considered as variants of other images', () async {
|
||||
const String image = 'assets/image.jpg';
|
||||
const String image2xVariant = 'assets/2x/image.jpg';
|
||||
const String imageNonVariant = 'assets/notAVariant/image.jpg';
|
||||
|
||||
final List<String> assets = <String>[
|
||||
image,
|
||||
image2xVariant,
|
||||
imageNonVariant
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
|
||||
expect(manifest, hasLength(2));
|
||||
expect(manifest[image], equals(<String>[image, image2xVariant]));
|
||||
expect(manifest[imageNonVariant], equals(<String>[imageNonVariant]));
|
||||
});
|
||||
|
||||
testWithoutContext('Asset directories are recursively searched for assets', () async {
|
||||
const String topLevelImage = 'assets/image.jpg';
|
||||
const String secondLevelImage = 'assets/folder/secondLevel.jpg';
|
||||
const String secondLevel2xVariant = 'assets/folder/2x/secondLevel.jpg';
|
||||
|
||||
final List<String> assets = <String>[
|
||||
topLevelImage,
|
||||
secondLevelImage,
|
||||
secondLevel2xVariant
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
expect(manifest, hasLength(2));
|
||||
expect(manifest[topLevelImage], equals(<String>[topLevelImage]));
|
||||
expect(manifest[secondLevelImage], equals(<String>[secondLevelImage, secondLevel2xVariant]));
|
||||
});
|
||||
|
||||
testWithoutContext('Asset paths should never be URI-encoded', () async {
|
||||
const String image = 'assets/normalFolder/i have URI-reserved_characters.jpg';
|
||||
const String imageVariant = 'assets/normalFolder/3x/i have URI-reserved_characters.jpg';
|
||||
|
||||
final List<String> assets = <String>[
|
||||
image,
|
||||
imageVariant
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
expect(manifest, hasLength(1));
|
||||
expect(manifest[image], equals(<String>[image, imageVariant]));
|
||||
});
|
||||
sdk: flutter
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
'''
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('Only images in folders named with device pixel ratios (e.g. 2x, 3.0x) should be considered as variants of other images', () async {
|
||||
const String image = 'assets/image.jpg';
|
||||
const String image2xVariant = 'assets/2x/image.jpg';
|
||||
const String imageNonVariant = 'assets/notAVariant/image.jpg';
|
||||
|
||||
group('AssetBundle asset variants (with Windows-style filepaths)', () {
|
||||
late final Platform platform;
|
||||
late final FileSystem fs;
|
||||
final List<String> assets = <String>[
|
||||
image,
|
||||
image2xVariant,
|
||||
imageNonVariant
|
||||
];
|
||||
|
||||
setUp(() {
|
||||
platform = FakePlatform(operatingSystem: 'windows');
|
||||
fs = MemoryFileSystem.test(style: FileSystemStyle.windows);
|
||||
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||
platform: platform,
|
||||
fileSystem: fs,
|
||||
userMessages: UserMessages()
|
||||
);
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
fs.file('.packages').createSync();
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
fs.file('pubspec.yaml').writeAsStringSync(
|
||||
'''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
'''
|
||||
);
|
||||
});
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
testWithoutContext('Variant detection works with windows-style filepaths', () async {
|
||||
const List<String> assets = <String>[
|
||||
r'assets\foo.jpg',
|
||||
r'assets\2x\foo.jpg',
|
||||
r'assets\somewhereElse\bar.jpg',
|
||||
r'assets\somewhereElse\2x\bar.jpg',
|
||||
];
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
expect(manifest, hasLength(2));
|
||||
expect(manifest[image], equals(<String>[image, image2xVariant]));
|
||||
expect(manifest[imageNonVariant], equals(<String>[imageNonVariant]));
|
||||
});
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
testWithoutContext('Asset directories are recursively searched for assets', () async {
|
||||
const String topLevelImage = 'assets/image.jpg';
|
||||
const String secondLevelImage = 'assets/folder/secondLevel.jpg';
|
||||
const String secondLevel2xVariant = 'assets/folder/2x/secondLevel.jpg';
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
final List<String> assets = <String>[
|
||||
topLevelImage,
|
||||
secondLevelImage,
|
||||
secondLevel2xVariant
|
||||
];
|
||||
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
expect(manifest, hasLength(2));
|
||||
expect(manifest['assets/foo.jpg'], equals(<String>['assets/foo.jpg', 'assets/2x/foo.jpg']));
|
||||
expect(manifest['assets/somewhereElse/bar.jpg'], equals(<String>['assets/somewhereElse/bar.jpg', 'assets/somewhereElse/2x/bar.jpg']));
|
||||
});
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
expect(manifest, hasLength(2));
|
||||
expect(manifest[topLevelImage], equals(<String>[topLevelImage]));
|
||||
expect(manifest[secondLevelImage], equals(<String>[secondLevelImage, secondLevel2xVariant]));
|
||||
});
|
||||
|
||||
testWithoutContext('Asset paths should never be URI-encoded', () async {
|
||||
const String image = 'assets/normalFolder/i have URI-reserved_characters.jpg';
|
||||
const String imageVariant = 'assets/normalFolder/3x/i have URI-reserved_characters.jpg';
|
||||
|
||||
final List<String> assets = <String>[
|
||||
image,
|
||||
imageVariant
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
expect(manifest, hasLength(1));
|
||||
expect(manifest[image], equals(<String>[image, imageVariant]));
|
||||
});
|
||||
});
|
||||
|
||||
group('Current asset manifest (AssetManifest.bin)', () {
|
||||
Future<String> extractAssetManifestFromBundleAsJson(ManifestAssetBundle bundle) async {
|
||||
final List<int> manifestBytes = await bundle.entries['AssetManifest.bin']!.contentsAsBytes();
|
||||
return json.encode(const StandardMessageCodec().decodeMessage(
|
||||
ByteData.sublistView(Uint8List.fromList(manifestBytes))
|
||||
));
|
||||
}
|
||||
|
||||
group('AssetBundle asset variants (with Unix-style paths)', () {
|
||||
late Platform platform;
|
||||
late FileSystem fs;
|
||||
group('AssetBundle asset variants (with Windows-style filepaths)', () {
|
||||
late final Platform platform;
|
||||
late final FileSystem fs;
|
||||
|
||||
setUp(() {
|
||||
platform = FakePlatform();
|
||||
fs = MemoryFileSystem.test();
|
||||
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||
platform: platform,
|
||||
fileSystem: fs,
|
||||
userMessages: UserMessages()
|
||||
);
|
||||
setUp(() {
|
||||
platform = FakePlatform(operatingSystem: 'windows');
|
||||
fs = MemoryFileSystem.test(style: FileSystemStyle.windows);
|
||||
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||
platform: platform,
|
||||
fileSystem: fs,
|
||||
userMessages: UserMessages()
|
||||
);
|
||||
|
||||
fs.file('.packages').createSync();
|
||||
fs.file('.packages').createSync();
|
||||
|
||||
fs.file('pubspec.yaml').writeAsStringSync(
|
||||
'''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
fs.file('pubspec.yaml').writeAsStringSync(
|
||||
'''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
'''
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('Only images in folders named with device pixel ratios (e.g. 2x, 3.0x) should be considered as variants of other images', () async {
|
||||
const String image = 'assets/image.jpg';
|
||||
const String image2xVariant = 'assets/2x/image.jpg';
|
||||
const String imageNonVariant = 'assets/notAVariant/image.jpg';
|
||||
|
||||
final List<String> assets = <String>[
|
||||
image,
|
||||
image2xVariant,
|
||||
imageNonVariant
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
const String expectedManifest = '{"$image":[{"asset":"$image2xVariant","dpr":2.0}],'
|
||||
'"$imageNonVariant":[]}';
|
||||
|
||||
final String manifest = await extractAssetManifestFromBundleAsJson(bundle);
|
||||
expect(manifest, equals(expectedManifest));
|
||||
});
|
||||
|
||||
testWithoutContext('Asset directories are recursively searched for assets', () async {
|
||||
const String topLevelImage = 'assets/image.jpg';
|
||||
const String secondLevelImage = 'assets/folder/secondLevel.jpg';
|
||||
const String secondLevel2xVariant = 'assets/folder/2x/secondLevel.jpg';
|
||||
|
||||
final List<String> assets = <String>[
|
||||
topLevelImage,
|
||||
secondLevelImage,
|
||||
secondLevel2xVariant
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
const String expectedManifest = '{'
|
||||
'"$secondLevelImage":[{"asset":"$secondLevel2xVariant","dpr":2.0}],'
|
||||
'"$topLevelImage":[]'
|
||||
'}';
|
||||
|
||||
final String manifest = await extractAssetManifestFromBundleAsJson(bundle);
|
||||
expect(manifest, equals(expectedManifest));
|
||||
});
|
||||
|
||||
testWithoutContext('Asset paths should never be URI-encoded', () async {
|
||||
const String image = 'assets/normalFolder/i have URI-reserved_characters.jpg';
|
||||
const String imageVariant = 'assets/normalFolder/3x/i have URI-reserved_characters.jpg';
|
||||
|
||||
final List<String> assets = <String>[
|
||||
image,
|
||||
imageVariant
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
const String expectedManifest = '{"$image":[{"asset":"$imageVariant","dpr":3.0}]}';
|
||||
|
||||
final String manifest = await extractAssetManifestFromBundleAsJson(bundle);
|
||||
expect(manifest, equals(expectedManifest));
|
||||
});
|
||||
sdk: flutter
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
'''
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('Variant detection works with windows-style filepaths', () async {
|
||||
const List<String> assets = <String>[
|
||||
r'assets\foo.jpg',
|
||||
r'assets\2x\foo.jpg',
|
||||
r'assets\somewhereElse\bar.jpg',
|
||||
r'assets\somewhereElse\2x\bar.jpg',
|
||||
];
|
||||
|
||||
group('AssetBundle asset variants (with Windows-style filepaths)', () {
|
||||
late final Platform platform;
|
||||
late final FileSystem fs;
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
platform = FakePlatform(operatingSystem: 'windows');
|
||||
fs = MemoryFileSystem.test(style: FileSystemStyle.windows);
|
||||
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||
platform: platform,
|
||||
fileSystem: fs,
|
||||
userMessages: UserMessages()
|
||||
);
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
fs.file('.packages').createSync();
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
fs.file('pubspec.yaml').writeAsStringSync(
|
||||
'''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
'''
|
||||
);
|
||||
});
|
||||
final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle);
|
||||
|
||||
testWithoutContext('Variant detection works with windows-style filepaths', () async {
|
||||
const List<String> assets = <String>[
|
||||
r'assets\foo.jpg',
|
||||
r'assets\2x\foo.jpg',
|
||||
r'assets\somewhereElse\bar.jpg',
|
||||
r'assets\somewhereElse\2x\bar.jpg',
|
||||
];
|
||||
|
||||
for (final String asset in assets) {
|
||||
final File assetFile = fs.file(asset);
|
||||
assetFile.createSync(recursive: true);
|
||||
assetFile.writeAsStringSync(asset);
|
||||
}
|
||||
|
||||
final ManifestAssetBundle bundle = ManifestAssetBundle(
|
||||
logger: BufferLogger.test(),
|
||||
fileSystem: fs,
|
||||
platform: platform,
|
||||
);
|
||||
|
||||
await bundle.build(
|
||||
packagesPath: '.packages',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory),
|
||||
);
|
||||
|
||||
const String expectedManifest = '{"assets/foo.jpg":[{"asset":"assets/2x/foo.jpg","dpr":2.0}],'
|
||||
'"assets/somewhereElse/bar.jpg":[{"asset":"assets/somewhereElse/2x/bar.jpg","dpr":2.0}]}';
|
||||
|
||||
final String manifest = await extractAssetManifestFromBundleAsJson(bundle);
|
||||
expect(manifest, equals(expectedManifest));
|
||||
});
|
||||
expect(manifest, hasLength(2));
|
||||
expect(manifest['assets/foo.jpg'], equals(<String>['assets/foo.jpg', 'assets/2x/foo.jpg']));
|
||||
expect(manifest['assets/somewhereElse/bar.jpg'], equals(<String>['assets/somewhereElse/bar.jpg', 'assets/somewhereElse/2x/bar.jpg']));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue