4
4
5
5
import 'dart:async' ;
6
6
import 'dart:collection' ;
7
- import 'dart:convert' ;
8
7
9
8
import 'package:flutter/foundation.dart' ;
10
9
import 'package:flutter/services.dart' ;
11
10
12
11
import 'image_provider.dart' ;
13
12
14
- const String _kAssetManifestFileName = 'AssetManifest.json' ;
15
-
16
13
/// A screen with a device-pixel ratio strictly less than this value is
17
14
/// considered a low-resolution screen (typically entry-level to mid-range
18
15
/// laptops, desktop screens up to QHD, low-end tablets such as Kindle Fire).
@@ -284,18 +281,18 @@ class AssetImage extends AssetBundleImageProvider {
284
281
Completer <AssetBundleImageKey >? completer;
285
282
Future <AssetBundleImageKey >? result;
286
283
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 (
290
288
keyName,
291
289
configuration,
292
- manifest == null ? null : manifest[keyName],
293
- )! ;
294
- final double chosenScale = _parseScale (chosenName);
290
+ candidateVariants,
291
+ );
295
292
final AssetBundleImageKey key = AssetBundleImageKey (
296
293
bundle: chosenBundle,
297
- name: chosenName ,
298
- scale: chosenScale ,
294
+ name: chosenVariant.key ,
295
+ scale: chosenVariant.targetDevicePixelRatio ?? _naturalResolution ,
299
296
);
300
297
if (completer != null ) {
301
298
// We already returned from this function, which means we are in the
@@ -309,14 +306,15 @@ class AssetImage extends AssetBundleImageProvider {
309
306
// ourselves.
310
307
result = SynchronousFuture <AssetBundleImageKey >(key);
311
308
}
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
+
320
318
if (result != null ) {
321
319
// The code above ran synchronously, and came up with an answer.
322
320
// Return the SynchronousFuture that we created above.
@@ -328,35 +326,34 @@ class AssetImage extends AssetBundleImageProvider {
328
326
return completer.future;
329
327
}
330
328
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
+ ]);
336
341
}
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
342
}
346
343
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) ;
350
347
}
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;
355
352
}
356
353
// TODO(ianh): implement support for config.locale, config.textDirection,
357
354
// config.size, config.platform (then document this over in the Image.asset
358
355
// docs)
359
- return _findBestVariant (mapping , config.devicePixelRatio! );
356
+ return _findBestVariant (candidatesByDevicePixelRatio , config.devicePixelRatio! );
360
357
}
361
358
362
359
// Returns the "best" asset variant amongst the available `candidates`.
@@ -371,48 +368,28 @@ class AssetImage extends AssetBundleImageProvider {
371
368
// lowest key higher than `value`.
372
369
// - If the screen has high device pixel ratio, choose the variant with the
373
370
// 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]! ;
377
374
}
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);
380
377
if (lower == null ) {
381
- return candidates [upper];
378
+ return candidatesByDpr [upper]! ;
382
379
}
383
380
if (upper == null ) {
384
- return candidates [lower];
381
+ return candidatesByDpr [lower]! ;
385
382
}
386
383
387
384
// On screens with low device-pixel ratios the artifacts from upscaling
388
385
// images are more visible than on screens with a higher device-pixel
389
386
// ratios because the physical pixels are larger. Choose the higher
390
387
// resolution image in that case instead of the nearest one.
391
388
if (value < _kLowDprLimit || value > (lower + upper) / 2 ) {
392
- return candidates [upper];
389
+ return candidatesByDpr [upper]! ;
393
390
} 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]! ;
414
392
}
415
- return _naturalResolution; // i.e. default to 1.0x
416
393
}
417
394
418
395
@override
0 commit comments