diff --git a/third_party/packages/flutter_svg/CHANGELOG.md b/third_party/packages/flutter_svg/CHANGELOG.md index 72f941829e55..4c8e2ba81776 100644 --- a/third_party/packages/flutter_svg/CHANGELOG.md +++ b/third_party/packages/flutter_svg/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.3.0 + +* Adds an `imageBuilder` property to `SvgPicture` for wrapping the loaded SVG + widget. + ## 2.2.4 * Updates README with example to scale SVG without losing quality diff --git a/third_party/packages/flutter_svg/lib/svg.dart b/third_party/packages/flutter_svg/lib/svg.dart index 1b07cce42d8b..cd059118eaee 100644 --- a/third_party/packages/flutter_svg/lib/svg.dart +++ b/third_party/packages/flutter_svg/lib/svg.dart @@ -21,6 +21,10 @@ export 'src/loaders.dart'; typedef SvgErrorWidgetBuilder = Widget Function(BuildContext context, Object error, StackTrace stackTrace); +/// Builder function to wrap the successfully loaded SVG widget. +typedef SvgImageWidgetBuilder = + Widget Function(BuildContext context, Widget child); + /// Instance for [Svg]'s utility methods, which can produce a [DrawableRoot] /// or [PictureInfo] from [String] or [Uint8List]. final Svg svg = Svg._(); @@ -92,6 +96,7 @@ class SvgPicture extends StatelessWidget { this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, this.errorBuilder, + this.imageBuilder, @Deprecated( 'No code should use this parameter. It never was implemented properly. ' 'The SVG theme must be set on the bytesLoader.', @@ -193,6 +198,7 @@ class SvgPicture extends StatelessWidget { this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, this.errorBuilder, + this.imageBuilder, SvgTheme? theme, ColorMapper? colorMapper, ui.ColorFilter? colorFilter, @@ -261,6 +267,7 @@ class SvgPicture extends StatelessWidget { this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, this.errorBuilder, + this.imageBuilder, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, SvgTheme? theme, ColorMapper? colorMapper, @@ -323,6 +330,7 @@ class SvgPicture extends StatelessWidget { this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, this.errorBuilder, + this.imageBuilder, SvgTheme? theme, ColorMapper? colorMapper, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, @@ -379,6 +387,7 @@ class SvgPicture extends StatelessWidget { this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, this.errorBuilder, + this.imageBuilder, SvgTheme? theme, ColorMapper? colorMapper, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, @@ -435,6 +444,7 @@ class SvgPicture extends StatelessWidget { this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, this.errorBuilder, + this.imageBuilder, SvgTheme? theme, ColorMapper? colorMapper, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, @@ -528,6 +538,14 @@ class SvgPicture extends StatelessWidget { /// Widget displayed while the target image failed loading. final SvgErrorWidgetBuilder? errorBuilder; + /// A builder that wraps the successfully loaded SVG widget. + /// + /// This builder is only called when the SVG has been successfully parsed and + /// is ready to be painted. It can be used to apply decorations such as + /// borders or shadows only when the image is available, without affecting the + /// placeholder or error states. + final SvgImageWidgetBuilder? imageBuilder; + /// The color filter, if any, to apply to this widget. final ColorFilter? colorFilter; @@ -551,6 +569,7 @@ class SvgPicture extends StatelessWidget { excludeFromSemantics: excludeFromSemantics, clipBehavior: clipBehavior, errorBuilder: errorBuilder, + imageBuilder: imageBuilder, colorFilter: colorFilter, placeholderBuilder: placeholderBuilder, strategy: renderingStrategy, diff --git a/third_party/packages/flutter_svg/pubspec.yaml b/third_party/packages/flutter_svg/pubspec.yaml index 60d9d19d4e01..f6300db9821e 100644 --- a/third_party/packages/flutter_svg/pubspec.yaml +++ b/third_party/packages/flutter_svg/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_svg description: An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files. repository: https://github.com/flutter/packages/tree/main/third_party/packages/flutter_svg issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_svg%22 -version: 2.2.4 +version: 2.3.0 environment: sdk: ^3.9.0 @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter http: ^1.0.0 - vector_graphics: ^1.1.13 + vector_graphics: ^1.2.0 vector_graphics_codec: ^1.1.11+1 vector_graphics_compiler: ^1.1.14 diff --git a/third_party/packages/flutter_svg/test/widget_svg_test.dart b/third_party/packages/flutter_svg/test/widget_svg_test.dart index 358f3ba11f51..8d4b9c755de6 100644 --- a/third_party/packages/flutter_svg/test/widget_svg_test.dart +++ b/third_party/packages/flutter_svg/test/widget_svg_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -981,6 +982,66 @@ void main() { } }); + group('SvgPicture - imageBuilder', () { + testWidgets('wraps successfully loaded SVG', (WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: mediaQueryData, + child: SvgPicture.string( + simpleSvg, + imageBuilder: (BuildContext context, Widget child) { + return Container( + key: const ValueKey('image-builder'), + child: child, + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect( + find.byKey(const ValueKey('image-builder')), + findsOneWidget, + ); + }); + + testWidgets('does not wrap placeholder state', (WidgetTester tester) async { + final response = Completer(); + + await tester.pumpWidget( + MediaQuery( + data: mediaQueryData, + child: SvgPicture.network( + 'test.svg', + httpClient: DelayedHttpClient(response.future), + imageBuilder: (BuildContext context, Widget child) { + return Container( + key: const ValueKey('image-builder'), + child: child, + ); + }, + placeholderBuilder: (BuildContext context) { + return Container(key: const ValueKey('placeholder')); + }, + ), + ), + ); + + expect(find.byKey(const ValueKey('placeholder')), findsOneWidget); + expect(find.byKey(const ValueKey('image-builder')), findsNothing); + + response.complete(http.Response(svgStr, 200)); + await tester.pumpAndSettle(); + + expect( + find.byKey(const ValueKey('image-builder')), + findsOneWidget, + ); + expect(find.byKey(const ValueKey('placeholder')), findsNothing); + }); + }); + group('SvgPicture - errorBuilder', () { testWidgets('SvgPicture.string handles failure', ( WidgetTester tester, @@ -1102,6 +1163,17 @@ class FakeHttpClient extends Fake implements http.Client { } } +class DelayedHttpClient extends Fake implements http.Client { + DelayedHttpClient(this.response); + + final Future response; + + @override + Future get(Uri url, {Map? headers}) { + return response; + } +} + const String simpleSvg = '''