mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Speed up first asset load by using the binary-formatted asset manifest for image resolution (#118782)
* add asset manifest bin loading and asset manifest api * use new api for image resolution * remove upfront smc data casting * fix typecasting issue * remove unused import * fix tests * lints * lints * fix import * fix outdated type name * restore AssetManifest docstrings * update test * update other test * make error message for invalid keys more useful
This commit is contained in:
parent
7175de4fe6
commit
e3db0488ad
@ -14,6 +14,6 @@ void main() {
|
|||||||
|
|
||||||
// If this asset couldn't be loaded, the exception message would be
|
// If this asset couldn't be loaded, the exception message would be
|
||||||
// "asset failed to load"
|
// "asset failed to load"
|
||||||
expect(tester.takeException().toString(), contains('Invalid image data'));
|
expect(tester.takeException().toString(), contains('The key was not found in the asset manifest'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,12 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'image_provider.dart';
|
import 'image_provider.dart';
|
||||||
|
|
||||||
const String _kAssetManifestFileName = 'AssetManifest.json';
|
|
||||||
|
|
||||||
/// A screen with a device-pixel ratio strictly less than this value is
|
/// A screen with a device-pixel ratio strictly less than this value is
|
||||||
/// considered a low-resolution screen (typically entry-level to mid-range
|
/// considered a low-resolution screen (typically entry-level to mid-range
|
||||||
/// laptops, desktop screens up to QHD, low-end tablets such as Kindle Fire).
|
/// laptops, desktop screens up to QHD, low-end tablets such as Kindle Fire).
|
||||||
@ -284,18 +281,18 @@ class AssetImage extends AssetBundleImageProvider {
|
|||||||
Completer<AssetBundleImageKey>? completer;
|
Completer<AssetBundleImageKey>? completer;
|
||||||
Future<AssetBundleImageKey>? result;
|
Future<AssetBundleImageKey>? result;
|
||||||
|
|
||||||
chosenBundle.loadStructuredData<Map<String, List<String>>?>(_kAssetManifestFileName, manifestParser).then<void>(
|
AssetManifest.loadFromAssetBundle(chosenBundle)
|
||||||
(Map<String, List<String>>? manifest) {
|
.then((AssetManifest manifest) {
|
||||||
final String chosenName = _chooseVariant(
|
final Iterable<AssetMetadata> candidateVariants = _getVariants(manifest, keyName);
|
||||||
|
final AssetMetadata chosenVariant = _chooseVariant(
|
||||||
keyName,
|
keyName,
|
||||||
configuration,
|
configuration,
|
||||||
manifest == null ? null : manifest[keyName],
|
candidateVariants,
|
||||||
)!;
|
);
|
||||||
final double chosenScale = _parseScale(chosenName);
|
|
||||||
final AssetBundleImageKey key = AssetBundleImageKey(
|
final AssetBundleImageKey key = AssetBundleImageKey(
|
||||||
bundle: chosenBundle,
|
bundle: chosenBundle,
|
||||||
name: chosenName,
|
name: chosenVariant.key,
|
||||||
scale: chosenScale,
|
scale: chosenVariant.targetDevicePixelRatio ?? _naturalResolution,
|
||||||
);
|
);
|
||||||
if (completer != null) {
|
if (completer != null) {
|
||||||
// We already returned from this function, which means we are in the
|
// We already returned from this function, which means we are in the
|
||||||
@ -309,14 +306,15 @@ class AssetImage extends AssetBundleImageProvider {
|
|||||||
// ourselves.
|
// ourselves.
|
||||||
result = SynchronousFuture<AssetBundleImageKey>(key);
|
result = SynchronousFuture<AssetBundleImageKey>(key);
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
).catchError((Object error, StackTrace stack) {
|
.onError((Object error, StackTrace stack) {
|
||||||
// We had an error. (This guarantees we weren't called synchronously.)
|
// We had an error. (This guarantees we weren't called synchronously.)
|
||||||
// Forward the error to the caller.
|
// Forward the error to the caller.
|
||||||
assert(completer != null);
|
assert(completer != null);
|
||||||
assert(result == null);
|
assert(result == null);
|
||||||
completer!.completeError(error, stack);
|
completer!.completeError(error, stack);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
// The code above ran synchronously, and came up with an answer.
|
// The code above ran synchronously, and came up with an answer.
|
||||||
// Return the SynchronousFuture that we created above.
|
// Return the SynchronousFuture that we created above.
|
||||||
@ -328,35 +326,34 @@ class AssetImage extends AssetBundleImageProvider {
|
|||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the asset manifest string into a strongly-typed map.
|
Iterable<AssetMetadata> _getVariants(AssetManifest manifest, String key) {
|
||||||
@visibleForTesting
|
try {
|
||||||
static Future<Map<String, List<String>>?> manifestParser(String? jsonData) {
|
return manifest.getAssetVariants(key);
|
||||||
if (jsonData == null) {
|
} catch (e) {
|
||||||
return SynchronousFuture<Map<String, List<String>>?>(null);
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary('Unable to load asset with key "$key".'),
|
||||||
|
ErrorDescription(
|
||||||
|
'''
|
||||||
|
The key was not found in the asset manifest.
|
||||||
|
Make sure the key is correct and the appropriate file or folder is specified in pubspec.yaml.
|
||||||
|
'''),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _chooseVariant(String main, ImageConfiguration config, List<String>? candidates) {
|
AssetMetadata _chooseVariant(String mainAssetKey, ImageConfiguration config, Iterable<AssetMetadata> candidateVariants) {
|
||||||
if (config.devicePixelRatio == null || candidates == null || candidates.isEmpty) {
|
if (config.devicePixelRatio == null || candidateVariants.isEmpty) {
|
||||||
return main;
|
return candidateVariants.firstWhere((AssetMetadata variant) => variant.main);
|
||||||
}
|
}
|
||||||
// TODO(ianh): Consider moving this parsing logic into _manifestParser.
|
final SplayTreeMap<double, AssetMetadata> candidatesByDevicePixelRatio =
|
||||||
final SplayTreeMap<double, String> mapping = SplayTreeMap<double, String>();
|
SplayTreeMap<double, AssetMetadata>();
|
||||||
for (final String candidate in candidates) {
|
for (final AssetMetadata candidate in candidateVariants) {
|
||||||
mapping[_parseScale(candidate)] = candidate;
|
candidatesByDevicePixelRatio[candidate.targetDevicePixelRatio ?? _naturalResolution] = candidate;
|
||||||
}
|
}
|
||||||
// TODO(ianh): implement support for config.locale, config.textDirection,
|
// TODO(ianh): implement support for config.locale, config.textDirection,
|
||||||
// config.size, config.platform (then document this over in the Image.asset
|
// config.size, config.platform (then document this over in the Image.asset
|
||||||
// docs)
|
// docs)
|
||||||
return _findBestVariant(mapping, config.devicePixelRatio!);
|
return _findBestVariant(candidatesByDevicePixelRatio, config.devicePixelRatio!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the "best" asset variant amongst the available `candidates`.
|
// Returns the "best" asset variant amongst the available `candidates`.
|
||||||
@ -371,17 +368,17 @@ class AssetImage extends AssetBundleImageProvider {
|
|||||||
// lowest key higher than `value`.
|
// lowest key higher than `value`.
|
||||||
// - If the screen has high device pixel ratio, choose the variant with the
|
// - If the screen has high device pixel ratio, choose the variant with the
|
||||||
// key nearest to `value`.
|
// key nearest to `value`.
|
||||||
String? _findBestVariant(SplayTreeMap<double, String> candidates, double value) {
|
AssetMetadata _findBestVariant(SplayTreeMap<double, AssetMetadata> candidatesByDpr, double value) {
|
||||||
if (candidates.containsKey(value)) {
|
if (candidatesByDpr.containsKey(value)) {
|
||||||
return candidates[value]!;
|
return candidatesByDpr[value]!;
|
||||||
}
|
}
|
||||||
final double? lower = candidates.lastKeyBefore(value);
|
final double? lower = candidatesByDpr.lastKeyBefore(value);
|
||||||
final double? upper = candidates.firstKeyAfter(value);
|
final double? upper = candidatesByDpr.firstKeyAfter(value);
|
||||||
if (lower == null) {
|
if (lower == null) {
|
||||||
return candidates[upper];
|
return candidatesByDpr[upper]!;
|
||||||
}
|
}
|
||||||
if (upper == null) {
|
if (upper == null) {
|
||||||
return candidates[lower];
|
return candidatesByDpr[lower]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On screens with low device-pixel ratios the artifacts from upscaling
|
// On screens with low device-pixel ratios the artifacts from upscaling
|
||||||
@ -389,32 +386,12 @@ class AssetImage extends AssetBundleImageProvider {
|
|||||||
// ratios because the physical pixels are larger. Choose the higher
|
// ratios because the physical pixels are larger. Choose the higher
|
||||||
// resolution image in that case instead of the nearest one.
|
// resolution image in that case instead of the nearest one.
|
||||||
if (value < _kLowDprLimit || value > (lower + upper) / 2) {
|
if (value < _kLowDprLimit || value > (lower + upper) / 2) {
|
||||||
return candidates[upper];
|
return candidatesByDpr[upper]!;
|
||||||
} else {
|
} else {
|
||||||
return candidates[lower];
|
return candidatesByDpr[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
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other.runtimeType != runtimeType) {
|
if (other.runtimeType != runtimeType) {
|
||||||
|
@ -71,7 +71,7 @@ class _AssetManifestBin implements AssetManifest {
|
|||||||
if (!_typeCastedData.containsKey(key)) {
|
if (!_typeCastedData.containsKey(key)) {
|
||||||
final Object? variantData = _data[key];
|
final Object? variantData = _data[key];
|
||||||
if (variantData == null) {
|
if (variantData == null) {
|
||||||
throw ArgumentError('Asset key $key was not found within the asset manifest.');
|
throw ArgumentError('Asset key "$key" was not found.');
|
||||||
}
|
}
|
||||||
_typeCastedData[key] = ((_data[key] ?? <Object?>[]) as Iterable<Object?>)
|
_typeCastedData[key] = ((_data[key] ?? <Object?>[]) as Iterable<Object?>)
|
||||||
.cast<Map<Object?, Object?>>()
|
.cast<Map<Object?, Object?>>()
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -13,18 +12,14 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
class TestAssetBundle extends CachingAssetBundle {
|
class TestAssetBundle extends CachingAssetBundle {
|
||||||
TestAssetBundle(this._assetBundleMap);
|
TestAssetBundle(this._assetBundleMap);
|
||||||
|
|
||||||
final Map<String, List<String>> _assetBundleMap;
|
final Map<String, List<Map<Object?, Object?>>> _assetBundleMap;
|
||||||
|
|
||||||
Map<String, int> loadCallCount = <String, int>{};
|
Map<String, int> loadCallCount = <String, int>{};
|
||||||
|
|
||||||
String get _assetBundleContents {
|
|
||||||
return json.encode(_assetBundleMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ByteData> load(String key) async {
|
Future<ByteData> load(String key) async {
|
||||||
if (key == 'AssetManifest.json') {
|
if (key == 'AssetManifest.bin') {
|
||||||
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert(_assetBundleContents)).buffer);
|
return const StandardMessageCodec().encodeMessage(_assetBundleMap)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
|
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
|
||||||
@ -45,9 +40,10 @@ class TestAssetBundle extends CachingAssetBundle {
|
|||||||
void main() {
|
void main() {
|
||||||
group('1.0 scale device tests', () {
|
group('1.0 scale device tests', () {
|
||||||
void buildAndTestWithOneAsset(String mainAssetPath) {
|
void buildAndTestWithOneAsset(String mainAssetPath) {
|
||||||
final Map<String, List<String>> assetBundleMap = <String, List<String>>{};
|
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||||
|
<String, List<Map<dynamic, dynamic>>>{};
|
||||||
|
|
||||||
assetBundleMap[mainAssetPath] = <String>[];
|
assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[];
|
||||||
|
|
||||||
final AssetImage assetImage = AssetImage(
|
final AssetImage assetImage = AssetImage(
|
||||||
mainAssetPath,
|
mainAssetPath,
|
||||||
@ -93,11 +89,13 @@ void main() {
|
|||||||
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
|
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
|
||||||
const String variantPath = 'assets/normalFolder/3.0x/normalFile.png';
|
const String variantPath = 'assets/normalFolder/3.0x/normalFile.png';
|
||||||
|
|
||||||
final Map<String, List<String>> assetBundleMap =
|
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||||
<String, List<String>>{};
|
<String, List<Map<dynamic, dynamic>>>{};
|
||||||
|
|
||||||
assetBundleMap[mainAssetPath] = <String>[mainAssetPath, variantPath];
|
|
||||||
|
|
||||||
|
final Map<dynamic, dynamic> mainAssetVariantManifestEntry = <dynamic, dynamic>{};
|
||||||
|
mainAssetVariantManifestEntry['asset'] = variantPath;
|
||||||
|
mainAssetVariantManifestEntry['dpr'] = 3.0;
|
||||||
|
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[mainAssetVariantManifestEntry];
|
||||||
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
||||||
|
|
||||||
final AssetImage assetImage = AssetImage(
|
final AssetImage assetImage = AssetImage(
|
||||||
@ -123,10 +121,10 @@ void main() {
|
|||||||
test('When high-res device and high-res asset not present in bundle then return main variant', () {
|
test('When high-res device and high-res asset not present in bundle then return main variant', () {
|
||||||
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
|
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
|
||||||
|
|
||||||
final Map<String, List<String>> assetBundleMap =
|
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||||
<String, List<String>>{};
|
<String, List<Map<dynamic, dynamic>>>{};
|
||||||
|
|
||||||
assetBundleMap[mainAssetPath] = <String>[mainAssetPath];
|
assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[];
|
||||||
|
|
||||||
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
||||||
|
|
||||||
@ -162,10 +160,13 @@ void main() {
|
|||||||
double chosenAssetRatio,
|
double chosenAssetRatio,
|
||||||
String expectedAssetPath,
|
String expectedAssetPath,
|
||||||
) {
|
) {
|
||||||
final Map<String, List<String>> assetBundleMap =
|
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
|
||||||
<String, List<String>>{};
|
<String, List<Map<dynamic, dynamic>>>{};
|
||||||
|
|
||||||
assetBundleMap[mainAssetPath] = <String>[mainAssetPath, variantPath];
|
final Map<dynamic, dynamic> mainAssetVariantManifestEntry = <dynamic, dynamic>{};
|
||||||
|
mainAssetVariantManifestEntry['asset'] = variantPath;
|
||||||
|
mainAssetVariantManifestEntry['dpr'] = 3.0;
|
||||||
|
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[mainAssetVariantManifestEntry];
|
||||||
|
|
||||||
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
@TestOn('!chrome')
|
@TestOn('!chrome')
|
||||||
library;
|
library;
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:ui' as ui show Image;
|
import 'dart:ui' as ui show Image;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -18,27 +19,31 @@ import '../image_data.dart';
|
|||||||
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
|
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
|
||||||
double scaleOf(ByteData data) => data.getFloat64(0);
|
double scaleOf(ByteData data) => data.getFloat64(0);
|
||||||
|
|
||||||
const String testManifest = '''
|
final Map<dynamic, dynamic> testManifest = json.decode('''
|
||||||
{
|
{
|
||||||
"assets/image.png" : [
|
"assets/image.png" : [
|
||||||
"assets/image.png",
|
{"asset": "assets/1.5x/image.png", "dpr": 1.5},
|
||||||
"assets/1.5x/image.png",
|
{"asset": "assets/2.0x/image.png", "dpr": 2.0},
|
||||||
"assets/2.0x/image.png",
|
{"asset": "assets/3.0x/image.png", "dpr": 3.0},
|
||||||
"assets/3.0x/image.png",
|
{"asset": "assets/4.0x/image.png", "dpr": 4.0}
|
||||||
"assets/4.0x/image.png"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
''';
|
''') as Map<Object?, Object?>;
|
||||||
|
|
||||||
class TestAssetBundle extends CachingAssetBundle {
|
class TestAssetBundle extends CachingAssetBundle {
|
||||||
TestAssetBundle({ this.manifest = testManifest });
|
TestAssetBundle({ required Map<dynamic, dynamic> manifest }) {
|
||||||
|
this.manifest = const StandardMessageCodec().encodeMessage(manifest)!;
|
||||||
|
}
|
||||||
|
|
||||||
final String manifest;
|
late final ByteData manifest;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ByteData> load(String key) {
|
Future<ByteData> load(String key) {
|
||||||
late ByteData data;
|
late ByteData data;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
case 'AssetManifest.bin':
|
||||||
|
data = manifest;
|
||||||
|
break;
|
||||||
case 'assets/image.png':
|
case 'assets/image.png':
|
||||||
data = testByteData(1.0);
|
data = testByteData(1.0);
|
||||||
break;
|
break;
|
||||||
@ -61,14 +66,6 @@ class TestAssetBundle extends CachingAssetBundle {
|
|||||||
return SynchronousFuture<ByteData>(data);
|
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
|
@override
|
||||||
String toString() => '${describeIdentity(this)}()';
|
String toString() => '${describeIdentity(this)}()';
|
||||||
}
|
}
|
||||||
@ -107,7 +104,7 @@ Widget buildImageAtRatio(String imageName, Key key, double ratio, bool inferSize
|
|||||||
devicePixelRatio: ratio,
|
devicePixelRatio: ratio,
|
||||||
),
|
),
|
||||||
child: DefaultAssetBundle(
|
child: DefaultAssetBundle(
|
||||||
bundle: bundle ?? TestAssetBundle(),
|
bundle: bundle ?? TestAssetBundle(manifest: testManifest),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: inferSize ?
|
child: inferSize ?
|
||||||
Image(
|
Image(
|
||||||
@ -260,46 +257,21 @@ void main() {
|
|||||||
expect(getRenderImage(tester, key).scale, 4.0);
|
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 {
|
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
|
// If both a main asset and a 1.0x asset are specified, then prefer
|
||||||
// the 1.0x asset.
|
// the 1.0x asset.
|
||||||
|
|
||||||
const String manifest = '''
|
final Map<Object?, Object?> manifest = json.decode('''
|
||||||
{
|
{
|
||||||
"assets/image.png" : [
|
"assets/image.png" : [
|
||||||
"assets/image.png",
|
{"asset": "assets/1.0x/image.png", "dpr": 1.0},
|
||||||
"assets/1.0x/image.png",
|
{"asset": "assets/1.5x/image.png", "dpr": 1.5},
|
||||||
"assets/1.5x/image.png",
|
{"asset": "assets/2.0x/image.png", "dpr": 2.0},
|
||||||
"assets/2.0x/image.png",
|
{"asset": "assets/3.0x/image.png", "dpr": 3.0},
|
||||||
"assets/3.0x/image.png",
|
{"asset": "assets/4.0x/image.png", "dpr": 4.0}
|
||||||
"assets/4.0x/image.png"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
''';
|
''') as Map<Object?, Object?>;
|
||||||
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
|
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
|
||||||
|
|
||||||
const double ratio = 1.0;
|
const double ratio = 1.0;
|
||||||
@ -338,14 +310,14 @@ void main() {
|
|||||||
// if higher resolution assets are not available we will pick the best
|
// if higher resolution assets are not available we will pick the best
|
||||||
// available.
|
// available.
|
||||||
testWidgets('Low-resolution assets', (WidgetTester tester) async {
|
testWidgets('Low-resolution assets', (WidgetTester tester) async {
|
||||||
final AssetBundle bundle = TestAssetBundle(manifest: '''
|
final Map<Object?, Object?> manifest = json.decode('''
|
||||||
{
|
{
|
||||||
"assets/image.png" : [
|
"assets/image.png" : [
|
||||||
"assets/image.png",
|
{"asset": "assets/1.5x/image.png", "dpr": 1.5}
|
||||||
"assets/1.5x/image.png"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
''');
|
''') as Map<Object?, Object?>;
|
||||||
|
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
|
||||||
|
|
||||||
Future<void> testRatio({required double ratio, required double expectedScale}) async {
|
Future<void> testRatio({required double ratio, required double expectedScale}) async {
|
||||||
Key key = GlobalKey();
|
Key key = GlobalKey();
|
||||||
|
@ -2000,10 +2000,9 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
tester.takeException().toString(),
|
tester.takeException().toString(),
|
||||||
equals(
|
equals('Unable to load asset with key "missing-asset".\n'
|
||||||
'Unable to load asset: "missing-asset".\n'
|
'The key was not found in the asset manifest.\n'
|
||||||
'The asset does not exist or has empty data.',
|
'Make sure the key is correct and the appropriate file or folder is specified in pubspec.yaml.'),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await expectLater(
|
await expectLater(
|
||||||
|
Loading…
Reference in New Issue
Block a user