Skip to content

Commit 7f1e056

Browse files
committed
add new option to parse metadata for animated images
1 parent fd63623 commit 7f1e056

35 files changed

+847
-492
lines changed

packages/command/pubspec.yaml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_gen
22
description: The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
3-
version: 5.10.0
3+
version: 5.11.0
44
homepage: https://github.com/FlutterGen/flutter_gen
55
repository: https://github.com/FlutterGen/flutter_gen
66
documentation: https://github.com/FlutterGen/flutter_gen
@@ -13,7 +13,8 @@ executables:
1313
fluttergen: flutter_gen_command
1414

1515
dependencies:
16-
flutter_gen_core: 5.10.0
16+
flutter_gen_core: 5.11.0
17+
1718
args: ^2.0.0
1819

1920
dev_dependencies:

packages/core/lib/generators/assets_generator.dart

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Future<String> generateAssets(
6363
ImageIntegration(
6464
config.packageParameterLiteral,
6565
parseMetadata: config.flutterGen.parseMetadata,
66+
parseAnimation: config.flutterGen.parseAnimation,
6667
),
6768
if (config.flutterGen.integrations.flutterSvg)
6869
SvgIntegration(

packages/core/lib/generators/integrations/image_integration.dart

+57-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:io';
22

33
import 'package:flutter_gen_core/generators/integrations/integration.dart';
4+
import 'package:image/image.dart' as img;
45
import 'package:image_size_getter/file_input.dart';
56
import 'package:image_size_getter/image_size_getter.dart';
67

@@ -11,9 +12,12 @@ import 'package:image_size_getter/image_size_getter.dart';
1112
class ImageIntegration extends Integration {
1213
ImageIntegration(
1314
String packageName, {
15+
required this.parseAnimation,
1416
super.parseMetadata,
1517
}) : super(packageName);
1618

19+
final bool parseAnimation;
20+
1721
String get packageParameter => isPackage ? ' = package' : '';
1822

1923
String get keyName =>
@@ -32,6 +36,9 @@ class ImageIntegration extends Integration {
3236
this._assetName, {
3337
this.size,
3438
this.flavors = const {},
39+
this.isAnimation = false,
40+
this.duration = Duration.zero,
41+
this.frames = 1,
3542
});
3643
3744
final String _assetName;
@@ -40,6 +47,9 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}
4047
4148
final Size? size;
4249
final Set<String> flavors;
50+
final bool isAnimation;
51+
final Duration duration;
52+
final int frames;
4353
4454
Image image({
4555
Key? key,
@@ -116,12 +126,20 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}
116126

117127
@override
118128
String classInstantiate(AssetType asset) {
119-
final info = parseMetadata ? _getMetadata(asset) : null;
129+
final info = parseMetadata || parseAnimation ? _getMetadata(asset) : null;
120130
final buffer = StringBuffer(className);
121131
buffer.write('(');
122132
buffer.write('\'${asset.posixStylePath}\'');
123133
if (info != null) {
124-
buffer.write(', size: Size(${info.width}, ${info.height})');
134+
buffer.write(', size: const Size(${info.width}, ${info.height})');
135+
136+
if (info.animation case final animation?) {
137+
buffer.write(', isAnimation: ${animation.frames > 1}');
138+
buffer.write(
139+
', duration: const Duration(milliseconds: ${animation.duration.inMilliseconds})',
140+
);
141+
buffer.write(', frames: ${animation.frames}');
142+
}
125143
}
126144
if (asset.flavors.isNotEmpty) {
127145
buffer.write(', flavors: {');
@@ -161,11 +179,47 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}
161179
FileInput(File(asset.fullPath)),
162180
);
163181
final size = result.size;
164-
return ImageMetadata(size.width.toDouble(), size.height.toDouble());
182+
final animation = parseAnimation ? _parseAnimation(asset) : null;
183+
184+
return ImageMetadata(
185+
width: size.width.toDouble(),
186+
height: size.height.toDouble(),
187+
animation: animation,
188+
);
165189
} catch (e) {
166190
stderr
167191
.writeln('[WARNING] Failed to parse \'${asset.path}\' metadata: $e');
168192
}
169193
return null;
170194
}
195+
196+
ImageAnimation? _parseAnimation(AssetType asset) {
197+
final decoder = switch (asset.mime) {
198+
'image/gif' => img.GifDecoder(),
199+
'image/webp' => img.WebPDecoder(),
200+
_ => null,
201+
};
202+
203+
if (decoder == null) {
204+
return null;
205+
}
206+
207+
final file = File(asset.fullPath);
208+
final bytes = file.readAsBytesSync();
209+
final image = decoder.decode(bytes);
210+
211+
if (image == null) {
212+
return null;
213+
}
214+
215+
return ImageAnimation(
216+
frames: image.frames.length,
217+
duration: Duration(
218+
milliseconds: image.frames.fold(
219+
0,
220+
(duration, frame) => duration + frame.frameDuration,
221+
),
222+
),
223+
);
224+
}
171225
}

