Repository: FlutterGen/flutter_gen

Issue:
Support path method to an asset folder
For example I have the next settings:
```
# pubspec.yaml
flutter:
  assets:
    - assets/images/profile.jpg
```

And the library generates path to the profile image. 
```
Widget build(BuildContext context) {
  // Assets.images.profile.path = 'assets/images/profile.jpg'
  return Image.asset(Assets.images.profile.path);
}
```

But sometime I need to write a specific code and have access to the folder `assets/images`. Could you support the logic?
```
var imagesPath = Assets.images.path;
// do some magic in the folder
```


Discussion: Same here. Working on a project (online game) which is split into mobile app and backend API. The mobile app keeps all the image assets, so for the player avatars I have:

```
...
AssetGenImage get femaleAristocrat =>
      const AssetGenImage('assets/images/players/female_aristocrat.png');
  AssetGenImage get femaleCommander =>
      const AssetGenImage('assets/images/players/female_commander.png');
...
```

but the API just returns the player avatar as `female_aristocrat`. Would be great to have some way to get the part `assets/images/players/` somehow, without hardcoding it in the app.

So instead of calling:

```
String get imagePath => 'assets/images/players/$avatar.png';
```

I could make it like:

```
String get imagePath => '{${Assets.images.players.path}/$avatar.png';
```

