diff --git a/README.md b/README.md index f051ef4a8..ab26d201d 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,18 @@ flutter: These configurations will generate **`assets.gen.dart`** under the **`lib/gen/`** directory by default. +#### Flavored assets + +Flutter supports +[Conditionally bundling assets based on flavor](https://docs.flutter.dev/deployment/flavors#conditionally-bundling-assets-based-on-flavor). +Assets are only available with flavors if specified. +`flutter_gen` will generate the specified `flavors` for assets regardless the current flavor. +The `flavors` field accessible though `.flavors`, for example: + +```dart +print(MyAssets.images.chip4.flavors); // -> {'extern'} +``` + #### Excluding generating for assets You can specify `flutter_gen > assets > exclude` using `Glob` patterns to exclude particular assets. diff --git a/examples/example/lib/gen/assets.gen.dart b/examples/example/lib/gen/assets.gen.dart index 9efacf68d..ff963ec28 100644 --- a/examples/example/lib/gen/assets.gen.dart +++ b/examples/example/lib/gen/assets.gen.dart @@ -35,6 +35,9 @@ class $AssetsImagesGen { /// 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(); @@ -142,12 +145,25 @@ class $AssetsUnknownGen { List get values => [changelog, readme, unknownMimeType]; } +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'); + AssetGenImage get chip4 => const AssetGenImage( + 'assets/images/chip4/chip4.jpg', + flavors: {'extern'}, + ); /// List of all assets List get values => [chip4]; @@ -202,11 +218,16 @@ class MyAssets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, @@ -280,17 +301,19 @@ class AssetGenImage { class SvgGenImage { const SvgGenImage( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = false; const SvgGenImage.vec( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = true; final String _assetName; - final Size? size; + final Set flavors; final bool _isVecFormat; SvgPicture svg({ @@ -313,12 +336,23 @@ class SvgGenImage { @deprecated BlendMode colorBlendMode = BlendMode.srcIn, @deprecated bool cacheColorFilter = false, }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } return SvgPicture( - _isVecFormat - ? AssetBytesLoader(_assetName, - assetBundle: bundle, packageName: package) - : SvgAssetLoader(_assetName, - assetBundle: bundle, packageName: package, theme: theme), + loader, key: key, matchTextDirection: matchTextDirection, width: width, @@ -342,9 +376,13 @@ class SvgGenImage { } class FlareGenImage { - const FlareGenImage(this._assetName); + const FlareGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; FlareActor flare({ String? boundsNode, @@ -385,9 +423,13 @@ class FlareGenImage { } class RiveGenImage { - const RiveGenImage(this._assetName); + const RiveGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; RiveAnimation rive({ String? artboard, @@ -422,9 +464,13 @@ class RiveGenImage { } class LottieGenImage { - const LottieGenImage(this._assetName); + const LottieGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; LottieBuilder lottie({ Animation? controller, diff --git a/examples/example/pubspec.yaml b/examples/example/pubspec.yaml index fd9faa368..4e92cb1d8 100644 --- a/examples/example/pubspec.yaml +++ b/examples/example/pubspec.yaml @@ -51,7 +51,6 @@ flutter_gen: style: dot-delimiter exclude: - - assets/images/chip3/chip3.jpg - assets-extern/* - pictures/chip5.jpg @@ -76,7 +75,6 @@ flutter: - 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 @@ -91,6 +89,10 @@ flutter: - assets/mix/ - assets-extern/ - pictures/chip5.jpg + + - path: assets/images/chip4/chip4.jpg + flavors: + - extern fonts: - family: Raleway fonts: diff --git a/packages/core/lib/generators/assets_generator.dart b/packages/core/lib/generators/assets_generator.dart index 60d638aec..2547f0d48 100644 --- a/packages/core/lib/generators/assets_generator.dart +++ b/packages/core/lib/generators/assets_generator.dart @@ -13,11 +13,13 @@ import 'package:flutter_gen_core/generators/integrations/rive_integration.dart'; import 'package:flutter_gen_core/generators/integrations/svg_integration.dart'; import 'package:flutter_gen_core/settings/asset_type.dart'; import 'package:flutter_gen_core/settings/config.dart'; +import 'package:flutter_gen_core/settings/flavored_asset.dart'; import 'package:flutter_gen_core/settings/pubspec.dart'; import 'package:flutter_gen_core/utils/error.dart'; import 'package:flutter_gen_core/utils/string.dart'; import 'package:glob/glob.dart'; import 'package:path/path.dart'; +import 'package:yaml/yaml.dart'; class AssetsGenConfig { AssetsGenConfig._( @@ -41,7 +43,7 @@ class AssetsGenConfig { final String rootPath; final String _packageName; final FlutterGen flutterGen; - final List assets; + final List assets; final List exclude; String get packageParameterLiteral => @@ -194,51 +196,89 @@ String? generatePackageNameForConfig(AssetsGenConfig config) { } } -/// Returns a list of all releative path assets that are to be considered. -List _getAssetRelativePathList( +/// Returns a list of all relative path assets that are to be considered. +List _getAssetRelativePathList( /// The absolute root path of the assets directory. String rootPath, - /// List of assets as provided the `flutter`.`assets` section in the pubspec.yaml. - List assets, + /// List of assets as provided the `flutter -> assets` + /// section in the pubspec.yaml. + List assets, - /// List of globs as provided the `flutter_gen`.`assets`.`exclude` section in the pubspec.yaml. + /// List of globs as provided the `flutter_gen -> assets -> exclude` + /// section in the pubspec.yaml. List excludes, ) { - final assetRelativePathList = []; - for (final assetName in assets) { - final assetAbsolutePath = join(rootPath, assetName); + // Normalize. + final normalizedAssets = {...assets.whereType()}; + final normalizingMap = >{}; + // Resolve flavored assets. + for (final map in assets.whereType()) { + final path = (map['path'] as String).trim(); + final flavors = + (map['flavors'] as YamlList?)?.toSet().cast() ?? {}; + if (normalizingMap.containsKey(path)) { + // https://github.com/flutter/flutter/blob/5187cab7bdd434ca74abb45895d17e9fa553678a/packages/flutter_tools/lib/src/asset.dart#L1137-L1139 + throw StateError( + 'Multiple assets entries include the file "$path", ' + 'but they specify different lists of flavors.', + ); + } + normalizingMap[path] = flavors; + } + for (final entry in normalizingMap.entries) { + normalizedAssets.add( + YamlMap.wrap({'path': entry.key, 'flavors': entry.value}), + ); + } + + final assetRelativePathList = []; + for (final asset in normalizedAssets) { + final FlavoredAsset tempAsset; + if (asset is YamlMap) { + tempAsset = FlavoredAsset(path: asset['path'], flavors: asset['flavors']); + } else { + tempAsset = FlavoredAsset(path: (asset as String).trim()); + } + final assetAbsolutePath = join(rootPath, tempAsset.path); if (FileSystemEntity.isDirectorySync(assetAbsolutePath)) { assetRelativePathList.addAll(Directory(assetAbsolutePath) .listSync() .whereType() - .map((e) => relative(e.path, from: rootPath)) + .map( + (e) => tempAsset.copyWith(path: relative(e.path, from: rootPath)), + ) .toList()); } else if (FileSystemEntity.isFileSync(assetAbsolutePath)) { - assetRelativePathList.add(relative(assetAbsolutePath, from: rootPath)); + assetRelativePathList.add( + tempAsset.copyWith(path: relative(assetAbsolutePath, from: rootPath)), + ); } } if (excludes.isEmpty) { return assetRelativePathList; } - return assetRelativePathList - .where((file) => !excludes.any((exclude) => exclude.matches(file))) + .where((asset) => !excludes.any((exclude) => exclude.matches(asset.path))) .toList(); } AssetType _constructAssetTree( - List assetRelativePathList, String rootPath) { + List assetRelativePathList, + String rootPath, +) { // Relative path is the key final assetTypeMap = { - '.': AssetType(rootPath: rootPath, path: '.'), + '.': AssetType(rootPath: rootPath, path: '.', flavors: {}), }; - for (final assetPath in assetRelativePathList) { - var path = assetPath; + for (final asset in assetRelativePathList) { + String path = asset.path; while (path != '.') { assetTypeMap.putIfAbsent( - path, () => AssetType(rootPath: rootPath, path: path)); + path, + () => AssetType(rootPath: rootPath, path: path, flavors: asset.flavors), + ); path = dirname(path); } } @@ -320,7 +360,8 @@ String _dotDelimiterStyleDefinition( final assetsStaticStatements = <_Statement>[]; final assetTypeQueue = ListQueue.from( - _constructAssetTree(assetRelativePathList, rootPath).children); + _constructAssetTree(assetRelativePathList, rootPath).children, + ); while (assetTypeQueue.isNotEmpty) { final assetType = assetTypeQueue.removeFirst(); @@ -428,14 +469,20 @@ String _flatStyleDefinition( List integrations, String Function(String) style, ) { - final statements = _getAssetRelativePathList( + final List paths = _getAssetRelativePathList( config.rootPath, config.assets, config.exclude, - ) - .distinct() - .sorted() - .map((assetPath) => AssetType(rootPath: config.rootPath, path: assetPath)) + ); + paths.sort(((a, b) => a.path.compareTo(b.path))); + final statements = paths + .map( + (assetPath) => AssetType( + rootPath: config.rootPath, + path: assetPath.path, + flavors: assetPath.flavors, + ), + ) .mapToUniqueAssetType(style) .map( (e) => _createAssetTypeStatement( diff --git a/packages/core/lib/generators/integrations/flare_integration.dart b/packages/core/lib/generators/integrations/flare_integration.dart index db2b4dcc2..e8644e16f 100644 --- a/packages/core/lib/generators/integrations/flare_integration.dart +++ b/packages/core/lib/generators/integrations/flare_integration.dart @@ -17,9 +17,14 @@ class FlareIntegration extends Integration { String get classOutput => _classDefinition; String get _classDefinition => '''class FlareGenImage { - const FlareGenImage(this._assetName); + const FlareGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; + ${isPackage ? "\n static const String package = '$packageName';" : ''} FlareActor flare({ @@ -63,10 +68,6 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''} @override String get className => 'FlareGenImage'; - @override - String classInstantiate(AssetType asset) => - 'FlareGenImage(\'${asset.posixStylePath}\')'; - @override bool isSupport(AssetType asset) => asset.extension == '.flr'; diff --git a/packages/core/lib/generators/integrations/image_integration.dart b/packages/core/lib/generators/integrations/image_integration.dart index 672675786..17c71b561 100644 --- a/packages/core/lib/generators/integrations/image_integration.dart +++ b/packages/core/lib/generators/integrations/image_integration.dart @@ -10,8 +10,10 @@ import 'package:image_size_getter/image_size_getter.dart'; /// /// This integration is by enabled by default. class ImageIntegration extends Integration { - ImageIntegration(String packageName, {super.parseMetadata}) - : super(packageName); + ImageIntegration( + String packageName, { + super.parseMetadata, + }) : super(packageName); String get packageParameter => isPackage ? ' = package' : ''; @@ -25,12 +27,18 @@ class ImageIntegration extends Integration { String get classOutput => _classDefinition; String get _classDefinition => '''class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; + ${isPackage ? "\n static const String package = '$packageName';" : ''} final Size? size; + final Set flavors; Image image({ Key? key, @@ -109,11 +117,22 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''} @override String classInstantiate(AssetType asset) { - ImageMetadata? info = parseMetadata ? _getMetadata(asset) : null; - - return 'AssetGenImage(\'${asset.posixStylePath}\'' - '${(info != null) ? ', size: Size(${info.width}, ${info.height})' : ''}' - ')'; + final info = parseMetadata ? _getMetadata(asset) : null; + final buffer = StringBuffer(className); + buffer.write('('); + buffer.write('\'${asset.posixStylePath}\''); + if (info != null) { + buffer.write(', size: Size(${info.width}, ${info.height})'); + } + if (asset.flavors.isNotEmpty) { + buffer.write(', flavors: {'); + final flavors = asset.flavors.map((e) => '\'$e\'').join(', '); + buffer.write(flavors); + buffer.write('}'); + buffer.write(','); // Better formatting. + } + buffer.write(')'); + return buffer.toString(); } @override diff --git a/packages/core/lib/generators/integrations/integration.dart b/packages/core/lib/generators/integrations/integration.dart index f4aa8bad8..4a32c713a 100644 --- a/packages/core/lib/generators/integrations/integration.dart +++ b/packages/core/lib/generators/integrations/integration.dart @@ -19,12 +19,25 @@ abstract class Integration { String get className; - String classInstantiate(AssetType asset); - /// Is this asset type supported by this integration? bool isSupport(AssetType asset); bool get isConstConstructor; + + String classInstantiate(AssetType asset) { + final buffer = StringBuffer(className); + buffer.write('('); + buffer.write('\'${asset.posixStylePath}\''); + if (asset.flavors.isNotEmpty) { + buffer.write(', flavors: {'); + final flavors = asset.flavors.map((e) => '\'$e\'').join(', '); + buffer.write(flavors); + buffer.write('}'); + buffer.write(','); // Better formatting. + } + buffer.write(')'); + return buffer.toString(); + } } /// The deprecation message for the package argument diff --git a/packages/core/lib/generators/integrations/lottie_integration.dart b/packages/core/lib/generators/integrations/lottie_integration.dart index 264153404..ac9bddd6e 100644 --- a/packages/core/lib/generators/integrations/lottie_integration.dart +++ b/packages/core/lib/generators/integrations/lottie_integration.dart @@ -32,9 +32,14 @@ class LottieIntegration extends Integration { String get classOutput => _classDefinition; String get _classDefinition => '''class LottieGenImage { - const LottieGenImage(this._assetName); + const LottieGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; + ${isPackage ? "\n static const String package = '$packageName';" : ''} LottieBuilder lottie({ @@ -95,10 +100,6 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''} @override String get className => 'LottieGenImage'; - @override - String classInstantiate(AssetType asset) => - 'LottieGenImage(\'${asset.posixStylePath}\')'; - @override bool isSupport(AssetType asset) => isLottieFile(asset); diff --git a/packages/core/lib/generators/integrations/rive_integration.dart b/packages/core/lib/generators/integrations/rive_integration.dart index 9830e4ff7..98742c54e 100644 --- a/packages/core/lib/generators/integrations/rive_integration.dart +++ b/packages/core/lib/generators/integrations/rive_integration.dart @@ -16,9 +16,14 @@ class RiveIntegration extends Integration { String get classOutput => _classDefinition; String get _classDefinition => '''class RiveGenImage { - const RiveGenImage(this._assetName); + const RiveGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; + ${isPackage ? "\n static const String package = '$packageName';" : ''} RiveAnimation rive({ @@ -56,10 +61,6 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''} @override String get className => 'RiveGenImage'; - @override - String classInstantiate(AssetType asset) => - 'RiveGenImage(\'${asset.posixStylePath}\')'; - @override bool isSupport(AssetType asset) => asset.extension == '.riv'; diff --git a/packages/core/lib/generators/integrations/svg_integration.dart b/packages/core/lib/generators/integrations/svg_integration.dart index 247afb1c9..1e21e1839 100644 --- a/packages/core/lib/generators/integrations/svg_integration.dart +++ b/packages/core/lib/generators/integrations/svg_integration.dart @@ -24,20 +24,23 @@ class SvgIntegration extends Integration { String get _classDefinition => '''class SvgGenImage { const SvgGenImage( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = false; const SvgGenImage.vec( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = true; final String _assetName; -${isPackage ? "\n static const String package = '$packageName';" : ''} - final Size? size; + final Set flavors; final bool _isVecFormat; +${isPackage ? "\n static const String package = '$packageName';" : ''} + SvgPicture svg({ Key? key, bool matchTextDirection = false, @@ -59,10 +62,23 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''} @deprecated BlendMode colorBlendMode = BlendMode.srcIn, @deprecated bool cacheColorFilter = false, }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } return SvgPicture( - _isVecFormat ? - AssetBytesLoader(_assetName, assetBundle: bundle, packageName: package) : - SvgAssetLoader(_assetName, assetBundle: bundle, packageName: package, theme: theme), + loader, key: key, matchTextDirection: matchTextDirection, width: width, @@ -89,31 +105,42 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''} @override String classInstantiate(AssetType asset) { - // Query extra information about the SVG - ImageMetadata? info = parseMetadata ? _getMetadata(asset) : null; - - final String constructorName = - asset.extension == '.vec' ? 'SvgGenImage.vec' : 'SvgGenImage'; - - return "$constructorName('${asset.posixStylePath}'" - "${(info != null) ? ', size: Size(${info.width}, ${info.height})' : ''}" - ')'; + // Query extra information about the SVG. + final info = parseMetadata ? _getMetadata(asset) : null; + final buffer = StringBuffer(className); + if (asset.extension == '.vec') { + buffer.write('.vec'); + } + buffer.write('('); + buffer.write('\'${asset.posixStylePath}\''); + if (info != null) { + buffer.write(', size: Size(${info.width}, ${info.height})'); + } + if (asset.flavors.isNotEmpty) { + buffer.write(', flavors: {'); + final flavors = asset.flavors.map((e) => '\'$e\'').join(', '); + buffer.write(flavors); + buffer.write('}'); + buffer.write(','); // Better formatting. + } + buffer.write(')'); + return buffer.toString(); } ImageMetadata? _getMetadata(AssetType asset) { try { // The SVG file is read fully, then parsed with the vector_graphics - // library. This is quite a heavy way to extract just the dimenions, but - // it's also the same way it will be eventually rendered by Flutter. + // library. This is quite a heavy way to extract just the dimensions, + // but it's also the same way it will be eventually rendered by Flutter. final svg = File(asset.fullPath).readAsStringSync(); final vec = parseWithoutOptimizers(svg); return ImageMetadata(vec.width, vec.height); } catch (e) { stderr.writeln( - '[WARNING] Failed to parse SVG \'${asset.path}\' metadata: $e'); + '[WARNING] Failed to parse SVG \'${asset.path}\' metadata: $e', + ); + return null; } - - return null; } @override diff --git a/packages/core/lib/settings/asset_type.dart b/packages/core/lib/settings/asset_type.dart index 0f682c034..3c2fee972 100644 --- a/packages/core/lib/settings/asset_type.dart +++ b/packages/core/lib/settings/asset_type.dart @@ -7,10 +7,15 @@ import 'package:path/path.dart'; /// https://github.com/dart-lang/mime/blob/master/lib/src/default_extension_map.dart class AssetType { - AssetType({required this.rootPath, required this.path}); + AssetType({ + required this.rootPath, + required this.path, + required this.flavors, + }); final String rootPath; final String path; + final Set flavors; final List _children = List.empty(growable: true); @@ -68,7 +73,11 @@ class UniqueAssetType extends AssetType { this.basenameOnly = false, this.needExtension = false, this.suffix = '', - }) : super(rootPath: assetType.rootPath, path: assetType.path); + }) : super( + rootPath: assetType.rootPath, + path: assetType.path, + flavors: assetType.flavors, + ); /// Convert the asset name to a correctly styled name, e.g camelCase or /// snakeCase. diff --git a/packages/core/lib/settings/flavored_asset.dart b/packages/core/lib/settings/flavored_asset.dart new file mode 100644 index 000000000..66e24aa03 --- /dev/null +++ b/packages/core/lib/settings/flavored_asset.dart @@ -0,0 +1,19 @@ +class FlavoredAsset { + const FlavoredAsset({ + required this.path, + this.flavors = const {}, + }); + + final String path; + final Set flavors; + + FlavoredAsset copyWith({String? path, Set? flavors}) { + return FlavoredAsset( + path: path ?? this.path, + flavors: flavors ?? this.flavors, + ); + } + + @override + String toString() => 'FlavoredAsset(path: $path, flavors: $flavors)'; +} diff --git a/packages/core/lib/settings/pubspec.dart b/packages/core/lib/settings/pubspec.dart index f2592a4e7..62436f6b5 100644 --- a/packages/core/lib/settings/pubspec.dart +++ b/packages/core/lib/settings/pubspec.dart @@ -32,7 +32,7 @@ class Flutter { }); @JsonKey(name: 'assets', required: true) - final List assets; + final List assets; @JsonKey(name: 'fonts', required: true) final List fonts; diff --git a/packages/core/lib/settings/pubspec.g.dart b/packages/core/lib/settings/pubspec.g.dart index 9945fcdb5..c03f15b07 100644 --- a/packages/core/lib/settings/pubspec.g.dart +++ b/packages/core/lib/settings/pubspec.g.dart @@ -36,7 +36,7 @@ Flutter _$FlutterFromJson(Map json) => $checkedCreate( ); final val = Flutter( assets: $checkedConvert('assets', - (v) => (v as List).map((e) => e as String).toList()), + (v) => (v as List).map((e) => e as Object).toList()), fonts: $checkedConvert( 'fonts', (v) => (v as List) diff --git a/packages/core/test/assets_gen_integrations_test.dart b/packages/core/test/assets_gen_integrations_test.dart index 3fc67a039..66002d32b 100644 --- a/packages/core/test/assets_gen_integrations_test.dart +++ b/packages/core/test/assets_gen_integrations_test.dart @@ -1,5 +1,6 @@ @TestOn('vm') import 'package:flutter_gen_core/generators/integrations/flare_integration.dart'; +import 'package:flutter_gen_core/generators/integrations/integration.dart'; import 'package:flutter_gen_core/generators/integrations/lottie_integration.dart'; import 'package:flutter_gen_core/generators/integrations/rive_integration.dart'; import 'package:flutter_gen_core/generators/integrations/svg_integration.dart'; @@ -9,6 +10,27 @@ import 'package:test/test.dart'; import 'gen_test_helper.dart'; +class TestIntegration extends Integration { + TestIntegration() : super(''); + + @override + String get className => 'TestIntegration'; + + @override + String get classOutput => throw UnimplementedError(); + + @override + bool get isConstConstructor => true; + + @override + bool isSupport(AssetType asset) { + return true; + } + + @override + List get requiredImports => []; +} + void main() { group('Test Assets Integration generator', () { final resPath = p.absolute('test_resources'); @@ -21,6 +43,15 @@ void main() { await expectedAssetsGen(pubspec, generated, fact); }); + test('Integration.classInstantiate', () { + expect( + TestIntegration().classInstantiate( + AssetType(rootPath: resPath, path: 'assets/path', flavors: {'test'}), + ), + 'TestIntegration(\'assets/path\', flavors: {\'test\'},)', + ); + }); + test('Assets with Svg integrations on pubspec.yaml', () async { const pubspec = 'test_resources/pubspec_assets_svg_integrations.yaml'; const fact = @@ -34,17 +65,64 @@ void main() { expect(integration.className, 'SvgGenImage'); expect( integration.classInstantiate( - AssetType(rootPath: resPath, path: 'assets/path')), + AssetType( + rootPath: resPath, + path: 'assets/path', + flavors: {}, + ), + ), 'SvgGenImage(\'assets/path\')', ); expect( - integration.isSupport( - AssetType(rootPath: resPath, path: 'assets/path/dog.svg')), - isTrue); + integration.classInstantiate( + AssetType( + rootPath: resPath, + path: 'assets/path', + flavors: {'test'}, + ), + ), + 'SvgGenImage(\'assets/path\', flavors: {\'test\'},)', + ); + expect( + integration.classInstantiate( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.vec', + flavors: {}, + ), + ), + 'SvgGenImage.vec(\'assets/path/dog.vec\')', + ); + expect( + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.svg', + flavors: {}, + ), + ), + isTrue, + ); + expect( + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.vec', + flavors: {}, + ), + ), + isTrue, + ); expect( - integration.isSupport( - AssetType(rootPath: resPath, path: 'assets/path/dog.png')), - isFalse); + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.png', + flavors: {}, + ), + ), + isFalse, + ); expect(integration.isConstConstructor, isTrue); expect(integration.classOutput.contains('String? package,'), isTrue); @@ -75,17 +153,35 @@ void main() { final integration = FlareIntegration(''); expect(integration.className, 'FlareGenImage'); expect( - integration.classInstantiate( - AssetType(rootPath: resPath, path: 'assets/path')), - 'FlareGenImage(\'assets/path\')'); + integration.classInstantiate( + AssetType( + rootPath: resPath, + path: 'assets/path', + flavors: {}, + ), + ), + 'FlareGenImage(\'assets/path\')', + ); expect( - integration.isSupport( - AssetType(rootPath: resPath, path: 'assets/path/dog.flr')), - isTrue); + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.flr', + flavors: {}, + ), + ), + isTrue, + ); expect( - integration.isSupport( - AssetType(rootPath: resPath, path: 'assets/path/dog.json')), - isFalse); + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.json', + flavors: {}, + ), + ), + isFalse, + ); expect(integration.isConstConstructor, isTrue); expect(integration.classOutput.contains('_assetName,'), isTrue); @@ -109,17 +205,35 @@ void main() { final integration = RiveIntegration(''); expect(integration.className, 'RiveGenImage'); expect( - integration.classInstantiate( - AssetType(rootPath: resPath, path: 'assets/path')), - 'RiveGenImage(\'assets/path\')'); + integration.classInstantiate( + AssetType( + rootPath: resPath, + path: 'assets/path', + flavors: {}, + ), + ), + 'RiveGenImage(\'assets/path\')', + ); expect( - integration.isSupport( - AssetType(rootPath: resPath, path: 'assets/path/dog.riv')), - isTrue); + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.riv', + flavors: {}, + ), + ), + isTrue, + ); expect( - integration.isSupport( - AssetType(rootPath: resPath, path: 'assets/path/dog.json')), - isFalse); + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/path/dog.json', + flavors: {}, + ), + ), + isFalse, + ); expect(integration.isConstConstructor, isTrue); expect(integration.classOutput.contains('_assetName,'), isTrue); @@ -143,18 +257,35 @@ void main() { final integration = LottieIntegration(''); expect(integration.className, 'LottieGenImage'); expect( - integration.classInstantiate( - AssetType(rootPath: resPath, path: 'assets/lottie')), - 'LottieGenImage(\'assets/lottie\')'); + integration.classInstantiate( + AssetType( + rootPath: resPath, + path: 'assets/lottie', + flavors: {}, + ), + ), + 'LottieGenImage(\'assets/lottie\')', + ); expect( - integration.isSupport(AssetType( - rootPath: resPath, path: 'assets/lottie/hamburger_arrow.json')), - isTrue); + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/lottie/hamburger_arrow.json', + flavors: {}, + ), + ), + isTrue, + ); expect( - integration.isSupport(AssetType( - rootPath: resPath, - path: 'assets/lottie/hamburger_arrow_without_version.json')), - isFalse); + integration.isSupport( + AssetType( + rootPath: resPath, + path: 'assets/lottie/hamburger_arrow_without_version.json', + flavors: {}, + ), + ), + isFalse, + ); expect(integration.isConstConstructor, isTrue); expect(integration.classOutput.contains('String? package,'), isTrue); diff --git a/packages/core/test/assets_gen_test.dart b/packages/core/test/assets_gen_test.dart index 4f3315d22..ec2d11f41 100644 --- a/packages/core/test/assets_gen_test.dart +++ b/packages/core/test/assets_gen_test.dart @@ -127,6 +127,28 @@ void main() { await expectedAssetsGen(pubspec, generated, fact); }); + test('Assets with flavored assets', () async { + const pubspec = 'test_resources/pubspec_assets_flavored.yaml'; + const fact = 'test_resources/actual_data/assets_flavored.gen.dart'; + const generated = 'test_resources/lib/gen/assets_flavored.gen.dart'; + + await expectedAssetsGen(pubspec, generated, fact); + }); + + test('Assets with duplicate flavoring entries', () async { + const pubspec = + 'test_resources/pubspec_assets_flavored_duplicate_entry.yaml'; + const fact = + 'test_resources/actual_data/assets_flavored_duplicate_entry.gen.dart'; + const generated = + 'test_resources/lib/gen/assets_flavored_duplicate_entry.gen.dart'; + + await expectLater( + () => runAssetsGen(pubspec, generated, fact), + throwsA(isA()), + ); + }); + test('Assets with terrible names (camelCase)', () async { // See [AssetTypeIterable.mapToUniqueAssetType] for the rules for picking // identifer names. @@ -143,7 +165,8 @@ void main() { 'assets/profilePng.jpg': 'profilePngJpg', // Asset overlapping with a directory name. - 'assets/image': 'image', // Directory + 'assets/image': 'image', + // Directory 'assets/image.jpg': 'imageJpg', // Asset with no base name (but ends up overlapping the previous asset) @@ -155,12 +178,18 @@ void main() { 'assets/français.jpg': 'franAis', // Dart Reserved Words - 'assets/async.png': 'async', // allowed - 'assets/abstract.png': 'abstract', // allowed - 'assets/await.png': 'awaitPng', // must be suffixed (but can use Png) - 'assets/assert.png': 'assertPng', // must be suffixed (but can use Png) - 'assets/await': 'await_', // must be suffixed - 'assets/assert': 'assert_', // must be suffixed + // allowed + 'assets/async.png': 'async', + // allowed + 'assets/abstract.png': 'abstract', + // must be suffixed (but can use Png) + 'assets/await.png': 'awaitPng', + // must be suffixed (but can use Png) + 'assets/assert.png': 'assertPng', + // must be suffixed + 'assets/await': 'await_', + // must be suffixed + 'assets/assert': 'assert_', // Asset with a number as the first character 'assets/7up.png': 'a7up', @@ -176,12 +205,12 @@ void main() { final List assets = tests.keys .sorted() - .map((e) => AssetType(rootPath: '', path: e)) + .map((e) => AssetType(rootPath: '', path: e, flavors: {})) .toList(); final got = assets.mapToUniqueAssetType(camelCase); - // Expect no dups. + // Expect no duplicates. final names = got.map((e) => e.name); expect(names.sorted(), tests.values.sorted()); }); diff --git a/packages/core/test/config_test.dart b/packages/core/test/config_test.dart index ab73b3a23..53cc117d6 100644 --- a/packages/core/test/config_test.dart +++ b/packages/core/test/config_test.dart @@ -1 +1,36 @@ -void main() {} +import 'package:collection/collection.dart'; +import 'package:flutter_gen_core/settings/flavored_asset.dart'; +import 'package:test/test.dart'; + +void main() { + group(FlavoredAsset, () { + test('constructor', () { + expect( + FlavoredAsset(path: '').toString(), + 'FlavoredAsset(path: , flavors: {})', + ); + expect( + FlavoredAsset(path: 'assets/path'), + isA(), + ); + expect( + FlavoredAsset(path: 'assets/path', flavors: {}), + isA(), + ); + expect( + FlavoredAsset(path: 'assets/path', flavors: {'test'}), + isA(), + ); + expect( + FlavoredAsset(path: '1').copyWith(path: '2'), + predicate((e) => e.path == '2'), + ); + expect( + FlavoredAsset(path: '1').copyWith(flavors: {'test'}), + predicate( + (e) => SetEquality().equals(e.flavors, {'test'}), + ), + ); + }); + }); +} diff --git a/packages/core/test/gen_test_helper.dart b/packages/core/test/gen_test_helper.dart index e6c48d82b..6543fc595 100644 --- a/packages/core/test/gen_test_helper.dart +++ b/packages/core/test/gen_test_helper.dart @@ -12,22 +12,41 @@ import 'package:test/test.dart'; Future clearTestResults() async {} -/// Assets -Future expectedAssetsGen( - String pubspec, String generated, String fact) async { - await FlutterGenerator(File(pubspec), assetsName: p.basename(generated)) - .build(); +Future> runAssetsGen( + String pubspec, + String generated, + String fact, +) async { + await FlutterGenerator( + File(pubspec), + assetsName: p.basename(generated), + ).build(); final pubspecFile = File(pubspec); final config = loadPubspecConfig(pubspecFile); final formatter = DartFormatter( - pageWidth: config.pubspec.flutterGen.lineLength, lineEnding: '\n'); + pageWidth: config.pubspec.flutterGen.lineLength, + lineEnding: '\n', + ); final actual = generateAssets( - AssetsGenConfig.fromConfig(pubspecFile, config), formatter); - final expected = - formatter.format(File(fact).readAsStringSync().replaceAll('\r\n', '\n')); + AssetsGenConfig.fromConfig(pubspecFile, config), + formatter, + ); + final expected = formatter.format( + File(fact).readAsStringSync().replaceAll('\r\n', '\n'), + ); + return [actual, expected]; +} +/// Assets +Future expectedAssetsGen( + String pubspec, + String generated, + String fact, +) async { + final results = await runAssetsGen(pubspec, generated, fact); + final actual = results.first, expected = results.last; expect( File(generated).readAsStringSync(), isNotEmpty, @@ -35,22 +54,42 @@ Future expectedAssetsGen( expect(actual, expected); } -/// Colors -Future expectedColorsGen( - String pubspec, String generated, String fact) async { - await FlutterGenerator(File(pubspec), colorsName: p.basename(generated)) - .build(); +Future> runColorsGen( + String pubspec, + String generated, + String fact, +) async { + await FlutterGenerator( + File(pubspec), + colorsName: p.basename(generated), + ).build(); final pubspecFile = File(pubspec); final config = loadPubspecConfig(pubspecFile); final formatter = DartFormatter( - pageWidth: config.pubspec.flutterGen.lineLength, lineEnding: '\n'); + pageWidth: config.pubspec.flutterGen.lineLength, + lineEnding: '\n', + ); - final actual = - generateColors(pubspecFile, formatter, config.pubspec.flutterGen.colors); - final expected = - formatter.format(File(fact).readAsStringSync().replaceAll('\r\n', '\n')); + final actual = generateColors( + pubspecFile, + formatter, + config.pubspec.flutterGen.colors, + ); + final expected = formatter.format( + File(fact).readAsStringSync().replaceAll('\r\n', '\n'), + ); + return [actual, expected]; +} +/// Colors +Future expectedColorsGen( + String pubspec, + String generated, + String fact, +) async { + final results = await runColorsGen(pubspec, generated, fact); + final actual = results.first, expected = results.last; expect( File(generated).readAsStringSync(), isNotEmpty, @@ -58,22 +97,43 @@ Future expectedColorsGen( expect(actual, expected); } -/// Fonts -Future expectedFontsGen( - String pubspec, String generated, String fact) async { - await FlutterGenerator(File(pubspec), fontsName: p.basename(generated)) - .build(); +Future> runFontsGen( + String pubspec, + String generated, + String fact, +) async { + await FlutterGenerator( + File(pubspec), + fontsName: p.basename(generated), + ).build(); final pubspecFile = File(pubspec); final config = loadPubspecConfig(pubspecFile); final formatter = DartFormatter( - pageWidth: config.pubspec.flutterGen.lineLength, lineEnding: '\n'); + pageWidth: config.pubspec.flutterGen.lineLength, + lineEnding: '\n', + ); final actual = generateFonts( - formatter, config.pubspec.flutter.fonts, config.pubspec.flutterGen.fonts); - final expected = - formatter.format(File(fact).readAsStringSync().replaceAll('\r\n', '\n')); + formatter, + config.pubspec.flutter.fonts, + config.pubspec.flutterGen.fonts, + ); + final expected = formatter.format( + File(fact).readAsStringSync().replaceAll('\r\n', '\n'), + ); + + return [actual, expected]; +} +/// Fonts +Future expectedFontsGen( + String pubspec, + String generated, + String fact, +) async { + final results = await runFontsGen(pubspec, generated, fact); + final actual = results.first, expected = results.last; expect( File(generated).readAsStringSync(), isNotEmpty, diff --git a/packages/core/test_resources/actual_data/assets.gen.dart b/packages/core/test_resources/actual_data/assets.gen.dart index 623750cb5..acf5974b7 100644 --- a/packages/core/test_resources/actual_data/assets.gen.dart +++ b/packages/core/test_resources/actual_data/assets.gen.dart @@ -160,11 +160,16 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, @@ -238,17 +243,19 @@ class AssetGenImage { class SvgGenImage { const SvgGenImage( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = false; const SvgGenImage.vec( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = true; final String _assetName; - final Size? size; + final Set flavors; final bool _isVecFormat; SvgPicture svg({ @@ -271,12 +278,23 @@ class SvgGenImage { @deprecated BlendMode colorBlendMode = BlendMode.srcIn, @deprecated bool cacheColorFilter = false, }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } return SvgPicture( - _isVecFormat - ? AssetBytesLoader(_assetName, - assetBundle: bundle, packageName: package) - : SvgAssetLoader(_assetName, - assetBundle: bundle, packageName: package, theme: theme), + loader, key: key, matchTextDirection: matchTextDirection, width: width, @@ -300,9 +318,13 @@ class SvgGenImage { } class FlareGenImage { - const FlareGenImage(this._assetName); + const FlareGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; FlareActor flare({ String? boundsNode, diff --git a/packages/core/test_resources/actual_data/assets_camel_case.gen.dart b/packages/core/test_resources/actual_data/assets_camel_case.gen.dart index 95df6265c..f409e1043 100644 --- a/packages/core/test_resources/actual_data/assets_camel_case.gen.dart +++ b/packages/core/test_resources/actual_data/assets_camel_case.gen.dart @@ -82,11 +82,16 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, diff --git a/packages/core/test_resources/actual_data/assets_change_class_name.gen.dart b/packages/core/test_resources/actual_data/assets_change_class_name.gen.dart index e73d70e0a..36f01d714 100644 --- a/packages/core/test_resources/actual_data/assets_change_class_name.gen.dart +++ b/packages/core/test_resources/actual_data/assets_change_class_name.gen.dart @@ -41,11 +41,16 @@ class MyAssets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, diff --git a/packages/core/test_resources/actual_data/assets_directory_path.gen.dart b/packages/core/test_resources/actual_data/assets_directory_path.gen.dart index 0071ab5d1..e4025aad4 100644 --- a/packages/core/test_resources/actual_data/assets_directory_path.gen.dart +++ b/packages/core/test_resources/actual_data/assets_directory_path.gen.dart @@ -78,11 +78,16 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, @@ -156,17 +161,19 @@ class AssetGenImage { class SvgGenImage { const SvgGenImage( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = false; const SvgGenImage.vec( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = true; final String _assetName; - final Size? size; + final Set flavors; final bool _isVecFormat; SvgPicture svg({ @@ -189,12 +196,23 @@ class SvgGenImage { @deprecated BlendMode colorBlendMode = BlendMode.srcIn, @deprecated bool cacheColorFilter = false, }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } return SvgPicture( - _isVecFormat - ? AssetBytesLoader(_assetName, - assetBundle: bundle, packageName: package) - : SvgAssetLoader(_assetName, - assetBundle: bundle, packageName: package, theme: theme), + loader, key: key, matchTextDirection: matchTextDirection, width: width, diff --git a/packages/core/test_resources/actual_data/assets_flare_integrations.gen.dart b/packages/core/test_resources/actual_data/assets_flare_integrations.gen.dart index 231614e7c..02b53db94 100644 --- a/packages/core/test_resources/actual_data/assets_flare_integrations.gen.dart +++ b/packages/core/test_resources/actual_data/assets_flare_integrations.gen.dart @@ -28,9 +28,13 @@ class Assets { } class FlareGenImage { - const FlareGenImage(this._assetName); + const FlareGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; FlareActor flare({ String? boundsNode, diff --git a/packages/core/test_resources/actual_data/assets_flavored.gen.dart b/packages/core/test_resources/actual_data/assets_flavored.gen.dart new file mode 100644 index 000000000..987e64ade --- /dev/null +++ b/packages/core/test_resources/actual_data/assets_flavored.gen.dart @@ -0,0 +1,367 @@ +/// 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:flutter/widgets.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:vector_graphics/vector_graphics.dart'; +import 'package:flare_flutter/flare_actor.dart'; +import 'package:flare_flutter/flare_controller.dart'; + +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 $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(); + + /// 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 $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', + flavors: {'test'}, + ); + + /// 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 $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 = false, + 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; + + 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, + SvgTheme? theme, + ColorFilter? colorFilter, + Clip clipBehavior = Clip.hardEdge, + @deprecated Color? color, + @deprecated BlendMode colorBlendMode = BlendMode.srcIn, + @deprecated bool cacheColorFilter = false, + }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } + return 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; + + FlareActor flare({ + String? boundsNode, + String? animation, + BoxFit fit = BoxFit.contain, + Alignment alignment = Alignment.center, + bool isPaused = false, + bool snapToEnd = false, + FlareController? controller, + FlareCompletedCallback? callback, + Color? color, + bool shouldClip = true, + bool sizeFromArtboard = false, + String? artboard, + bool antialias = true, + }) { + return 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/actual_data/assets_lottie_integrations.gen.dart b/packages/core/test_resources/actual_data/assets_lottie_integrations.gen.dart index abde7ee96..8db51bf65 100644 --- a/packages/core/test_resources/actual_data/assets_lottie_integrations.gen.dart +++ b/packages/core/test_resources/actual_data/assets_lottie_integrations.gen.dart @@ -28,9 +28,13 @@ class Assets { } class LottieGenImage { - const LottieGenImage(this._assetName); + const LottieGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; LottieBuilder lottie({ Animation? controller, diff --git a/packages/core/test_resources/actual_data/assets_no_integrations.gen.dart b/packages/core/test_resources/actual_data/assets_no_integrations.gen.dart index 722ecdc7c..8774a22cb 100644 --- a/packages/core/test_resources/actual_data/assets_no_integrations.gen.dart +++ b/packages/core/test_resources/actual_data/assets_no_integrations.gen.dart @@ -116,11 +116,16 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, diff --git a/packages/core/test_resources/actual_data/assets_package_exclude_files.gen.dart b/packages/core/test_resources/actual_data/assets_package_exclude_files.gen.dart index d60634822..e67908c7e 100644 --- a/packages/core/test_resources/actual_data/assets_package_exclude_files.gen.dart +++ b/packages/core/test_resources/actual_data/assets_package_exclude_files.gen.dart @@ -71,11 +71,16 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, diff --git a/packages/core/test_resources/actual_data/assets_package_parameter.gen.dart b/packages/core/test_resources/actual_data/assets_package_parameter.gen.dart index 99ae52686..21f1b1a9b 100644 --- a/packages/core/test_resources/actual_data/assets_package_parameter.gen.dart +++ b/packages/core/test_resources/actual_data/assets_package_parameter.gen.dart @@ -69,13 +69,18 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; static const String package = 'test'; final Size? size; + final Set flavors; Image image({ Key? key, @@ -151,21 +156,23 @@ class AssetGenImage { class SvgGenImage { const SvgGenImage( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = false; const SvgGenImage.vec( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = true; final String _assetName; - - static const String package = 'test'; - final Size? size; + final Set flavors; final bool _isVecFormat; + static const String package = 'test'; + SvgPicture svg({ Key? key, bool matchTextDirection = false, @@ -187,12 +194,23 @@ class SvgGenImage { @deprecated BlendMode colorBlendMode = BlendMode.srcIn, @deprecated bool cacheColorFilter = false, }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } return SvgPicture( - _isVecFormat - ? AssetBytesLoader(_assetName, - assetBundle: bundle, packageName: package) - : SvgAssetLoader(_assetName, - assetBundle: bundle, packageName: package, theme: theme), + loader, key: key, matchTextDirection: matchTextDirection, width: width, diff --git a/packages/core/test_resources/actual_data/assets_parse_metadata.gen.dart b/packages/core/test_resources/actual_data/assets_parse_metadata.gen.dart index 8a343ff4b..ef4968182 100644 --- a/packages/core/test_resources/actual_data/assets_parse_metadata.gen.dart +++ b/packages/core/test_resources/actual_data/assets_parse_metadata.gen.dart @@ -169,11 +169,16 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, @@ -247,17 +252,19 @@ class AssetGenImage { class SvgGenImage { const SvgGenImage( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = false; const SvgGenImage.vec( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = true; final String _assetName; - final Size? size; + final Set flavors; final bool _isVecFormat; SvgPicture svg({ @@ -280,12 +287,23 @@ class SvgGenImage { @deprecated BlendMode colorBlendMode = BlendMode.srcIn, @deprecated bool cacheColorFilter = false, }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } return SvgPicture( - _isVecFormat - ? AssetBytesLoader(_assetName, - assetBundle: bundle, packageName: package) - : SvgAssetLoader(_assetName, - assetBundle: bundle, packageName: package, theme: theme), + loader, key: key, matchTextDirection: matchTextDirection, width: width, @@ -309,9 +327,13 @@ class SvgGenImage { } class FlareGenImage { - const FlareGenImage(this._assetName); + const FlareGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; FlareActor flare({ String? boundsNode, diff --git a/packages/core/test_resources/actual_data/assets_rive_integrations.gen.dart b/packages/core/test_resources/actual_data/assets_rive_integrations.gen.dart index 54841081b..d296b1689 100644 --- a/packages/core/test_resources/actual_data/assets_rive_integrations.gen.dart +++ b/packages/core/test_resources/actual_data/assets_rive_integrations.gen.dart @@ -27,9 +27,13 @@ class Assets { } class RiveGenImage { - const RiveGenImage(this._assetName); + const RiveGenImage( + this._assetName, { + this.flavors = const {}, + }); final String _assetName; + final Set flavors; RiveAnimation rive({ String? artboard, diff --git a/packages/core/test_resources/actual_data/assets_snake_case.gen.dart b/packages/core/test_resources/actual_data/assets_snake_case.gen.dart index 07baa9ccc..31d813ae0 100644 --- a/packages/core/test_resources/actual_data/assets_snake_case.gen.dart +++ b/packages/core/test_resources/actual_data/assets_snake_case.gen.dart @@ -83,11 +83,16 @@ class Assets { } class AssetGenImage { - const AssetGenImage(this._assetName, {this.size = null}); + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); final String _assetName; final Size? size; + final Set flavors; Image image({ Key? key, diff --git a/packages/core/test_resources/actual_data/assets_svg_integrations.gen.dart b/packages/core/test_resources/actual_data/assets_svg_integrations.gen.dart index 4d9b32b11..8eedabddb 100644 --- a/packages/core/test_resources/actual_data/assets_svg_integrations.gen.dart +++ b/packages/core/test_resources/actual_data/assets_svg_integrations.gen.dart @@ -43,17 +43,19 @@ class Assets { class SvgGenImage { const SvgGenImage( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = false; const SvgGenImage.vec( this._assetName, { - this.size = null, + this.size, + this.flavors = const {}, }) : _isVecFormat = true; final String _assetName; - final Size? size; + final Set flavors; final bool _isVecFormat; SvgPicture svg({ @@ -76,12 +78,23 @@ class SvgGenImage { @deprecated BlendMode colorBlendMode = BlendMode.srcIn, @deprecated bool cacheColorFilter = false, }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } return SvgPicture( - _isVecFormat - ? AssetBytesLoader(_assetName, - assetBundle: bundle, packageName: package) - : SvgAssetLoader(_assetName, - assetBundle: bundle, packageName: package, theme: theme), + loader, key: key, matchTextDirection: matchTextDirection, width: width, diff --git a/packages/core/test_resources/pubspec_assets_flavored.yaml b/packages/core/test_resources/pubspec_assets_flavored.yaml new file mode 100644 index 000000000..aa185e4a0 --- /dev/null +++ b/packages/core/test_resources/pubspec_assets_flavored.yaml @@ -0,0 +1,28 @@ +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/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 + - path: assets/images/chip4/ + flavors: + - test diff --git a/packages/core/test_resources/pubspec_assets_flavored_duplicate_entry.yaml b/packages/core/test_resources/pubspec_assets_flavored_duplicate_entry.yaml new file mode 100644 index 000000000..53a4af6e9 --- /dev/null +++ b/packages/core/test_resources/pubspec_assets_flavored_duplicate_entry.yaml @@ -0,0 +1,31 @@ +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/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 + - path: assets/images/chip4/ + flavors: + - test + - path: assets/images/chip4/ + flavors: + - another_test diff --git a/packages/runner/lib/flutter_gen_runner.dart b/packages/runner/lib/flutter_gen_runner.dart index e3b047ec4..47c085fdb 100644 --- a/packages/runner/lib/flutter_gen_runner.dart +++ b/packages/runner/lib/flutter_gen_runner.dart @@ -6,8 +6,10 @@ import 'package:collection/collection.dart'; import 'package:crypto/crypto.dart'; import 'package:flutter_gen_core/flutter_generator.dart'; import 'package:flutter_gen_core/settings/config.dart'; +import 'package:flutter_gen_core/settings/flavored_asset.dart'; import 'package:glob/glob.dart'; import 'package:path/path.dart'; +import 'package:yaml/yaml.dart'; Builder build(BuilderOptions options) => FlutterGenBuilder(); @@ -60,13 +62,30 @@ class FlutterGenBuilder extends Builder { ) async { final pubspec = config.pubspec; - final HashSet assets = HashSet(); + final HashSet assets = HashSet(); if (pubspec.flutterGen.assets.enabled) { - for (var assetInput in pubspec.flutter.assets) { - if (assetInput.isEmpty) continue; - if (assetInput.endsWith('/')) assetInput += '*'; + for (final asset in pubspec.flutter.assets) { + final FlavoredAsset flavored; + String assetInput; + if (asset is YamlMap) { + flavored = FlavoredAsset( + path: asset['path'], + flavors: + (asset['flavors'] as YamlList?)?.toSet().cast() ?? {}, + ); + assetInput = asset['path']; + } else { + flavored = FlavoredAsset(path: asset as String); + assetInput = asset; + } + if (assetInput.isEmpty) { + continue; + } + if (assetInput.endsWith('/')) { + assetInput += '*'; + } await for (final assetId in buildStep.findAssets(Glob(assetInput))) { - assets.add(assetId.path); + assets.add(flavored.copyWith(path: assetId.path)); } } } @@ -96,16 +115,16 @@ class FlutterGenBuilder extends Builder { } class _FlutterGenBuilderState { - final Digest pubspecDigest; - final HashSet assets; - final HashMap colors; - - _FlutterGenBuilderState({ + const _FlutterGenBuilderState({ required this.pubspecDigest, required this.assets, required this.colors, }); + final Digest pubspecDigest; + final HashSet assets; + final HashMap colors; + bool shouldSkipGenerate(_FlutterGenBuilderState? previous) { if (previous == null) return false; return pubspecDigest == previous.pubspecDigest && diff --git a/packages/runner/pubspec.yaml b/packages/runner/pubspec.yaml index e35371b03..fa12a4777 100644 --- a/packages/runner/pubspec.yaml +++ b/packages/runner/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: crypto: ^3.0.0 glob: ^2.0.0 path: ^1.8.0 + yaml: ^3.0.0 dev_dependencies: lints: any # Ignoring the version to allow editing across SDK versions.