packages/core/lib/generators/integrations/integration.dart

+17-1
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,24 @@ const String deprecationMessagePackage =
5555
/// Currently only contains the width and height, but could contain more in
5656
/// future.
5757
class ImageMetadata {
58-
const ImageMetadata(this.width, this.height);
58+
const ImageMetadata({
59+
required this.width,
60+
required this.height,
61+
this.animation,
62+
});
5963

6064
final double width;
6165
final double height;
66+
final ImageAnimation? animation;
67+
}
68+
69+
/// Metadata about the parsed animation file when [parseAnimation] is true.
70+
class ImageAnimation {
71+
const ImageAnimation({
72+
required this.frames,
73+
required this.duration,
74+
});
75+
76+
final int frames;
77+
final Duration duration;
6278
}

packages/core/lib/generators/integrations/svg_integration.dart

+4-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}
132132
// but it's also the same way it will be eventually rendered by Flutter.
133133
final svg = File(asset.fullPath).readAsStringSync();
134134
final vec = parseWithoutOptimizers(svg);
135-
return ImageMetadata(vec.width, vec.height);
135+
return ImageMetadata(
136+
width: vec.width,
137+
height: vec.height,
138+
);
136139
} catch (e) {
137140
stderr.writeln(
138141
'[WARNING] Failed to parse SVG \'${asset.path}\' metadata: $e',

packages/core/lib/settings/config_default.dart

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ flutter_gen:
88
line_length: 80
99
# Optional
1010
parse_metadata: false
11+
# Optional
12+
parse_animation: false
1113
1214
# Optional
1315
integrations:

packages/core/lib/settings/pubspec.dart

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class FlutterGen {
5656
required this.output,
5757
required this.lineLength,
5858
required this.parseMetadata,
59+
required this.parseAnimation,
5960
required this.assets,
6061
required this.fonts,
6162
required this.integrations,
@@ -73,6 +74,9 @@ class FlutterGen {
7374
@JsonKey(name: 'parse_metadata', required: true)
7475
final bool parseMetadata;
7576

77+
@JsonKey(name: 'parse_animation', required: true)
78+
final bool parseAnimation;
79+
7680
@JsonKey(name: 'assets', required: true)
7781
final FlutterGenAssets assets;
7882

packages/core/lib/settings/pubspec.g.dart

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/lib/version.gen.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/// DO NOT MODIFY BY HAND, Generated by version_gen
2-
String packageVersion = '5.10.0';
2+
String packageVersion = '5.11.0';

packages/core/pubspec.yaml

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_gen_core
22
description: The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
3-
version: 5.10.0
3+
version: 5.11.0
44
homepage: https://github.com/FlutterGen/flutter_gen
55
repository: https://github.com/FlutterGen/flutter_gen
66
documentation: https://github.com/FlutterGen/flutter_gen
@@ -16,20 +16,21 @@ dependencies:
1616
meta: ^1.7.0
1717
path: ^1.8.0
1818
yaml: ^3.0.0
19-
mime: '>=1.0.0 <3.0.0'
19+
mime: ">=1.0.0 <3.0.0"
2020
xml: ^6.0.0
2121
dartx: ^1.0.0
2222
color: ^3.0.0
2323
collection: ^1.15.0
2424
json_annotation: ^4.4.0
2525
glob: ^2.0.0
2626

27-
dart_style: '>=2.3.7 <4.0.0'
28-
archive: '>=3.4.0 <5.0.0'
27+
dart_style: ">=2.3.7 <4.0.0"
28+
archive: ">=3.4.0 <5.0.0"
2929
args: ^2.0.0
3030
pub_semver: ^2.0.0
3131
vector_graphics_compiler: ^1.1.9
3232
image_size_getter: ^2.4.0
33+
image: ^4.5.4
3334

3435
dev_dependencies:
3536
lints: any # Ignoring the version to allow editing across SDK versions.

packages/core/test/assets_gen_test.dart

+2-4
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,8 @@ void main() {
117117

118118
test('Assets with excluded files and directories', () async {
119119
const pubspec = 'test_resources/pubspec_assets_exclude_files.yaml';
120-
const fact =
121-
'test_resources/actual_data/assets_package_exclude_files.gen.dart';
122-
const generated =
123-
'test_resources/lib/gen/assets_package_exclude_files.gen.dart';
120+
const fact = 'test_resources/actual_data/assets_exclude_files.gen.dart';
121+
const generated = 'test_resources/lib/gen/assets_exclude_files.gen.dart';
124122

125123
await expectedAssetsGen(pubspec, generated, fact);
126124
});

packages/core/test_resources/actual_data/assets.gen.dart

+21-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)