Not a big deal, but already made a dozen of typos in these kind of paths.
Similarly [easy_localization](https://pub.dev/packages/easy_localization) only accepts a path, so I'm hardcoding the path.
Note you have to declare the translations within assets in pubspec already, so flutter_gen is picking it up regardless - it's just not accessible since only the values for the files: `en-us` etc are available - but not helpful in this instance.

```dart
 EasyLocalization(
    supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')],
    path: 'assets/translations', // <-- change the path of the translation files 
    fallbackLocale: Locale('en', 'US'),
    child: MyApp()
)
```

Fix patch:
diff --git a/README.md b/README.md
index 054e1e464..8efbf744a 100644
--- a/README.md
+++ b/README.md
@@ -217,6 +217,31 @@ Widget build(BuildContext context) {
 }
 ```
 
+#### Generate directories path
+
+If you want to generate the path of directories,
+use `directory_path_enabled` under `flutter_gen > assets > outputs`.
+
+```yaml
+flutter_gen:
+  assets:
+    outputs:
+      directory_path_enabled: true # <- Add this line.
+```
+
+This would add the `path` getter to the generated directory class. For example:
+
+```dart
+class $AssetsImagesGen {
+  const $AssetsImagesGen();
+
+  ///******///
+
+  /// Directory path: assets/images
+  String get path => 'assets/images';
+}
+```
+
 #### Including additional metadata
 
 At build time, additional metadata may be included in the generated class, by using the
diff --git a/examples/example/lib/gen/assets.gen.dart b/examples/example/lib/gen/assets.gen.dart
index b28831623..b4c18d758 100644
--- a/examples/example/lib/gen/assets.gen.dart
+++ b/examples/example/lib/gen/assets.gen.dart
@@ -34,7 +34,10 @@ class $AssetsImagesGen {
   /// File path: assets/images/chip2.jpg
   AssetGenImage get chip2 => const AssetGenImage('assets/images/chip2.jpg');
 
+  /// 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
@@ -81,6 +84,7 @@ class $AssetsLottieGen {
   LottieGenImage get hamburgerArrow =>
       const LottieGenImage('assets/lottie/hamburger_arrow.json');
 
+  /// Directory path: assets/lottie/wrong
   $AssetsLottieWrongGen get wrong => const $AssetsLottieWrongGen();
 
   /// List of all assets
diff --git a/packages/core/lib/generators/assets_generator.dart b/packages/core/lib/generators/assets_generator.dart
index 2c4667ddf..60d638aec 100644
--- a/packages/core/lib/generators/assets_generator.dart
+++ b/packages/core/lib/generators/assets_generator.dart
@@ -361,7 +361,15 @@ String _dotDelimiterStyleDefinition(
         );
       } else {
         final className = '\$${assetType.path.camelCase().capitalize()}Gen';
-        buffer.writeln(_directoryClassGenDefinition(className, statements));
+        buffer.writeln(
+          _directoryClassGenDefinition(
+            className,
+            statements,
+            config.flutterGen.assets.outputs.directoryPathEnabled
+                ? assetType.posixStylePath
+                : null,
+          ),
+        );
         // Add this directory reference to Assets class
         // if we are not under the default asset folder
         if (dirname(assetType.path) == '.') {
@@ -516,14 +524,22 @@ ${packageName != null ? "\n  static const String package = '$packageName';" : ''
 String _directoryClassGenDefinition(
   String className,
   List<_Statement> statements,
+  String? directoryPath,
 ) {
-  final statementsBlock = statements
-      .map((statement) => statement.needDartDoc
-          ? '''${statement.toDartDocString()}
-          ${statement.toGetterString()}
-          '''
-          : statement.toGetterString())
-      .join('\n');
+  final statementsBlock = statements.map((statement) {
+    final buffer = StringBuffer();
+    if (statement.needDartDoc) {
+      buffer.writeln(statement.toDartDocString());
+    }
+    buffer.writeln(statement.toGetterString());
+    return buffer.toString();
+  }).join('\n');
+  final pathBlock = directoryPath != null
+      ? '''
+  /// Directory path: $directoryPath
+  String get path => '$directoryPath';
+'''
+      : '';
   final valuesBlock = _assetValuesDefinition(statements);
 
   return '''
@@ -531,6 +547,7 @@ class $className {
   const $className();
   
   $statementsBlock
+  $pathBlock
   $valuesBlock
 }
 ''';
@@ -567,8 +584,19 @@ class _Statement {
 
   String toDartDocString() => '/// File path: $filePath';
 
-  String toGetterString() =>
-      '$type get $name => ${isConstConstructor ? 'const' : ''} $value;';
+  String toGetterString() {
+    final buffer = StringBuffer('');
+    if (isDirectory) {
+      buffer.writeln(
+        '/// Directory path: '
+        '${Directory(filePath).path.replaceAll(r'\', r'/')}',
+      );
+    }
+    buffer.writeln(
+      '$type get $name => ${isConstConstructor ? 'const' : ''} $value;',
+    );
+    return buffer.toString();
+  }
 
   String toStaticFieldString() => 'static const $type $name = $value;';
 }
diff --git a/packages/core/lib/settings/pubspec.dart b/packages/core/lib/settings/pubspec.dart
index 25eac96e1..f2592a4e7 100644
--- a/packages/core/lib/settings/pubspec.dart
+++ b/packages/core/lib/settings/pubspec.dart
@@ -201,6 +201,7 @@ class FlutterGenElementAssetsOutputs extends FlutterGenElementOutputs {
   FlutterGenElementAssetsOutputs({
     required String className,
     required this.packageParameterEnabled,
+    required this.directoryPathEnabled,
     required this.style,
   }) : super(className: className) {
     if (style != dotDelimiterStyle &&
@@ -210,9 +211,12 @@ class FlutterGenElementAssetsOutputs extends FlutterGenElementOutputs {
     }
   }
 
-  @JsonKey(name: 'package_parameter_enabled', required: true)
+  @JsonKey(name: 'package_parameter_enabled', defaultValue: false)
   final bool packageParameterEnabled;
 
+  @JsonKey(name: 'directory_path_enabled', defaultValue: false)
+  final bool directoryPathEnabled;
+
   @JsonKey(name: 'style', required: true)
   final String style;
 
diff --git a/packages/core/lib/settings/pubspec.g.dart b/packages/core/lib/settings/pubspec.g.dart
index 453ea9d27..9945fcdb5 100644
--- a/packages/core/lib/settings/pubspec.g.dart
+++ b/packages/core/lib/settings/pubspec.g.dart
@@ -213,22 +213,21 @@ FlutterGenElementAssetsOutputs _$FlutterGenElementAssetsOutputsFromJson(
       ($checkedConvert) {
         $checkKeys(
           json,
-          requiredKeys: const [
-            'class_name',
-            'package_parameter_enabled',
-            'style'
-          ],
+          requiredKeys: const ['class_name', 'style'],
         );
         final val = FlutterGenElementAssetsOutputs(
           className: $checkedConvert('class_name', (v) => v as String),
-          packageParameterEnabled:
-              $checkedConvert('package_parameter_enabled', (v) => v as bool),
+          packageParameterEnabled: $checkedConvert(
+              'package_parameter_enabled', (v) => v as bool? ?? false),
+          directoryPathEnabled: $checkedConvert(
+              'directory_path_enabled', (v) => v as bool? ?? false),
           style: $checkedConvert('style', (v) => v as String),
         );
         return val;
       },
       fieldKeyMap: const {
         'className': 'class_name',
-        'packageParameterEnabled': 'package_parameter_enabled'
+        'packageParameterEnabled': 'package_parameter_enabled',
+        'directoryPathEnabled': 'directory_path_enabled'
       },
     );


Test patch:
diff --git a/packages/core/test/assets_gen_test.dart b/packages/core/test/assets_gen_test.dart
index 7964e7c40..4f3315d22 100644
--- a/packages/core/test/assets_gen_test.dart
+++ b/packages/core/test/assets_gen_test.dart
@@ -92,6 +92,13 @@ void main() {
       );
     });
 
+    test('Assets with directory path enabled', () async {
+      const pubspec = 'test_resources/pubspec_assets_directory_path.yaml';
+      const fact = 'test_resources/actual_data/assets_directory_path.gen.dart';
+      const generated = 'test_resources/lib/gen/assets_directory_path.gen.dart';
+      await expectedAssetsGen(pubspec, generated, fact);
+    });
+
     test('Assets with excluded files and directories', () async {
       const pubspec = 'test_resources/pubspec_assets_exclude_files.yaml';
       const fact =
diff --git a/packages/core/test_resources/actual_data/assets.gen.dart b/packages/core/test_resources/actual_data/assets.gen.dart
index 6e149848f..c9c132717 100644
--- a/packages/core/test_resources/actual_data/assets.gen.dart
+++ b/packages/core/test_resources/actual_data/assets.gen.dart
@@ -42,8 +42,13 @@ 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();
+
+  /// Directory path: assets/images/icons
   $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen();
 
   /// File path: assets/images/logo.png
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
new file mode 100644
index 000000000..2ba0e64af
--- /dev/null
+++ b/packages/core/test_resources/actual_data/assets_directory_path.gen.dart
@@ -0,0 +1,208 @@
+/// 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_svg/flutter_svg.dart';
+import 'package:flutter/services.dart';
+
+class $AssetsImagesGen {
+  const $AssetsImagesGen();
+
+  /// Directory path: assets/images/chip3
+  $AssetsImagesChip3Gen get chip3 => const $AssetsImagesChip3Gen();
+
+  /// Directory path: assets/images/icons
+  $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen();
+
+  /// Directory path: assets/images
+  String get path => 'assets/images';
+}
+
+class $AssetsUnknownGen {
+  const $AssetsUnknownGen();
+
+  /// File path: assets/unknown/unknown_mime_type.bk
+  String get unknownMimeType => 'assets/unknown/unknown_mime_type.bk';
+
+  /// Directory path: assets/unknown
+  String get path => 'assets/unknown';
+
+  /// List of all assets
+  List<String> get values => [unknownMimeType];
+}
+
+class $AssetsImagesChip3Gen {
+  const $AssetsImagesChip3Gen();
+
+  /// File path: assets/images/chip3/chip3.jpg
+  AssetGenImage get chip3 =>
+      const AssetGenImage('assets/images/chip3/chip3.jpg');
+
+  /// Directory path: assets/images/chip3
+  String get path => 'assets/images/chip3';
+
+  /// List of all assets
+  List<AssetGenImage> get values => [chip3];
+}
+
+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');
+
+  /// Directory path: assets/images/icons
+  String get path => 'assets/images/icons';
+
+  /// List of all assets
+  List<SvgGenImage> get values => [dartTest, fuchsia];
+}
+
+class Assets {
+  Assets._();
+
+  static const $AssetsImagesGen images = $AssetsImagesGen();
+  static const $AssetsUnknownGen unknown = $AssetsUnknownGen();
+}
+
+class AssetGenImage {
+  const AssetGenImage(this._assetName, {this.size = null});
+
+  final String _assetName;
+
+  final Size? size;
+
+  Image image({
+    Key? key,
+    AssetBundle? bundle,
+    ImageFrameBuilder? frameBuilder,
+    ImageErrorWidgetBuilder? errorBuilder,
+    String? semanticLabel,
+    bool excludeFromSemantics = false,
+    double? scale,
+    double? width,
+    double? height,
+    Color? color,
+    Animation<double>? 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 = null});
+
+  final String _assetName;
+
+  final Size? size;
+
+  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,
+  }) {
+    return SvgPicture.asset(
+      _assetName,
+      key: key,
+      matchTextDirection: matchTextDirection,
+      bundle: bundle,
+      package: package,
+      width: width,
+      height: height,
+      fit: fit,
+      alignment: alignment,
+      allowDrawingOutsideViewBox: allowDrawingOutsideViewBox,
+      placeholderBuilder: placeholderBuilder,
+      semanticsLabel: semanticsLabel,
+      excludeFromSemantics: excludeFromSemantics,
+      theme: theme,
+      colorFilter: colorFilter,
+      color: color,
+      colorBlendMode: colorBlendMode,
+      clipBehavior: clipBehavior,
+      cacheColorFilter: cacheColorFilter,
+    );
+  }
+
+  String get path => _assetName;
+
+  String get keyName => _assetName;
+}
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 ce894e0f0..722ecdc7c 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
@@ -28,8 +28,13 @@ 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();
+
+  /// Directory path: assets/images/icons
   $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen();
 
   /// File path: assets/images/logo.png
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 7fb0ff0b6..d60634822 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
@@ -28,6 +28,7 @@ class $AssetsImagesGen {
   /// File path: assets/images/chip2.jpg
   AssetGenImage get chip2 => const AssetGenImage('assets/images/chip2.jpg');
 
+  /// Directory path: assets/images/chip4
   $AssetsImagesChip4Gen get chip4 => const $AssetsImagesChip4Gen();
 
   /// File path: assets/images/profile.jpg
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 6cbb1084f..136ed7cd3 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
@@ -14,7 +14,10 @@ import 'package:flutter/services.dart';
 class $AssetsImagesGen {
   const $AssetsImagesGen();
 
+  /// Directory path: assets/images/chip3
   $AssetsImagesChip3Gen get chip3 => const $AssetsImagesChip3Gen();
+
+  /// Directory path: assets/images/icons
   $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen();
 }
 
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 4a1269d6a..6de76878b 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
@@ -44,8 +44,13 @@ 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();
+
+  /// Directory path: assets/images/icons
   $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen();
 
   /// File path: assets/images/logo.png
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 4038ac0b2..f7a694db4 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
@@ -14,6 +14,7 @@ import 'package:flutter/services.dart';
 class $AssetsImagesGen {
   const $AssetsImagesGen();
 
+  /// Directory path: assets/images/icons
   $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen();
 }
 
diff --git a/packages/core/test_resources/pubspec_assets_directory_path.yaml b/packages/core/test_resources/pubspec_assets_directory_path.yaml
new file mode 100644
index 000000000..48c35404f
--- /dev/null
+++ b/packages/core/test_resources/pubspec_assets_directory_path.yaml
@@ -0,0 +1,15 @@
+name: test
+
+flutter_gen:
+  integrations:
+    flutter_svg: true
+  assets:
+    outputs:
+      directory_path_enabled: true
+
+flutter:
+  assets:
+    - assets/images/chip3/chip3.jpg
+    - assets/images/icons/dart@test.svg
+    - assets/images/icons/fuchsia.svg
+    - assets/unknown/unknown_mime_type.bk