diff --git a/README.md b/README.md index 163f12840..b63429fa2 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,14 @@ flutter: - assets/flare/Penguin.flr - assets/rive/vehicles.riv - pictures/ocean_view.jpg + + # Also include assets from deferred components + # https://docs.flutter.dev/perf/deferred-components + deferred-components: + - name: myDeferredComponent + assets: + - assets/images/another_image.jps + - assets/videos/a_large_video.mp4 ``` These configurations will generate **`assets.gen.dart`** under the **`lib/gen/`** directory by default. diff --git a/packages/core/lib/generators/assets_generator.dart b/packages/core/lib/generators/assets_generator.dart index 8194425f9..4a3c23406 100644 --- a/packages/core/lib/generators/assets_generator.dart +++ b/packages/core/lib/generators/assets_generator.dart @@ -33,7 +33,7 @@ class AssetsGenConfig { pubspecFile.parent.absolute.path, config.pubspec.packageName, config.pubspec.flutterGen, - config.pubspec.flutter.assets, + _buildFlutterAssetsList(config.pubspec.flutter), config.pubspec.flutterGen.assets.exclude.map(Glob.new).toList(), ); } @@ -48,6 +48,19 @@ class AssetsGenConfig { flutterGen.assets.outputs.packageParameterEnabled ? _packageName : ''; } +/// Build assets from the main list and the deferred components. +List _buildFlutterAssetsList(Flutter flutter) { + final flutterAssets = flutter.assets; + // We may have several deferred components, with a list of assets for each. + // So before spreading the list of deferred components, we need to spread + // the list of assets for each deferred component. + final deferredComponents = flutter.deferredComponents ?? []; + return deferredComponents.fold( + flutterAssets, + (list, component) => list + (component.assets ?? []), + ); +} + Future generateAssets( AssetsGenConfig config, DartFormatter formatter, diff --git a/packages/core/lib/settings/pubspec.dart b/packages/core/lib/settings/pubspec.dart index a8d90afa9..bbe8d600a 100644 --- a/packages/core/lib/settings/pubspec.dart +++ b/packages/core/lib/settings/pubspec.dart @@ -76,6 +76,7 @@ class Flutter { const Flutter({ required this.assets, required this.fonts, + required this.deferredComponents, }); factory Flutter.fromJson(Map json) => _$FlutterFromJson(json); @@ -85,6 +86,9 @@ class Flutter { @JsonKey(name: 'fonts', required: true) final List fonts; + + @JsonKey(name: 'deferred-components', required: false) + final List? deferredComponents; } @JsonSerializable(disallowUnrecognizedKeys: false) @@ -311,3 +315,20 @@ class FlutterGenElementFontsOutputs extends FlutterGenElementOutputs { @JsonKey(name: 'package_parameter_enabled', defaultValue: false) final bool packageParameterEnabled; } + +@JsonSerializable() +class FlutterDeferredComponents { + const FlutterDeferredComponents({ + required this.name, + required this.assets, + }); + + factory FlutterDeferredComponents.fromJson(Map json) => + _$FlutterDeferredComponentsFromJson(json); + + @JsonKey(name: 'name', required: true) + final String name; + + @JsonKey(name: 'assets', required: false) + final List? assets; +} diff --git a/packages/core/lib/settings/pubspec.g.dart b/packages/core/lib/settings/pubspec.g.dart index a72beec85..e5751e2b9 100644 --- a/packages/core/lib/settings/pubspec.g.dart +++ b/packages/core/lib/settings/pubspec.g.dart @@ -44,9 +44,15 @@ Flutter _$FlutterFromJson(Map json) => $checkedCreate( (v) => (v as List) .map((e) => FlutterFonts.fromJson(e as Map)) .toList()), + deferredComponents: $checkedConvert( + 'deferred-components', + (v) => (v as List?) + ?.map((e) => FlutterDeferredComponents.fromJson(e as Map)) + .toList()), ); return val; }, + fieldKeyMap: const {'deferredComponents': 'deferred-components'}, ); FlutterFonts _$FlutterFontsFromJson(Map json) => $checkedCreate( @@ -297,3 +303,22 @@ FlutterGenElementFontsOutputs _$FlutterGenElementFontsOutputsFromJson( 'packageParameterEnabled': 'package_parameter_enabled' }, ); + +FlutterDeferredComponents _$FlutterDeferredComponentsFromJson(Map json) => + $checkedCreate( + 'FlutterDeferredComponents', + json, + ($checkedConvert) { + $checkKeys( + json, + allowedKeys: const ['name', 'assets'], + requiredKeys: const ['name'], + ); + final val = FlutterDeferredComponents( + name: $checkedConvert('name', (v) => v as String), + assets: $checkedConvert('assets', + (v) => (v as List?)?.map((e) => e as Object).toList()), + ); + return val; + }, + ); diff --git a/packages/core/test/assets_gen_test.dart b/packages/core/test/assets_gen_test.dart index a0120477c..66087c494 100644 --- a/packages/core/test/assets_gen_test.dart +++ b/packages/core/test/assets_gen_test.dart @@ -102,6 +102,11 @@ void main() { await expectedAssetsGen(pubspec); }); + test('Assets with deferred components assets', () async { + const pubspec = 'test_resources/pubspec_assets_deferred_components.yaml'; + await expectedAssetsGen(pubspec); + }); + test('Assets with duplicate flavoring entries', () async { const pubspec = 'test_resources/pubspec_assets_flavored_duplicate_entry.yaml'; diff --git a/packages/core/test_resources/actual_data/assets_assets_deferred_components.gen.dart b/packages/core/test_resources/actual_data/assets_assets_deferred_components.gen.dart new file mode 100644 index 000000000..3d44fc5e9 --- /dev/null +++ b/packages/core/test_resources/actual_data/assets_assets_deferred_components.gen.dart @@ -0,0 +1,381 @@ +// dart format width=80 + +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart' as _svg; +import 'package:vector_graphics/vector_graphics.dart' as _vg; + +class $PicturesGen { + const $PicturesGen(); + + /// File path: pictures/chip5.jpg + AssetGenImage get chip5 => const AssetGenImage('pictures/chip5.jpg'); + + /// List of all assets + List get values => [chip5]; +} + +class $AssetsDeferredComponentGen { + const $AssetsDeferredComponentGen(); + + /// Directory path: assets/deferred_component/images + $AssetsDeferredComponentImagesGen get images => + const $AssetsDeferredComponentImagesGen(); +} + +class $AssetsFlareGen { + const $AssetsFlareGen(); + + /// File path: assets/flare/Penguin.flr + String get penguin => 'assets/flare/Penguin.flr'; + + /// List of all assets + List get values => [penguin]; +} + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + /// Directory path: assets/images/2.0x + $AssetsImages20xGen get a2 => const $AssetsImages20xGen(); + + /// Directory path: assets/images/3.0x + $AssetsImages30xGen get a3 => const $AssetsImages30xGen(); + + /// File path: assets/images/chip1.jpg + AssetGenImage get chip1 => const AssetGenImage('assets/images/chip1.jpg'); + + /// File path: assets/images/chip2.jpg + AssetGenImage get chip2 => const AssetGenImage('assets/images/chip2.jpg'); + + /// Directory path: assets/images/chip3 + $AssetsImagesChip3Gen get chip3 => const $AssetsImagesChip3Gen(); + + /// Directory path: assets/images/chip4 + $AssetsImagesChip4Gen get chip4 => const $AssetsImagesChip4Gen(); + + /// Directory path: assets/images/icons + $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen(); + + /// File path: assets/images/logo.png + AssetGenImage get logo => const AssetGenImage('assets/images/logo.png'); + + /// File path: assets/images/profile.jpg + AssetGenImage get profileJpg => + const AssetGenImage('assets/images/profile.jpg'); + + /// File path: assets/images/profile.png + AssetGenImage get profilePng => + const AssetGenImage('assets/images/profile.png'); + + /// List of all assets + List get values => [ + chip1, + chip2, + logo, + profileJpg, + profilePng, + ]; +} + +class $AssetsJsonGen { + const $AssetsJsonGen(); + + /// File path: assets/json/list.json + String get list => 'assets/json/list.json'; + + /// File path: assets/json/map.json + String get map => 'assets/json/map.json'; + + /// List of all assets + List get values => [list, map]; +} + +class $AssetsMovieGen { + const $AssetsMovieGen(); + + /// File path: assets/movie/the_earth.mp4 + String get theEarth => 'assets/movie/the_earth.mp4'; + + /// List of all assets + List get values => [theEarth]; +} + +class $AssetsUnknownGen { + const $AssetsUnknownGen(); + + /// File path: assets/unknown/unknown_mime_type.bk + String get unknownMimeType => 'assets/unknown/unknown_mime_type.bk'; + + /// List of all assets + List get values => [unknownMimeType]; +} + +class $AssetsDeferredComponentImagesGen { + const $AssetsDeferredComponentImagesGen(); + + /// File path: assets/deferred_component/images/chip1.jpg + AssetGenImage get chip1 => + const AssetGenImage('assets/deferred_component/images/chip1.jpg'); + + /// File path: assets/deferred_component/images/component_logo.png + AssetGenImage get componentLogo => const AssetGenImage( + 'assets/deferred_component/images/component_logo.png', + ); + + /// List of all assets + List get values => [chip1, componentLogo]; +} + +class $AssetsImages20xGen { + const $AssetsImages20xGen(); + + /// File path: assets/images/2.0x/chip1.jpg + AssetGenImage get chip1 => + const AssetGenImage('assets/images/2.0x/chip1.jpg'); + + /// List of all assets + List get values => [chip1]; +} + +class $AssetsImages30xGen { + const $AssetsImages30xGen(); + + /// File path: assets/images/3.0x/chip1.jpg + AssetGenImage get chip1 => + const AssetGenImage('assets/images/3.0x/chip1.jpg'); + + /// List of all assets + List get values => [chip1]; +} + +class $AssetsImagesChip3Gen { + const $AssetsImagesChip3Gen(); + + /// File path: assets/images/chip3/chip3.jpg + AssetGenImage get chip3 => + const AssetGenImage('assets/images/chip3/chip3.jpg'); + + /// List of all assets + List get values => [chip3]; +} + +class $AssetsImagesChip4Gen { + const $AssetsImagesChip4Gen(); + + /// File path: assets/images/chip4/chip4.jpg + AssetGenImage get chip4 => + const AssetGenImage('assets/images/chip4/chip4.jpg'); + + /// List of all assets + List get values => [chip4]; +} + +class $AssetsImagesIconsGen { + const $AssetsImagesIconsGen(); + + /// File path: assets/images/icons/dart@test.svg + SvgGenImage get dartTest => + const SvgGenImage('assets/images/icons/dart@test.svg'); + + /// File path: assets/images/icons/fuchsia.svg + SvgGenImage get fuchsia => + const SvgGenImage('assets/images/icons/fuchsia.svg'); + + /// File path: assets/images/icons/kmm.svg + SvgGenImage get kmm => const SvgGenImage('assets/images/icons/kmm.svg'); + + /// File path: assets/images/icons/paint.svg + SvgGenImage get paint => const SvgGenImage('assets/images/icons/paint.svg'); + + /// List of all assets + List get values => [dartTest, fuchsia, kmm, paint]; +} + +class Assets { + const Assets._(); + + static const String changelog = 'CHANGELOG.md'; + static const $AssetsDeferredComponentGen deferredComponent = + $AssetsDeferredComponentGen(); + static const $AssetsFlareGen flare = $AssetsFlareGen(); + static const $AssetsImagesGen images = $AssetsImagesGen(); + static const $AssetsJsonGen json = $AssetsJsonGen(); + static const $AssetsMovieGen movie = $AssetsMovieGen(); + static const $AssetsUnknownGen unknown = $AssetsUnknownGen(); + static const $PicturesGen pictures = $PicturesGen(); + + /// List of all assets + static List get values => [changelog]; +} + +class AssetGenImage { + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + this.animation, + }); + + final String _assetName; + + final Size? size; + final Set flavors; + final AssetGenImageAnimation? animation; + + Image image({ + Key? key, + AssetBundle? bundle, + ImageFrameBuilder? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? scale, + double? width, + double? height, + Color? color, + Animation? opacity, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = true, + bool isAntiAlias = false, + String? package, + FilterQuality filterQuality = FilterQuality.medium, + int? cacheWidth, + int? cacheHeight, + }) { + return Image.asset( + _assetName, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + scale: scale, + width: width, + height: height, + color: color, + opacity: opacity, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + package: package, + filterQuality: filterQuality, + cacheWidth: cacheWidth, + cacheHeight: cacheHeight, + ); + } + + ImageProvider provider({AssetBundle? bundle, String? package}) { + return AssetImage(_assetName, bundle: bundle, package: package); + } + + String get path => _assetName; + + String get keyName => _assetName; +} + +class AssetGenImageAnimation { + const AssetGenImageAnimation({ + required this.isAnimation, + required this.duration, + required this.frames, + }); + + final bool isAnimation; + final Duration duration; + final int frames; +} + +class SvgGenImage { + const SvgGenImage(this._assetName, {this.size, this.flavors = const {}}) + : _isVecFormat = false; + + const SvgGenImage.vec(this._assetName, {this.size, this.flavors = const {}}) + : _isVecFormat = true; + + final String _assetName; + final Size? size; + final Set flavors; + final bool _isVecFormat; + + _svg.SvgPicture svg({ + Key? key, + bool matchTextDirection = false, + AssetBundle? bundle, + String? package, + double? width, + double? height, + BoxFit fit = BoxFit.contain, + AlignmentGeometry alignment = Alignment.center, + bool allowDrawingOutsideViewBox = false, + WidgetBuilder? placeholderBuilder, + String? semanticsLabel, + bool excludeFromSemantics = false, + _svg.SvgTheme? theme, + _svg.ColorMapper? colorMapper, + ColorFilter? colorFilter, + Clip clipBehavior = Clip.hardEdge, + @deprecated Color? color, + @deprecated BlendMode colorBlendMode = BlendMode.srcIn, + @deprecated bool cacheColorFilter = false, + }) { + final _svg.BytesLoader loader; + if (_isVecFormat) { + loader = _vg.AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = _svg.SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + colorMapper: colorMapper, + ); + } + return _svg.SvgPicture( + loader, + key: key, + matchTextDirection: matchTextDirection, + width: width, + height: height, + fit: fit, + alignment: alignment, + allowDrawingOutsideViewBox: allowDrawingOutsideViewBox, + placeholderBuilder: placeholderBuilder, + semanticsLabel: semanticsLabel, + excludeFromSemantics: excludeFromSemantics, + colorFilter: + colorFilter ?? + (color == null ? null : ColorFilter.mode(color, colorBlendMode)), + clipBehavior: clipBehavior, + cacheColorFilter: cacheColorFilter, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} diff --git a/packages/core/test_resources/assets/deferred_component/images/chip1.jpg b/packages/core/test_resources/assets/deferred_component/images/chip1.jpg new file mode 100644 index 000000000..3227e8a7b Binary files /dev/null and b/packages/core/test_resources/assets/deferred_component/images/chip1.jpg differ diff --git a/packages/core/test_resources/assets/deferred_component/images/component_logo.png b/packages/core/test_resources/assets/deferred_component/images/component_logo.png new file mode 100644 index 000000000..8f7e2e4f4 Binary files /dev/null and b/packages/core/test_resources/assets/deferred_component/images/component_logo.png differ diff --git a/packages/core/test_resources/pubspec_assets_deferred_components.yaml b/packages/core/test_resources/pubspec_assets_deferred_components.yaml new file mode 100644 index 000000000..eed30c536 --- /dev/null +++ b/packages/core/test_resources/pubspec_assets_deferred_components.yaml @@ -0,0 +1,39 @@ +name: test + +flutter_gen: + output: lib/gen/ # Optional (default: lib/gen/) + line_length: 80 # Optional (default: 80) + + integrations: + flutter_svg: true + +flutter: + assets: + - assets/images + - assets/images/chip3/chip3.jpg + - assets/images/chip3/chip3.jpg # duplicated + - assets/images/chip4/ + - assets/images/icons/fuchsia.svg + - assets/images/icons/kmm.svg + - assets/images/icons/paint.svg + - assets/images/icons/dart@test.svg + - assets/json/ + - pictures/chip5.jpg + - assets/flare/ + - assets/movie/ + - assets/unknown/unknown_mime_type.bk + - CHANGELOG.md + + deferred-components: + - name: myDeferredComponent + assets: + - assets/deferred_component/images + - assets/deferred_component/images/chip1.jpg + - assets/deferred_component/images/component_logo.png + # Deferred components can load assets from the main assets folder as well + - assets/images/icons/fuchsia.svg + - assets/images/2.0x/chip1.jpg + + - name: mySecondDeferredComponent + assets: + - assets/images/3.0x/chip1.jpg