Unverified Commit ed67c479 authored by Dan Field's avatar Dan Field Committed by GitHub

Add error callbacks to other image resolving code (#53329)

parent 0d111bc9
...@@ -17,6 +17,9 @@ import 'theme_data.dart'; ...@@ -17,6 +17,9 @@ import 'theme_data.dart';
/// such an image, the user's initials. A given user's initials should /// such an image, the user's initials. A given user's initials should
/// always be paired with the same background color, for consistency. /// always be paired with the same background color, for consistency.
/// ///
/// The [onBackgroundImageError] parameter must be null if the [backgroundImage]
/// is null.
///
/// {@tool snippet} /// {@tool snippet}
/// ///
/// If the avatar is to have an image, the image should be specified in the /// If the avatar is to have an image, the image should be specified in the
...@@ -57,11 +60,13 @@ class CircleAvatar extends StatelessWidget { ...@@ -57,11 +60,13 @@ class CircleAvatar extends StatelessWidget {
this.child, this.child,
this.backgroundColor, this.backgroundColor,
this.backgroundImage, this.backgroundImage,
this.onBackgroundImageError,
this.foregroundColor, this.foregroundColor,
this.radius, this.radius,
this.minRadius, this.minRadius,
this.maxRadius, this.maxRadius,
}) : assert(radius == null || (minRadius == null && maxRadius == null)), }) : assert(radius == null || (minRadius == null && maxRadius == null)),
assert(backgroundImage != null || onBackgroundImageError == null),
super(key: key); super(key: key);
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
...@@ -93,6 +98,10 @@ class CircleAvatar extends StatelessWidget { ...@@ -93,6 +98,10 @@ class CircleAvatar extends StatelessWidget {
/// If the [CircleAvatar] is to have the user's initials, use [child] instead. /// If the [CircleAvatar] is to have the user's initials, use [child] instead.
final ImageProvider backgroundImage; final ImageProvider backgroundImage;
/// An optional error callback for errors emitted when loading
/// [backgroundImage].
final ImageErrorListener onBackgroundImageError;
/// The size of the avatar, expressed as the radius (half the diameter). /// The size of the avatar, expressed as the radius (half the diameter).
/// ///
/// If [radius] is specified, then neither [minRadius] nor [maxRadius] may be /// If [radius] is specified, then neither [minRadius] nor [maxRadius] may be
...@@ -200,7 +209,11 @@ class CircleAvatar extends StatelessWidget { ...@@ -200,7 +209,11 @@ class CircleAvatar extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: effectiveBackgroundColor, color: effectiveBackgroundColor,
image: backgroundImage != null image: backgroundImage != null
? DecorationImage(image: backgroundImage, fit: BoxFit.cover) ? DecorationImage(
image: backgroundImage,
onError: onBackgroundImageError,
fit: BoxFit.cover,
)
: null, : null,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
......
...@@ -145,7 +145,8 @@ class Ink extends StatefulWidget { ...@@ -145,7 +145,8 @@ class Ink extends StatefulWidget {
/// ///
/// The `image` argument must not be null. If there is no /// The `image` argument must not be null. If there is no
/// intention to render anything on this image, consider using a /// intention to render anything on this image, consider using a
/// [Container] with a [BoxDecoration.image] instead. /// [Container] with a [BoxDecoration.image] instead. The `onImageError`
/// argument may be provided to listen for errors when resolving the image.
/// ///
/// The `alignment`, `repeat`, and `matchTextDirection` arguments must not /// The `alignment`, `repeat`, and `matchTextDirection` arguments must not
/// be null either, but they have default values. /// be null either, but they have default values.
...@@ -155,6 +156,7 @@ class Ink extends StatefulWidget { ...@@ -155,6 +156,7 @@ class Ink extends StatefulWidget {
Key key, Key key,
this.padding, this.padding,
@required ImageProvider image, @required ImageProvider image,
ImageErrorListener onImageError,
ColorFilter colorFilter, ColorFilter colorFilter,
BoxFit fit, BoxFit fit,
AlignmentGeometry alignment = Alignment.center, AlignmentGeometry alignment = Alignment.center,
...@@ -172,6 +174,7 @@ class Ink extends StatefulWidget { ...@@ -172,6 +174,7 @@ class Ink extends StatefulWidget {
decoration = BoxDecoration( decoration = BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: image, image: image,
onError: onImageError,
colorFilter: colorFilter, colorFilter: colorFilter,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
......
...@@ -71,7 +71,9 @@ class Switch extends StatefulWidget { ...@@ -71,7 +71,9 @@ class Switch extends StatefulWidget {
this.inactiveThumbColor, this.inactiveThumbColor,
this.inactiveTrackColor, this.inactiveTrackColor,
this.activeThumbImage, this.activeThumbImage,
this.onActiveThumbImageError,
this.inactiveThumbImage, this.inactiveThumbImage,
this.onInactiveThumbImageError,
this.materialTapTargetSize, this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.focusColor, this.focusColor,
...@@ -80,6 +82,8 @@ class Switch extends StatefulWidget { ...@@ -80,6 +82,8 @@ class Switch extends StatefulWidget {
this.autofocus = false, this.autofocus = false,
}) : _switchType = _SwitchType.material, }) : _switchType = _SwitchType.material,
assert(dragStartBehavior != null), assert(dragStartBehavior != null),
assert(activeThumbImage != null || onActiveThumbImageError == null),
assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
super(key: key); super(key: key);
/// Creates a [CupertinoSwitch] if the target platform is iOS, creates a /// Creates a [CupertinoSwitch] if the target platform is iOS, creates a
...@@ -87,7 +91,8 @@ class Switch extends StatefulWidget { ...@@ -87,7 +91,8 @@ class Switch extends StatefulWidget {
/// ///
/// If a [CupertinoSwitch] is created, the following parameters are /// If a [CupertinoSwitch] is created, the following parameters are
/// ignored: [activeTrackColor], [inactiveThumbColor], [inactiveTrackColor], /// ignored: [activeTrackColor], [inactiveThumbColor], [inactiveTrackColor],
/// [activeThumbImage], [inactiveThumbImage], [materialTapTargetSize]. /// [activeThumbImage], [onActiveThumbImageError], [inactiveThumbImage],
/// [onInactiveImageThumbError], [materialTapTargetSize].
/// ///
/// The target platform is based on the current [Theme]: [ThemeData.platform]. /// The target platform is based on the current [Theme]: [ThemeData.platform].
const Switch.adaptive({ const Switch.adaptive({
...@@ -99,7 +104,9 @@ class Switch extends StatefulWidget { ...@@ -99,7 +104,9 @@ class Switch extends StatefulWidget {
this.inactiveThumbColor, this.inactiveThumbColor,
this.inactiveTrackColor, this.inactiveTrackColor,
this.activeThumbImage, this.activeThumbImage,
this.onActiveThumbImageError,
this.inactiveThumbImage, this.inactiveThumbImage,
this.onInactiveThumbImageError,
this.materialTapTargetSize, this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.focusColor, this.focusColor,
...@@ -107,6 +114,8 @@ class Switch extends StatefulWidget { ...@@ -107,6 +114,8 @@ class Switch extends StatefulWidget {
this.focusNode, this.focusNode,
this.autofocus = false, this.autofocus = false,
}) : assert(autofocus != null), }) : assert(autofocus != null),
assert(activeThumbImage != null || onActiveThumbImageError == null),
assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
_switchType = _SwitchType.adaptive, _switchType = _SwitchType.adaptive,
super(key: key); super(key: key);
...@@ -170,11 +179,19 @@ class Switch extends StatefulWidget { ...@@ -170,11 +179,19 @@ class Switch extends StatefulWidget {
/// Ignored if this switch is created with [Switch.adaptive]. /// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider activeThumbImage; final ImageProvider activeThumbImage;
/// An optional error callback for errors emitted when loading
/// [activeThumbImage].
final ImageErrorListener onActiveThumbImageError;
/// An image to use on the thumb of this switch when the switch is off. /// An image to use on the thumb of this switch when the switch is off.
/// ///
/// Ignored if this switch is created with [Switch.adaptive]. /// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider inactiveThumbImage; final ImageProvider inactiveThumbImage;
/// An optional error callback for errors emitted when loading
/// [inactiveThumbImage].
final ImageErrorListener onInactiveThumbImageError;
/// Configures the minimum size of the tap target. /// Configures the minimum size of the tap target.
/// ///
/// Defaults to [ThemeData.materialTapTargetSize]. /// Defaults to [ThemeData.materialTapTargetSize].
...@@ -311,7 +328,9 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -311,7 +328,9 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
hoverColor: hoverColor, hoverColor: hoverColor,
focusColor: focusColor, focusColor: focusColor,
activeThumbImage: widget.activeThumbImage, activeThumbImage: widget.activeThumbImage,
onActiveThumbImageError: widget.onActiveThumbImageError,
inactiveThumbImage: widget.inactiveThumbImage, inactiveThumbImage: widget.inactiveThumbImage,
onInactiveThumbImageError: widget.onInactiveThumbImageError,
activeTrackColor: activeTrackColor, activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor, inactiveTrackColor: inactiveTrackColor,
configuration: createLocalImageConfiguration(context), configuration: createLocalImageConfiguration(context),
...@@ -381,7 +400,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -381,7 +400,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
this.hoverColor, this.hoverColor,
this.focusColor, this.focusColor,
this.activeThumbImage, this.activeThumbImage,
this.onActiveThumbImageError,
this.inactiveThumbImage, this.inactiveThumbImage,
this.onInactiveThumbImageError,
this.activeTrackColor, this.activeTrackColor,
this.inactiveTrackColor, this.inactiveTrackColor,
this.configuration, this.configuration,
...@@ -399,7 +420,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -399,7 +420,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
final Color hoverColor; final Color hoverColor;
final Color focusColor; final Color focusColor;
final ImageProvider activeThumbImage; final ImageProvider activeThumbImage;
final ImageErrorListener onActiveThumbImageError;
final ImageProvider inactiveThumbImage; final ImageProvider inactiveThumbImage;
final ImageErrorListener onInactiveThumbImageError;
final Color activeTrackColor; final Color activeTrackColor;
final Color inactiveTrackColor; final Color inactiveTrackColor;
final ImageConfiguration configuration; final ImageConfiguration configuration;
...@@ -420,7 +443,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -420,7 +443,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
hoverColor: hoverColor, hoverColor: hoverColor,
focusColor: focusColor, focusColor: focusColor,
activeThumbImage: activeThumbImage, activeThumbImage: activeThumbImage,
onActiveThumbImageError: onActiveThumbImageError,
inactiveThumbImage: inactiveThumbImage, inactiveThumbImage: inactiveThumbImage,
onInactiveThumbImageError: onInactiveThumbImageError,
activeTrackColor: activeTrackColor, activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor, inactiveTrackColor: inactiveTrackColor,
configuration: configuration, configuration: configuration,
...@@ -442,7 +467,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -442,7 +467,9 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
..hoverColor = hoverColor ..hoverColor = hoverColor
..focusColor = focusColor ..focusColor = focusColor
..activeThumbImage = activeThumbImage ..activeThumbImage = activeThumbImage
..onActiveThumbImageError = onActiveThumbImageError
..inactiveThumbImage = inactiveThumbImage ..inactiveThumbImage = inactiveThumbImage
..onInactiveThumbImageError = onInactiveThumbImageError
..activeTrackColor = activeTrackColor ..activeTrackColor = activeTrackColor
..inactiveTrackColor = inactiveTrackColor ..inactiveTrackColor = inactiveTrackColor
..configuration = configuration ..configuration = configuration
...@@ -464,7 +491,9 @@ class _RenderSwitch extends RenderToggleable { ...@@ -464,7 +491,9 @@ class _RenderSwitch extends RenderToggleable {
Color hoverColor, Color hoverColor,
Color focusColor, Color focusColor,
ImageProvider activeThumbImage, ImageProvider activeThumbImage,
ImageErrorListener onActiveThumbImageError,
ImageProvider inactiveThumbImage, ImageProvider inactiveThumbImage,
ImageErrorListener onInactiveThumbImageError,
Color activeTrackColor, Color activeTrackColor,
Color inactiveTrackColor, Color inactiveTrackColor,
ImageConfiguration configuration, ImageConfiguration configuration,
...@@ -477,7 +506,9 @@ class _RenderSwitch extends RenderToggleable { ...@@ -477,7 +506,9 @@ class _RenderSwitch extends RenderToggleable {
@required this.state, @required this.state,
}) : assert(textDirection != null), }) : assert(textDirection != null),
_activeThumbImage = activeThumbImage, _activeThumbImage = activeThumbImage,
_onActiveThumbImageError = onActiveThumbImageError,
_inactiveThumbImage = inactiveThumbImage, _inactiveThumbImage = inactiveThumbImage,
_onInactiveThumbImageError = onInactiveThumbImageError,
_activeTrackColor = activeTrackColor, _activeTrackColor = activeTrackColor,
_inactiveTrackColor = inactiveTrackColor, _inactiveTrackColor = inactiveTrackColor,
_configuration = configuration, _configuration = configuration,
...@@ -511,6 +542,16 @@ class _RenderSwitch extends RenderToggleable { ...@@ -511,6 +542,16 @@ class _RenderSwitch extends RenderToggleable {
markNeedsPaint(); markNeedsPaint();
} }
ImageErrorListener get onActiveThumbImageError => _onActiveThumbImageError;
ImageErrorListener _onActiveThumbImageError;
set onActiveThumbImageError(ImageErrorListener value) {
if (value == _onActiveThumbImageError) {
return;
}
_onActiveThumbImageError = value;
markNeedsPaint();
}
ImageProvider get inactiveThumbImage => _inactiveThumbImage; ImageProvider get inactiveThumbImage => _inactiveThumbImage;
ImageProvider _inactiveThumbImage; ImageProvider _inactiveThumbImage;
set inactiveThumbImage(ImageProvider value) { set inactiveThumbImage(ImageProvider value) {
...@@ -520,6 +561,16 @@ class _RenderSwitch extends RenderToggleable { ...@@ -520,6 +561,16 @@ class _RenderSwitch extends RenderToggleable {
markNeedsPaint(); markNeedsPaint();
} }
ImageErrorListener get onInactiveThumbImageError => _onInactiveThumbImageError;
ImageErrorListener _onInactiveThumbImageError;
set onInactiveThumbImageError(ImageErrorListener value) {
if (value == _onInactiveThumbImageError) {
return;
}
_onInactiveThumbImageError = value;
markNeedsPaint();
}
Color get activeTrackColor => _activeTrackColor; Color get activeTrackColor => _activeTrackColor;
Color _activeTrackColor; Color _activeTrackColor;
set activeTrackColor(Color value) { set activeTrackColor(Color value) {
...@@ -642,12 +693,13 @@ class _RenderSwitch extends RenderToggleable { ...@@ -642,12 +693,13 @@ class _RenderSwitch extends RenderToggleable {
Color _cachedThumbColor; Color _cachedThumbColor;
ImageProvider _cachedThumbImage; ImageProvider _cachedThumbImage;
ImageErrorListener _cachedThumbErrorListener;
BoxPainter _cachedThumbPainter; BoxPainter _cachedThumbPainter;
BoxDecoration _createDefaultThumbDecoration(Color color, ImageProvider image) { BoxDecoration _createDefaultThumbDecoration(Color color, ImageProvider image, ImageErrorListener errorListener) {
return BoxDecoration( return BoxDecoration(
color: color, color: color,
image: image == null ? null : DecorationImage(image: image), image: image == null ? null : DecorationImage(image: image, onError: errorListener),
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: kElevationToShadow[1], boxShadow: kElevationToShadow[1],
); );
...@@ -698,6 +750,10 @@ class _RenderSwitch extends RenderToggleable { ...@@ -698,6 +750,10 @@ class _RenderSwitch extends RenderToggleable {
? (currentValue < 0.5 ? inactiveThumbImage : activeThumbImage) ? (currentValue < 0.5 ? inactiveThumbImage : activeThumbImage)
: inactiveThumbImage; : inactiveThumbImage;
final ImageErrorListener thumbErrorListener = isEnabled
? (currentValue < 0.5 ? onInactiveThumbImageError : onActiveThumbImageError)
: onInactiveThumbImageError;
// Paint the track // Paint the track
final Paint paint = Paint() final Paint paint = Paint()
..color = trackColor; ..color = trackColor;
...@@ -721,10 +777,11 @@ class _RenderSwitch extends RenderToggleable { ...@@ -721,10 +777,11 @@ class _RenderSwitch extends RenderToggleable {
try { try {
_isPainting = true; _isPainting = true;
BoxPainter thumbPainter; BoxPainter thumbPainter;
if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage) { if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage || thumbErrorListener != _cachedThumbErrorListener) {
_cachedThumbColor = thumbColor; _cachedThumbColor = thumbColor;
_cachedThumbImage = thumbImage; _cachedThumbImage = thumbImage;
_cachedThumbPainter = _createDefaultThumbDecoration(thumbColor, thumbImage).createBoxPainter(_handleDecorationChanged); _cachedThumbErrorListener = thumbErrorListener;
_cachedThumbPainter = _createDefaultThumbDecoration(thumbColor, thumbImage, thumbErrorListener).createBoxPainter(_handleDecorationChanged);
} }
thumbPainter = _cachedThumbPainter; thumbPainter = _cachedThumbPainter;
......
...@@ -40,6 +40,7 @@ class DecorationImage { ...@@ -40,6 +40,7 @@ class DecorationImage {
/// must not be null. /// must not be null.
const DecorationImage({ const DecorationImage({
@required this.image, @required this.image,
this.onError,
this.colorFilter, this.colorFilter,
this.fit, this.fit,
this.alignment = Alignment.center, this.alignment = Alignment.center,
...@@ -57,6 +58,9 @@ class DecorationImage { ...@@ -57,6 +58,9 @@ class DecorationImage {
/// application) or a [NetworkImage] (for an image obtained from the network). /// application) or a [NetworkImage] (for an image obtained from the network).
final ImageProvider image; final ImageProvider image;
/// An optional error callback for errors emitted when loading [image].
final ImageErrorListener onError;
/// A color filter to apply to the image before painting it. /// A color filter to apply to the image before painting it.
final ColorFilter colorFilter; final ColorFilter colorFilter;
...@@ -239,7 +243,10 @@ class DecorationImagePainter { ...@@ -239,7 +243,10 @@ class DecorationImagePainter {
final ImageStream newImageStream = _details.image.resolve(configuration); final ImageStream newImageStream = _details.image.resolve(configuration);
if (newImageStream.key != _imageStream?.key) { if (newImageStream.key != _imageStream?.key) {
final ImageStreamListener listener = ImageStreamListener(_handleImage); final ImageStreamListener listener = ImageStreamListener(
_handleImage,
onError: _details.onError,
);
_imageStream?.removeListener(listener); _imageStream?.removeListener(listener);
_imageStream = newImageStream; _imageStream = newImageStream;
_imageStream.addListener(listener); _imageStream.addListener(listener);
...@@ -286,7 +293,10 @@ class DecorationImagePainter { ...@@ -286,7 +293,10 @@ class DecorationImagePainter {
/// After this method has been called, the object is no longer usable. /// After this method has been called, the object is no longer usable.
@mustCallSuper @mustCallSuper
void dispose() { void dispose() {
_imageStream?.removeListener(ImageStreamListener(_handleImage)); _imageStream?.removeListener(ImageStreamListener(
_handleImage,
onError: _details.onError,
));
} }
@override @override
......
...@@ -77,7 +77,9 @@ class FadeInImage extends StatelessWidget { ...@@ -77,7 +77,9 @@ class FadeInImage extends StatelessWidget {
const FadeInImage({ const FadeInImage({
Key key, Key key,
@required this.placeholder, @required this.placeholder,
this.placeholderErrorBuilder,
@required this.image, @required this.image,
this.imageErrorBuilder,
this.excludeFromSemantics = false, this.excludeFromSemantics = false,
this.imageSemanticLabel, this.imageSemanticLabel,
this.fadeOutDuration = const Duration(milliseconds: 300), this.fadeOutDuration = const Duration(milliseconds: 300),
...@@ -132,7 +134,9 @@ class FadeInImage extends StatelessWidget { ...@@ -132,7 +134,9 @@ class FadeInImage extends StatelessWidget {
FadeInImage.memoryNetwork({ FadeInImage.memoryNetwork({
Key key, Key key,
@required Uint8List placeholder, @required Uint8List placeholder,
this.placeholderErrorBuilder,
@required String image, @required String image,
this.imageErrorBuilder,
double placeholderScale = 1.0, double placeholderScale = 1.0,
double imageScale = 1.0, double imageScale = 1.0,
this.excludeFromSemantics = false, this.excludeFromSemantics = false,
...@@ -200,7 +204,9 @@ class FadeInImage extends StatelessWidget { ...@@ -200,7 +204,9 @@ class FadeInImage extends StatelessWidget {
FadeInImage.assetNetwork({ FadeInImage.assetNetwork({
Key key, Key key,
@required String placeholder, @required String placeholder,
this.placeholderErrorBuilder,
@required String image, @required String image,
this.imageErrorBuilder,
AssetBundle bundle, AssetBundle bundle,
double placeholderScale, double placeholderScale,
double imageScale = 1.0, double imageScale = 1.0,
...@@ -239,9 +245,24 @@ class FadeInImage extends StatelessWidget { ...@@ -239,9 +245,24 @@ class FadeInImage extends StatelessWidget {
/// Image displayed while the target [image] is loading. /// Image displayed while the target [image] is loading.
final ImageProvider placeholder; final ImageProvider placeholder;
/// A builder function that is called if an error occurs during placeholder
/// image loading.
///
/// If this builder is not provided, any exceptions will be reported to
/// [FlutterError.onError]. If it is provided, the caller should either handle
/// the exception by providing a replacement widget, or rethrow the exception.
final ImageErrorWidgetBuilder placeholderErrorBuilder;
/// The target image that is displayed once it has loaded. /// The target image that is displayed once it has loaded.
final ImageProvider image; final ImageProvider image;
/// A builder function that is called if an error occurs during image loading.
///
/// If this builder is not provided, any exceptions will be reported to
/// [FlutterError.onError]. If it is provided, the caller should either handle
/// the exception by providing a replacement widget, or rethrow the exception.
final ImageErrorWidgetBuilder imageErrorBuilder;
/// The duration of the fade-out animation for the [placeholder]. /// The duration of the fade-out animation for the [placeholder].
final Duration fadeOutDuration; final Duration fadeOutDuration;
...@@ -337,11 +358,13 @@ class FadeInImage extends StatelessWidget { ...@@ -337,11 +358,13 @@ class FadeInImage extends StatelessWidget {
Image _image({ Image _image({
@required ImageProvider image, @required ImageProvider image,
ImageErrorWidgetBuilder errorBuilder,
ImageFrameBuilder frameBuilder, ImageFrameBuilder frameBuilder,
}) { }) {
assert(image != null); assert(image != null);
return Image( return Image(
image: image, image: image,
errorBuilder: errorBuilder,
frameBuilder: frameBuilder, frameBuilder: frameBuilder,
width: width, width: width,
height: height, height: height,
...@@ -358,12 +381,13 @@ class FadeInImage extends StatelessWidget { ...@@ -358,12 +381,13 @@ class FadeInImage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget result = _image( Widget result = _image(
image: image, image: image,
errorBuilder: imageErrorBuilder,
frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) { frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) if (wasSynchronouslyLoaded)
return child; return child;
return _AnimatedFadeOutFadeIn( return _AnimatedFadeOutFadeIn(
target: child, target: child,
placeholder: _image(image: placeholder), placeholder: _image(image: placeholder, errorBuilder: placeholderErrorBuilder),
isTargetLoaded: frame != null, isTargetLoaded: frame != null,
fadeInDuration: fadeInDuration, fadeInDuration: fadeInDuration,
fadeOutDuration: fadeOutDuration, fadeOutDuration: fadeOutDuration,
......
...@@ -40,6 +40,22 @@ class SynchronousTestImageProvider extends ImageProvider<int> { ...@@ -40,6 +40,22 @@ class SynchronousTestImageProvider extends ImageProvider<int> {
} }
} }
class SynchronousErrorTestImageProvider extends ImageProvider<int> {
const SynchronousErrorTestImageProvider(this.throwable);
final Object throwable;
@override
Future<int> obtainKey(ImageConfiguration configuration) {
throw throwable;
}
@override
ImageStreamCompleter load(int key, DecoderCallback decode) {
throw throwable;
}
}
class AsyncTestImageProvider extends ImageProvider<int> { class AsyncTestImageProvider extends ImageProvider<int> {
@override @override
Future<int> obtainKey(ImageConfiguration configuration) { Future<int> obtainKey(ImageConfiguration configuration) {
...@@ -269,6 +285,26 @@ void main() { ...@@ -269,6 +285,26 @@ void main() {
); );
}); });
test('DecorationImage - error listener', () async {
String exception;
final DecorationImage backgroundImage = DecorationImage(
image: const SynchronousErrorTestImageProvider('threw'),
onError: (dynamic error, StackTrace stackTrace) {
exception = error as String;
}
);
backgroundImage.createPainter(() { }).paint(
TestCanvas(),
Rect.largest,
Path(),
ImageConfiguration.empty,
);
// Yield so that the exception callback gets called before we check it.
await null;
expect(exception, 'threw');
});
test('BoxDecoration.lerp - shapes', () { test('BoxDecoration.lerp - shapes', () {
// We don't lerp the shape, we just switch from one to the other at t=0.5. // We don't lerp the shape, we just switch from one to the other at t=0.5.
// (Use a ShapeDecoration and ShapeBorder if you want to lerp the shapes...) // (Use a ShapeDecoration and ShapeBorder if you want to lerp the shapes...)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment