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 = manifest.getAssetVariants (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,24 @@ 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
+ AssetMetadata _chooseVariant (String mainAssetKey, ImageConfiguration config, Iterable <AssetMetadata >? candidateVariants) {
330
+ if (candidateVariants == null ) {
331
+ return AssetMetadata (key: mainAssetKey, targetDevicePixelRatio: null , main: true );
336
332
}
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
- }
346
333
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);
350
336
}
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;
355
342
}
356
343
// TODO(ianh): implement support for config.locale, config.textDirection,
357
344
// config.size, config.platform (then document this over in the Image.asset
358
345
// docs)
359
- return _findBestVariant (mapping , config.devicePixelRatio! );
346
+ return _findBestVariant (candidatesByDevicePixelRatio , config.devicePixelRatio! );
360
347
}
361
348
362
349
// Returns the "best" asset variant amongst the available `candidates`.
@@ -371,48 +358,28 @@ class AssetImage extends AssetBundleImageProvider {
371
358
// lowest key higher than `value`.
372
359
// - If the screen has high device pixel ratio, choose the variant with the
373
360
// 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]! ;
377
364
}
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);
380
367
if (lower == null ) {
381
- return candidates [upper];
368
+ return candidatesByDpr [upper]! ;
382
369
}
383
370
if (upper == null ) {
384
- return candidates [lower];
371
+ return candidatesByDpr [lower]! ;
385
372
}
386
373
387
374
// On screens with low device-pixel ratios the artifacts from upscaling
388
375
// images are more visible than on screens with a higher device-pixel
389
376
// ratios because the physical pixels are larger. Choose the higher
390
377
// resolution image in that case instead of the nearest one.
391
378
if (value < _kLowDprLimit || value > (lower + upper) / 2 ) {
392
- return candidates [upper];
379
+ return candidatesByDpr [upper]! ;
393
380
} 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]! ;
414
382
}
415
- return _naturalResolution; // i.e. default to 1.0x
416
383
}
417
384
418
385
@override
0 commit comments