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 = manifest.getAssetVariants (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,24 @@ 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+ AssetMetadata _chooseVariant (String mainAssetKey, ImageConfiguration config, Iterable <AssetMetadata >? candidateVariants) {
330+ if (candidateVariants == null ) {
331+ return AssetMetadata (key: mainAssetKey, targetDevicePixelRatio: null , main: true );
336332 }
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);
345- }
346333
347- String ? _chooseVariant (String main, ImageConfiguration config, List <String >? candidates) {
348- if (config.devicePixelRatio == null || candidates == null || candidates.isEmpty) {
349- return main;
334+ if (config.devicePixelRatio == null ) {
335+ return candidateVariants.firstWhere ((AssetMetadata variant) => variant.main);
350336 }
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;
337+
338+ final SplayTreeMap <double , AssetMetadata > candidatesByDevicePixelRatio =
339+ SplayTreeMap <double , AssetMetadata >();
340+ for (final AssetMetadata candidate in candidateVariants) {
341+ candidatesByDevicePixelRatio[candidate.targetDevicePixelRatio ?? _naturalResolution] = candidate;
355342 }
356343 // TODO(ianh): implement support for config.locale, config.textDirection,
357344 // config.size, config.platform (then document this over in the Image.asset
358345 // docs)
359- return _findBestVariant (mapping , config.devicePixelRatio! );
346+ return _findBestVariant (candidatesByDevicePixelRatio , config.devicePixelRatio! );
360347 }
361348
362349 // Returns the "best" asset variant amongst the available `candidates`.
@@ -371,48 +358,28 @@ class AssetImage extends AssetBundleImageProvider {
371358 // lowest key higher than `value`.
372359 // - If the screen has high device pixel ratio, choose the variant with the
373360 // key nearest to `value`.
374- String ? _findBestVariant (SplayTreeMap <double , String > candidates , double value) {
375- if (candidates .containsKey (value)) {
376- return candidates [value]! ;
361+ AssetMetadata _findBestVariant (SplayTreeMap <double , AssetMetadata > candidatesByDpr , double value) {
362+ if (candidatesByDpr .containsKey (value)) {
363+ return candidatesByDpr [value]! ;
377364 }
378- final double ? lower = candidates .lastKeyBefore (value);
379- final double ? upper = candidates .firstKeyAfter (value);
365+ final double ? lower = candidatesByDpr .lastKeyBefore (value);
366+ final double ? upper = candidatesByDpr .firstKeyAfter (value);
380367 if (lower == null ) {
381- return candidates [upper];
368+ return candidatesByDpr [upper]! ;
382369 }
383370 if (upper == null ) {
384- return candidates [lower];
371+ return candidatesByDpr [lower]! ;
385372 }
386373
387374 // On screens with low device-pixel ratios the artifacts from upscaling
388375 // images are more visible than on screens with a higher device-pixel
389376 // ratios because the physical pixels are larger. Choose the higher
390377 // resolution image in that case instead of the nearest one.
391378 if (value < _kLowDprLimit || value > (lower + upper) / 2 ) {
392- return candidates [upper];
379+ return candidatesByDpr [upper]! ;
393380 } 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 )! );
381+ return candidatesByDpr[lower]! ;
414382 }
415- return _naturalResolution; // i.e. default to 1.0x
416383 }
417384
418385 @override
0 commit comments