diff --git a/packages/flutter/lib/src/painting/image_resolution.dart b/packages/flutter/lib/src/painting/image_resolution.dart index 58d3acf71af3d..2644a05f72583 100644 --- a/packages/flutter/lib/src/painting/image_resolution.dart +++ b/packages/flutter/lib/src/painting/image_resolution.dart @@ -327,14 +327,10 @@ class AssetImage extends AssetBundleImageProvider { } AssetMetadata _chooseVariant(String mainAssetKey, ImageConfiguration config, Iterable? candidateVariants) { - if (candidateVariants == null) { + if (candidateVariants == null || candidateVariants.isEmpty || config.devicePixelRatio == null) { return AssetMetadata(key: mainAssetKey, targetDevicePixelRatio: null, main: true); } - if (config.devicePixelRatio == null) { - return candidateVariants.firstWhere((AssetMetadata variant) => variant.main); - } - final SplayTreeMap candidatesByDevicePixelRatio = SplayTreeMap(); for (final AssetMetadata candidate in candidateVariants) { diff --git a/packages/flutter/lib/src/services/asset_manifest.dart b/packages/flutter/lib/src/services/asset_manifest.dart index dd62c7d0b8467..1fb06dba8a6cd 100644 --- a/packages/flutter/lib/src/services/asset_manifest.dart +++ b/packages/flutter/lib/src/services/asset_manifest.dart @@ -33,8 +33,8 @@ abstract class AssetManifest { /// Retrieves metadata about an asset and its variants. Returns null if the /// key was not found in the asset manifest. /// - /// This method considers a main asset to be a variant of itself and - /// includes it in the returned list. + /// This method considers a main asset to be a variant of itself. The returned + /// list will include it if it exists. List? getAssetVariants(String key); } @@ -73,22 +73,21 @@ class _AssetManifestBin implements AssetManifest { } _typeCastedData[key] = ((_data[key] ?? []) as Iterable) .cast>() - .map((Map data) => AssetMetadata( + .map((Map data) { + final String asset = data['asset']! as String; + final Object? dpr = data['dpr']; + return AssetMetadata( key: data['asset']! as String, - targetDevicePixelRatio: data['dpr']! as double, - main: false, - )) + targetDevicePixelRatio: dpr as double?, + main: key == asset, + ); + }) .toList(); _data.remove(key); } - final AssetMetadata mainAsset = AssetMetadata(key: key, - targetDevicePixelRatio: null, - main: true - ); - - return [mainAsset, ..._typeCastedData[key]!]; + return _typeCastedData[key]!; } @override diff --git a/packages/flutter/test/painting/image_resolution_test.dart b/packages/flutter/test/painting/image_resolution_test.dart index fd64c06bd01c0..bfc9c6ede13f6 100644 --- a/packages/flutter/test/painting/image_resolution_test.dart +++ b/packages/flutter/test/painting/image_resolution_test.dart @@ -43,8 +43,6 @@ void main() { final Map>> assetBundleMap = >>{}; - assetBundleMap[mainAssetPath] = >[]; - final AssetImage assetImage = AssetImage( mainAssetPath, bundle: TestAssetBundle(assetBundleMap), @@ -160,15 +158,17 @@ void main() { double chosenAssetRatio, String expectedAssetPath, ) { - final Map>> assetBundleMap = - >>{}; - - final Map mainAssetVariantManifestEntry = {}; - mainAssetVariantManifestEntry['asset'] = variantPath; - mainAssetVariantManifestEntry['dpr'] = 3.0; - assetBundleMap[mainAssetPath] = >[mainAssetVariantManifestEntry]; - - final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap); + const Map>> assetManifest = + >>{ + 'assets/normalFolder/normalFile.png': >[ + {'asset': 'assets/normalFolder/normalFile.png'}, + { + 'asset': 'assets/normalFolder/3.0x/normalFile.png', + 'dpr': 3.0 + }, + ] + }; + final TestAssetBundle testAssetBundle = TestAssetBundle(assetManifest); final AssetImage assetImage = AssetImage( mainAssetPath, diff --git a/packages/flutter/test/services/asset_manifest_test.dart b/packages/flutter/test/services/asset_manifest_test.dart index e715ba6b352cf..11342333ac706 100644 --- a/packages/flutter/test/services/asset_manifest_test.dart +++ b/packages/flutter/test/services/asset_manifest_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -11,12 +13,19 @@ class TestAssetBundle extends AssetBundle { if (key == 'AssetManifest.smcbin') { final Map> binManifestData = >{ 'assets/foo.png': [ + { + 'asset': 'assets/foo.png', + }, { 'asset': 'assets/2x/foo.png', 'dpr': 2.0 - } + }, + ], + 'assets/bar.png': [ + { + 'asset': 'assets/bar.png', + }, ], - 'assets/bar.png': [], }; final ByteData data = const StandardMessageCodec().encodeMessage(binManifestData)!; @@ -32,7 +41,6 @@ class TestAssetBundle extends AssetBundle { } } - void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -65,3 +73,29 @@ void main() { expect(manifest.getAssetVariants('invalid asset key'), isNull); }); } + +String createAssetManifestJson(Map> manifest) { + final Map jsonObject = manifest.map( + (String key, List value) { + final List variants = value.map((AssetMetadata e) => e.key).toList(); + return MapEntry>(key, variants); + } + ); + + return json.encode(jsonObject); +} + +ByteData createAssetManifestSmcBin(Map> manifest) { + final Map smcObject = manifest.map( + (String key, List value) { + final List variants = value.map((AssetMetadata variant) => { + 'asset': variant.key, + 'dpr': variant.targetDevicePixelRatio, + }).toList(); + + return MapEntry>(key, variants); + } + ); + + return const StandardMessageCodec().encodeMessage(smcObject)!; +} diff --git a/packages/flutter/test/widgets/image_resolution_test.dart b/packages/flutter/test/widgets/image_resolution_test.dart index dedd14adcd793..620cbdff8f8ef 100644 --- a/packages/flutter/test/widgets/image_resolution_test.dart +++ b/packages/flutter/test/widgets/image_resolution_test.dart @@ -19,16 +19,15 @@ import '../image_data.dart'; ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale); double scaleOf(ByteData data) => data.getFloat64(0); -final Map testManifest = json.decode(''' -{ - "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} - ] -} -''') as Map; +final Map testManifest = { + 'assets/image.png' : >[ + {'asset': '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} + ], +}; class TestAssetBundle extends CachingAssetBundle { TestAssetBundle({ required Map manifest }) { @@ -303,13 +302,12 @@ void main() { // if higher resolution assets are not available we will pick the best // available. testWidgets('Low-resolution assets', (WidgetTester tester) async { - final Map manifest = json.decode(''' - { - "assets/image.png" : [ - {"asset": "assets/1.5x/image.png", "dpr": 1.5} - ] - } - ''') as Map; + const Map manifest = { + 'assets/image.png': >[ + {'asset': 'assets/image.png'}, + {'asset': 'assets/1.5x/image.png', 'dpr': 1.5}, + ], + }; final AssetBundle bundle = TestAssetBundle(manifest: manifest); Future testRatio({required double ratio, required double expectedScale}) async { diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index b5a6c8b75dd4d..4eb71f8b8914c 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -166,7 +166,6 @@ class ManifestAssetBundle implements AssetBundle { DateTime? _lastBuildTimestamp; // We assume the main asset is designed for a device pixel ratio of 1.0. - static const double _defaultResolution = 1.0; static const String _kAssetManifestJsonFilename = 'AssetManifest.json'; static const String _kAssetManifestBinFilename = 'AssetManifest.smcbin'; @@ -688,7 +687,7 @@ class ManifestAssetBundle implements AssetBundle { DevFSByteContent _createAssetManifestBinary( Map> assetManifest ) { - double parseScale(String key) { + double? parseScale(String key) { final Uri assetUri = Uri.parse(key); String directoryPath = ''; if (assetUri.pathSegments.length > 1) { @@ -699,7 +698,8 @@ class ManifestAssetBundle implements AssetBundle { if (match != null && match.groupCount > 0) { return double.parse(match.group(1)!); } - return _defaultResolution; + + return null; } final Map result = {}; @@ -708,15 +708,12 @@ class ManifestAssetBundle implements AssetBundle { final List resultVariants = []; final List entries = (manifestEntry.value as List).cast(); 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 resultVariant = {}; - final double variantDevicePixelRatio = parseScale(variant); + final double? variantDevicePixelRatio = parseScale(variant); resultVariant['asset'] = variant; - resultVariant['dpr'] = variantDevicePixelRatio; + if (variantDevicePixelRatio != null) { + resultVariant['dpr'] = variantDevicePixelRatio; + } resultVariants.add(resultVariant); } result[manifestEntry.key] = resultVariants; diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart index c8bb1339efa1b..c45d4e0647ea9 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart @@ -26,6 +26,7 @@ void main() { // rolls into Flutter. return path.replaceAll('/', globals.fs.path.separator); } + void writePubspecFile(String path, String name, { List? assets }) { String assetsSection; if (assets == null) { @@ -62,19 +63,19 @@ $assetsSection ..writeAsStringSync(packages); } + Map assetManifestBinToJson(Map manifest) { + List convertList(List variants) => variants + .map((Object variant) => (variant as Map)['asset']!) + .toList(); + + return manifest.map((Object key, Object value) => MapEntry(key, convertList(value as List))); + } + Future buildAndVerifyAssets( List assets, List packages, - String? expectedJsonAssetManifest, - String? expectedBinAssetManifestAsJson, { - bool expectExists = true, - }) async { - Future extractAssetManifestBinFromBundleAsJson(AssetBundle bundle) async { - final List manifestBytes = await bundle.entries['AssetManifest.smcbin']!.contentsAsBytes(); - return json.encode(const StandardMessageCodec().decodeMessage( - ByteData.sublistView(Uint8List.fromList(manifestBytes)) - )); - } + Map expectedAssetManifest + ) async { final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); @@ -82,27 +83,31 @@ $assetsSection for (final String packageName in packages) { for (final String asset in assets) { final String entryKey = Uri.encodeFull('packages/$packageName/$asset'); - expect(bundle.entries.containsKey(entryKey), expectExists, + expect(bundle.entries, contains(entryKey), reason: 'Cannot find key on bundle: $entryKey'); - if (expectExists) { - expect( - utf8.decode(await bundle.entries[entryKey]!.contentsAsBytes()), - asset, - ); - } + expect( + utf8.decode(await bundle.entries[entryKey]!.contentsAsBytes()), + asset, + ); } } - if (expectExists) { - expect( - utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()), - expectedJsonAssetManifest, - ); - expect( - await extractAssetManifestBinFromBundleAsJson(bundle), - expectedBinAssetManifestAsJson - ); - } + final Map assetManifest = const StandardMessageCodec().decodeMessage( + ByteData.sublistView( + Uint8List.fromList( + await bundle.entries['AssetManifest.smcbin']!.contentsAsBytes() + ) + ) + ) as Map; + + expect( + json.decode(utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes())), + assetManifestBinToJson(expectedAssetManifest), + ); + expect( + assetManifest, + expectedAssetManifest + ); } void writeAssets(String path, List assets) { @@ -190,14 +195,18 @@ $assetsSection writeAssets('p/p/', assets); - const String expectedJsonAssetManifest = '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = '{"packages/test_package/a/foo":[]}'; + final Map expectedAssetManifest = { + 'packages/test_package/a/foo': >[ + { + 'asset': 'packages/test_package/a/foo', + } + ] + }; + await buildAndVerifyAssets( assets, ['test_package'], - expectedJsonAssetManifest, - expectedBinAssetManifest + expectedAssetManifest, ); }, overrides: { FileSystem: () => testFileSystem, @@ -218,14 +227,16 @@ $assetsSection final List assets = ['a/foo']; writeAssets('p/p/lib/', assets); - const String expectedAssetManifest = '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = '{"packages/test_package/a/foo":[]}'; + + const Map expectedAssetManifest = { + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assets, ['test_package'], - expectedAssetManifest, - expectedBinAssetManifest + expectedAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -245,25 +256,20 @@ $assetsSection final List assets = ['a/foo', 'a/2x/foo', 'a/bar']; writeAssets('p/p/', assets); - const String expectedManifest = '{' - '"packages/test_package/a/bar":' - '["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"]' - '}'; - - const String expectedBinManifest = '{' - '"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}]' - '}'; - + const Map expectedManifest = { + 'packages/test_package/a/bar': >[ + {'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'}, + {'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, ['test_package'], expectedManifest, - expectedBinManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -286,17 +292,17 @@ $assetsSection final List assets = ['a/foo', 'a/2x/foo']; writeAssets('p/p/lib/', assets); - const String expectedManifest = '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"]}'; - - const String expectedBinManifest = '{"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}]}'; + const Map expectedManifest = { + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'}, + {'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, ['test_package'], expectedManifest, - expectedBinManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -316,19 +322,19 @@ $assetsSection ); writeAssets('p/p/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/bar":["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":[]}'; - + const Map expectedAssetManifest = { + 'packages/test_package/a/bar': >[ + {'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assets, ['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -354,18 +360,19 @@ $assetsSection ); writeAssets('p/p/lib/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/bar":["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":[]}'; + const Map expectedAssetManifest = { + 'packages/test_package/a/bar': >[ + {'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assets, ['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -393,22 +400,21 @@ $assetsSection writeAssets('p/p/', assets); writeAssets('p2/p/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"],' - '"packages/test_package2/a/foo":' - '["packages/test_package2/a/foo","packages/test_package2/a/2x/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}],' - '"packages/test_package2/a/foo":' - '[{"asset":"packages/test_package2/a/2x/foo","dpr":2.0}]}'; + const Map expectedAssetManifest = { + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'}, + {'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ], + 'packages/test_package2/a/foo': >[ + {'asset': 'packages/test_package2/a/foo'}, + {'asset': 'packages/test_package2/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, ['test_package', 'test_package2'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -439,23 +445,21 @@ $assetsSection writeAssets('p/p/lib/', assets); writeAssets('p2/p/lib/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"],' - '"packages/test_package2/a/foo":' - '["packages/test_package2/a/foo","packages/test_package2/a/2x/foo"]}'; - - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}],' - '"packages/test_package2/a/foo":' - '[{"asset":"packages/test_package2/a/2x/foo","dpr":2.0}]}'; + const Map expectedAssetManifest = { + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'}, + {'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ], + 'packages/test_package2/a/foo': >[ + {'asset': 'packages/test_package2/a/foo'}, + {'asset': 'packages/test_package2/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, ['test_package', 'test_package2'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -482,18 +486,17 @@ $assetsSection final List assets = ['a/foo', 'a/2x/foo']; writeAssets('p2/p/lib/', assets); - const String expectedAssetManifest = - '{"packages/test_package2/a/foo":' - '["packages/test_package2/a/foo","packages/test_package2/a/2x/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package2/a/foo":' - '[{"asset":"packages/test_package2/a/2x/foo","dpr":2.0}]}'; + const Map expectedAssetManifest = { + 'packages/test_package2/a/foo': >[ + {'asset': 'packages/test_package2/a/foo'}, + {'asset': 'packages/test_package2/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, ['test_package2'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -513,18 +516,19 @@ $assetsSection ); writeAssets('p/p/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":["packages/test_package/a/foo"],' - '"packages/test_package/a/foo [x]":["packages/test_package/a/foo [x]"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":[],' - '"packages/test_package/a/foo [x]":[]}'; + const Map expectedAssetManifest = { + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'} + ], + 'packages/test_package/a/foo [x]': >[ + {'asset': 'packages/test_package/a/foo [x]'} + ] + }; await buildAndVerifyAssets( assets, ['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -546,18 +550,19 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = - '{"packages/test_package/a/bar":["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":[]}'; + const Map expectedAssetManifest = { + 'packages/test_package/a/bar': >[ + {'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assetsOnDisk, ['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -578,18 +583,19 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":["packages/test_package/a/foo"],' - '"packages/test_package/abc/bar":["packages/test_package/abc/bar"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":[],' - '"packages/test_package/abc/bar":[]}'; + const Map expectedAssetManifest = { + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'} + ], + 'packages/test_package/abc/bar': >[ + {'asset': 'packages/test_package/abc/bar'} + ] + }; await buildAndVerifyAssets( assetsOnDisk, ['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -637,16 +643,16 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":["packages/test_package/a/foo","packages/test_package/a/2x/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}]}'; - + const Map expectedAssetManifest = { + 'packages/test_package/a/foo': >[ + {'asset': 'packages/test_package/a/foo'}, + {'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assetsOnDisk, ['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -667,15 +673,12 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = '{}'; - const String expectedBinAssetManifest = '{}'; - + const Map expectedAssetManifest = {}; await buildAndVerifyAssets( assetOnManifest, ['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: { FileSystem: () => testFileSystem, @@ -694,13 +697,8 @@ $assetsSection assets: assetOnManifest, ); - await buildAndVerifyAssets( - assetOnManifest, - ['test_package'], - null, - null, - expectExists: false, - ); + final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); + await bundle.build(packagesPath: '.packages'); }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart index 46c7caef6ca87..93c1e5c21c062 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:convert'; +import 'dart:typed_data'; import 'package:file/file.dart'; import 'package:file/memory.dart'; @@ -15,12 +16,13 @@ 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() { - Future>> extractAssetManifestFromBundle(ManifestAssetBundle bundle) async { + Future>> extractAssetManifestJsonFromBundle(ManifestAssetBundle bundle) async { final String manifestJson = utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()); final Map parsedJson = json.decode(manifestJson) as Map; final Iterable keys = parsedJson.keys; @@ -30,6 +32,13 @@ void main() { return parsedManifest; } + Future> extractAssetManifestSmcBinFromBundle(ManifestAssetBundle bundle) async { + final List manifest = await bundle.entries['AssetManifest.smcbin']!.contentsAsBytes(); + final ByteData asByteData = ByteData.view(Uint8List.fromList(manifest).buffer); + final Map decoded = const StandardMessageCodec().decodeMessage(asByteData)! as Map; + return decoded; + } + group('AssetBundle asset variants (with Unix-style paths)', () { late Platform platform; late FileSystem fs; @@ -44,7 +53,11 @@ void main() { ); fs.file('.packages').createSync(); + }); + void createPubspec({ + required List assets, + }) { fs.file('pubspec.yaml').writeAsStringSync( ''' name: test @@ -53,15 +66,14 @@ dependencies: sdk: flutter flutter: assets: - - assets/ - - assets/notAVariant/ - - assets/folder/ - - assets/normalFolder/ +${assets.map((String entry) => ' - $entry').join('\n')} ''' ); - }); + } testWithoutContext('Only images in folders named with device pixel ratios (e.g. 2x, 3.0x) should be considered as variants of other images', () async { + createPubspec(assets: ['assets/', 'assets/notAVariant/']); + const String image = 'assets/image.jpg'; const String image2xVariant = 'assets/2x/image.jpg'; const String imageNonVariant = 'assets/notAVariant/image.jpg'; @@ -89,14 +101,33 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map> manifest = await extractAssetManifestFromBundle(bundle); - - expect(manifest, hasLength(2)); - expect(manifest[image], equals([image, image2xVariant])); - expect(manifest[imageNonVariant], equals([imageNonVariant])); + final Map> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + final Map>> expectedAssetManifest = >>{ + image: >[ + { + 'asset': image, + }, + { + 'asset': image2xVariant, + 'dpr': 2.0, + } + ], + imageNonVariant: >[ + { + 'asset': imageNonVariant, + } + ], + }; + + expect(smcBinManifest, equals(expectedAssetManifest)); + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); }); - testWithoutContext('Asset directories are recursively searched for assets', () async { + testWithoutContext('Asset directories have their subdirectories searched for asset variants', () async { + createPubspec(assets: ['assets/', 'assets/folder/']); + const String topLevelImage = 'assets/image.jpg'; const String secondLevelImage = 'assets/folder/secondLevel.jpg'; const String secondLevel2xVariant = 'assets/folder/2x/secondLevel.jpg'; @@ -124,13 +155,36 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map> manifest = await extractAssetManifestFromBundle(bundle); - expect(manifest, hasLength(2)); - expect(manifest[topLevelImage], equals([topLevelImage])); - expect(manifest[secondLevelImage], equals([secondLevelImage, secondLevel2xVariant])); + final Map> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + expect(jsonManifest, hasLength(2)); + expect(jsonManifest[topLevelImage], equals([topLevelImage])); + expect(jsonManifest[secondLevelImage], equals([secondLevelImage, secondLevel2xVariant])); + + final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + final Map>> expectedAssetManifest = >>{ + topLevelImage: >[ + { + 'asset': topLevelImage, + }, + ], + secondLevelImage: >[ + { + 'asset': secondLevelImage, + }, + { + 'asset': secondLevel2xVariant, + 'dpr': 2.0, + }, + ], + }; + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); + expect(smcBinManifest, equals(expectedAssetManifest)); }); testWithoutContext('Asset paths should never be URI-encoded', () async { + createPubspec(assets: ['assets/normalFolder/']); + const String image = 'assets/normalFolder/i have URI-reserved_characters.jpg'; const String imageVariant = 'assets/normalFolder/3x/i have URI-reserved_characters.jpg'; @@ -156,12 +210,66 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map> manifest = await extractAssetManifestFromBundle(bundle); - expect(manifest, hasLength(1)); - expect(manifest[image], equals([image, imageVariant])); + final Map> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + final Map>> expectedAssetManifest = >>{ + image: >[ + { + 'asset': image, + }, + { + 'asset': imageVariant, + 'dpr': 3.0 + }, + ], + }; + + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); + expect(smcBinManifest, equals(expectedAssetManifest)); }); - }); + testWithoutContext('Main assets are not included if the file does not exist', () async { + createPubspec(assets: ['assets/image.png']); + + // We intentionally do not add a 'assets/image.png'. + const String imageVariant = 'assets/2x/image.png'; + final List assets = [ + 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>> expectedManifest = >>{ + 'assets/image.png': >[ + { + 'asset': imageVariant, + 'dpr': 2.0 + }, + ], + }; + final Map> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + expect(jsonManifest, equals(_assetManifestBinToJson(expectedManifest))); + expect(smcBinManifest, equals(expectedManifest)); + }); + }); group('AssetBundle asset variants (with Windows-style filepaths)', () { late final Platform platform; @@ -217,11 +325,40 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map> manifest = await extractAssetManifestFromBundle(bundle); - - expect(manifest, hasLength(2)); - expect(manifest['assets/foo.jpg'], equals(['assets/foo.jpg', 'assets/2x/foo.jpg'])); - expect(manifest['assets/somewhereElse/bar.jpg'], equals(['assets/somewhereElse/bar.jpg', 'assets/somewhereElse/2x/bar.jpg'])); + final Map>> expectedAssetManifest = >>{ + 'assets/foo.jpg': >[ + { + 'asset': 'assets/foo.jpg', + }, + { + 'asset': 'assets/2x/foo.jpg', + 'dpr': 2.0, + }, + ], + 'assets/somewhereElse/bar.jpg': >[ + { + 'asset': 'assets/somewhereElse/bar.jpg', + }, + { + 'asset': 'assets/somewhereElse/2x/bar.jpg', + 'dpr': 2.0, + }, + ], + }; + + final Map> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); + expect(smcBinManifest, equals(expectedAssetManifest)); }); }); } + +Map _assetManifestBinToJson(Map manifest) { + List convertList(List variants) => variants + .map((Object variant) => (variant as Map)['asset']!) + .toList(); + + return manifest.map((Object key, Object value) => MapEntry(key, convertList(value as List))); +}