diff --git a/README.md b/README.md index 3de22f9b..2c601072 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,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 022fab2b..cf725521 100644 --- a/packages/core/lib/generators/assets_generator.dart +++ b/packages/core/lib/generators/assets_generator.dart @@ -34,7 +34,7 @@ class AssetsGenConfig { pubspecFile.parent.absolute.path, config.pubspec.packageName, config.pubspec.flutterGen, - config.pubspec.flutter.assets, + _buildAssetsList(config), config.pubspec.flutterGen.assets.exclude.map(Glob.new).toList(), ); } @@ -49,6 +49,21 @@ class AssetsGenConfig { flutterGen.assets.outputs.packageParameterEnabled ? _packageName : ''; } +/// Merge the deferred assets with the main assets. +List _buildAssetsList(Config config) { + // 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 List deferredAssets = []; + config.pubspec.flutter.deferredComponents?.forEach((deferredComponent) { + // Include all manipulated assets to the list of deferred assets. + deferredAssets.addAll(deferredComponent.assets ?? []); + }); + + // Merge the deferred assets with the main assets. + return [...config.pubspec.flutter.assets, ...deferredAssets]; +} + Future generateAssets( AssetsGenConfig config, DartFormatter formatter, diff --git a/packages/core/lib/settings/pubspec.dart b/packages/core/lib/settings/pubspec.dart index 6f8b9649..e01d0ace 100644 --- a/packages/core/lib/settings/pubspec.dart +++ b/packages/core/lib/settings/pubspec.dart @@ -29,6 +29,7 @@ class Flutter { Flutter({ required this.assets, required this.fonts, + required this.deferredComponents, }); @JsonKey(name: 'assets', required: true) @@ -37,6 +38,9 @@ class Flutter { @JsonKey(name: 'fonts', required: true) final List fonts; + @JsonKey(name: 'deferred-components', required: false) + final List? deferredComponents; + factory Flutter.fromJson(Map json) => _$FlutterFromJson(json); } @@ -243,3 +247,20 @@ class FlutterGenElementFontsOutputs extends FlutterGenElementOutputs { factory FlutterGenElementFontsOutputs.fromJson(Map json) => _$FlutterGenElementFontsOutputsFromJson(json); } + +@JsonSerializable() +class FlutterDeferredComponents { + const FlutterDeferredComponents({ + required this.name, + required this.assets, + }); + + @JsonKey(name: 'name', required: true) + final String name; + + @JsonKey(name: 'assets', required: false) + final List? assets; + + factory FlutterDeferredComponents.fromJson(Map json) => + _$FlutterDeferredComponentsFromJson(json); +} diff --git a/packages/core/lib/settings/pubspec.g.dart b/packages/core/lib/settings/pubspec.g.dart index 88be3070..ffe77023 100644 --- a/packages/core/lib/settings/pubspec.g.dart +++ b/packages/core/lib/settings/pubspec.g.dart @@ -42,9 +42,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( @@ -254,3 +260,21 @@ FlutterGenElementFontsOutputs _$FlutterGenElementFontsOutputsFromJson( 'packageParameterEnabled': 'package_parameter_enabled' }, ); + +FlutterDeferredComponents _$FlutterDeferredComponentsFromJson(Map json) => + $checkedCreate( + 'FlutterDeferredComponents', + json, + ($checkedConvert) { + $checkKeys( + json, + 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 f99ec77a..f78eccae 100644 --- a/packages/core/test/assets_gen_test.dart +++ b/packages/core/test/assets_gen_test.dart @@ -134,6 +134,16 @@ void main() { await expectedAssetsGen(pubspec, generated, fact); }); + test('Assets with deferred components assets', () async { + const pubspec = 'test_resources/pubspec_assets_deferred_components.yaml'; + const fact = + 'test_resources/actual_data/assets_deferred_components.gen.dart'; + const generated = + 'test_resources/lib/gen/assets_deferred_components.gen.dart'; + + await expectedAssetsGen(pubspec, generated, fact); + }); + 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_deferred_components.gen.dart b/packages/core/test_resources/actual_data/assets_deferred_components.gen.dart new file mode 100644 index 00000000..e80e6141 --- /dev/null +++ b/packages/core/test_resources/actual_data/assets_deferred_components.gen.dart @@ -0,0 +1,418 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +import 'package:flare_flutter/flare_actor.dart' as _flare_actor; +import 'package:flare_flutter/flare_controller.dart' as _flare_controller; +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 + FlareGenImage get penguin => const FlareGenImage('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 { + 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 {}, + }); + + final String _assetName; + + final Size? size; + final Set flavors; + + 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.low, + 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 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, + 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, + ); + } + 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; +} + +class FlareGenImage { + const FlareGenImage( + this._assetName, { + this.flavors = const {}, + }); + + final String _assetName; + final Set flavors; + + _flare_actor.FlareActor flare({ + String? boundsNode, + String? animation, + BoxFit fit = BoxFit.contain, + Alignment alignment = Alignment.center, + bool isPaused = false, + bool snapToEnd = false, + _flare_controller.FlareController? controller, + _flare_actor.FlareCompletedCallback? callback, + Color? color, + bool shouldClip = true, + bool sizeFromArtboard = false, + String? artboard, + bool antialias = true, + }) { + return _flare_actor.FlareActor( + _assetName, + boundsNode: boundsNode, + animation: animation, + fit: fit, + alignment: alignment, + isPaused: isPaused, + snapToEnd: snapToEnd, + controller: controller, + callback: callback, + color: color, + shouldClip: shouldClip, + sizeFromArtboard: sizeFromArtboard, + artboard: artboard, + antialias: antialias, + ); + } + + 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 00000000..3227e8a7 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 00000000..8f7e2e4f 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 00000000..9e62962a --- /dev/null +++ b/packages/core/test_resources/pubspec_assets_deferred_components.yaml @@ -0,0 +1,40 @@ +name: test + +flutter_gen: + output: lib/gen/ # Optional (default: lib/gen/) + line_length: 80 # Optional (default: 80) + + integrations: + flutter_svg: true + flare_flutter: 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