44
55import 'dart:async' ;
66import 'dart:collection' ;
7- import 'dart:convert' ;
87
98import 'package:flutter/foundation.dart' ;
109import 'package:flutter/services.dart' ;
1110
1211import 'image_provider.dart' ;
1312
14- const String _kAssetManifestFileName = 'AssetManifest.json' ;
15-
1613/// A screen with a device-pixel ratio strictly less than this value is
1714/// considered a low-resolution screen (typically entry-level to mid-range
1815/// laptops, desktop screens up to QHD, low-end tablets such as Kindle Fire).
@@ -284,18 +281,18 @@ class AssetImage extends AssetBundleImageProvider {
284281 Completer <AssetBundleImageKey >? completer;
285282 Future <AssetBundleImageKey >? result;
286283
287- chosenBundle.loadStructuredData <Map <String , List <String >>?>(_kAssetManifestFileName, manifestParser).then <void >(
288- (Map <String , List <String >>? manifest) {
289- final String chosenName = _chooseVariant (
284+ AssetManifest .loadFromAssetBundle (chosenBundle)
285+ .then ((AssetManifest manifest) {
286+ final Iterable <AssetMetadata > candidateVariants = _getVariants (manifest, keyName);
287+ final AssetMetadata chosenVariant = _chooseVariant (
290288 keyName,
291289 configuration,
292- manifest == null ? null : manifest[keyName],
293- )! ;
294- final double chosenScale = _parseScale (chosenName);
290+ candidateVariants,
291+ );
295292 final AssetBundleImageKey key = AssetBundleImageKey (
296293 bundle: chosenBundle,
297- name: chosenName ,
298- scale: chosenScale ,
294+ name: chosenVariant.key ,
295+ scale: chosenVariant.targetDevicePixelRatio ?? _naturalResolution ,
299296 );
300297 if (completer != null ) {
301298 // We already returned from this function, which means we are in the
@@ -309,14 +306,15 @@ class AssetImage extends AssetBundleImageProvider {
309306 // ourselves.
310307 result = SynchronousFuture <AssetBundleImageKey >(key);
311308 }
312- },
313- ).catchError ((Object error, StackTrace stack) {
314- // We had an error. (This guarantees we weren't called synchronously.)
315- // Forward the error to the caller.
316- assert (completer != null );
317- assert (result == null );
318- completer! .completeError (error, stack);
319- });
309+ })
310+ .onError ((Object error, StackTrace stack) {
311+ // We had an error. (This guarantees we weren't called synchronously.)
312+ // Forward the error to the caller.
313+ assert (completer != null );
314+ assert (result == null );
315+ completer! .completeError (error, stack);
316+ });
317+
320318 if (result != null ) {
321319 // The code above ran synchronously, and came up with an answer.
322320 // Return the SynchronousFuture that we created above.
@@ -328,35 +326,34 @@ class AssetImage extends AssetBundleImageProvider {
328326 return completer.future;
329327 }
330328
331- /// Parses the asset manifest string into a strongly-typed map.
332- @visibleForTesting
333- static Future <Map <String , List <String >>?> manifestParser (String ? jsonData) {
334- if (jsonData == null ) {
335- return SynchronousFuture <Map <String , List <String >>?>(null );
329+ Iterable <AssetMetadata > _getVariants (AssetManifest manifest, String key) {
330+ try {
331+ return manifest.getAssetVariants (key);
332+ } catch (e) {
333+ throw FlutterError .fromParts (< DiagnosticsNode > [
334+ ErrorSummary ('Unable to load asset with key "$key ".' ),
335+ ErrorDescription (
336+ '''
337+ The key was not found in the asset manifest.
338+ Make sure the key is correct and the appropriate file or folder is specified in pubspec.yaml.
339+ ''' ),
340+ ]);
336341 }
337- // TODO(ianh): JSON decoding really shouldn't be on the main thread.
338- final Map <String , dynamic > parsedJson = json.decode (jsonData) as Map <String , dynamic >;
339- final Iterable <String > keys = parsedJson.keys;
340- final Map <String , List <String >> parsedManifest = < String , List <String >> {
341- for (final String key in keys) key: List <String >.from (parsedJson[key] as List <dynamic >),
342- };
343- // TODO(ianh): convert that data structure to the right types.
344- return SynchronousFuture <Map <String , List <String >>?>(parsedManifest);
345342 }
346343
347- String ? _chooseVariant (String main , ImageConfiguration config, List < String > ? candidates ) {
348- if (config.devicePixelRatio == null || candidates == null || candidates .isEmpty) {
349- return main;
344+ AssetMetadata _chooseVariant (String mainAssetKey , ImageConfiguration config, Iterable < AssetMetadata > candidateVariants ) {
345+ if (config.devicePixelRatio == null || candidateVariants .isEmpty) {
346+ return candidateVariants. firstWhere (( AssetMetadata variant) => variant. main) ;
350347 }
351- // TODO(ianh): Consider moving this parsing logic into _manifestParser.
352- final SplayTreeMap < double , String > mapping = SplayTreeMap <double , String >();
353- for (final String candidate in candidates ) {
354- mapping[ _parseScale ( candidate) ] = candidate;
348+ final SplayTreeMap < double , AssetMetadata > candidatesByDevicePixelRatio =
349+ SplayTreeMap <double , AssetMetadata >();
350+ for (final AssetMetadata candidate in candidateVariants ) {
351+ candidatesByDevicePixelRatio[ candidate.targetDevicePixelRatio ?? _naturalResolution ] = candidate;
355352 }
356353 // TODO(ianh): implement support for config.locale, config.textDirection,
357354 // config.size, config.platform (then document this over in the Image.asset
358355 // docs)
359- return _findBestVariant (mapping , config.devicePixelRatio! );
356+ return _findBestVariant (candidatesByDevicePixelRatio , config.devicePixelRatio! );
360357 }
361358
362359 // Returns the "best" asset variant amongst the available `candidates`.
@@ -371,48 +368,28 @@ class AssetImage extends AssetBundleImageProvider {
371368 // lowest key higher than `value`.
372369 // - If the screen has high device pixel ratio, choose the variant with the
373370 // key nearest to `value`.
374- String ? _findBestVariant (SplayTreeMap <double , String > candidates , double value) {
375- if (candidates .containsKey (value)) {
376- return candidates [value]! ;
371+ AssetMetadata _findBestVariant (SplayTreeMap <double , AssetMetadata > candidatesByDpr , double value) {
372+ if (candidatesByDpr .containsKey (value)) {
373+ return candidatesByDpr [value]! ;
377374 }
378- final double ? lower = candidates .lastKeyBefore (value);
379- final double ? upper = candidates .firstKeyAfter (value);
375+ final double ? lower = candidatesByDpr .lastKeyBefore (value);
376+ final double ? upper = candidatesByDpr .firstKeyAfter (value);
380377 if (lower == null ) {
381- return candidates [upper];
378+ return candidatesByDpr [upper]! ;
382379 }
383380 if (upper == null ) {
384- return candidates [lower];
381+ return candidatesByDpr [lower]! ;
385382 }
386383
387384 // On screens with low device-pixel ratios the artifacts from upscaling
388385 // images are more visible than on screens with a higher device-pixel
389386 // ratios because the physical pixels are larger. Choose the higher
390387 // resolution image in that case instead of the nearest one.
391388 if (value < _kLowDprLimit || value > (lower + upper) / 2 ) {
392- return candidates [upper];
389+ return candidatesByDpr [upper]! ;
393390 } else {
394- return candidates[lower];
395- }
396- }
397-
398- static final RegExp _extractRatioRegExp = RegExp (r'/?(\d+(\.\d*)?)x$' );
399-
400- double _parseScale (String key) {
401- if (key == assetName) {
402- return _naturalResolution;
403- }
404-
405- final Uri assetUri = Uri .parse (key);
406- String directoryPath = '' ;
407- if (assetUri.pathSegments.length > 1 ) {
408- directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2 ];
409- }
410-
411- final Match ? match = _extractRatioRegExp.firstMatch (directoryPath);
412- if (match != null && match.groupCount > 0 ) {
413- return double .parse (match.group (1 )! );
391+ return candidatesByDpr[lower]! ;
414392 }
415- return _naturalResolution; // i.e. default to 1.0x
416393 }
417394
418395 @override
0 commit comments