using Unity.UIWidgets.foundation; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; namespace Unity.UIWidgets.material { public static class CircleAvatarUtils { // The default radius if nothing is specified. public static float _defaultRadius = 20.0f; // The default min if only the max is specified. public static float _defaultMinRadius = 0.0f; // The default max if only the min is specified. public static float _defaultMaxRadius = float.PositiveInfinity; } public class CircleAvatar : StatelessWidget { /// The color with which to fill the circle. Changing the background /// color will cause the avatar to animate to the new color. /// /// If a [backgroundColor] is not specified, the theme's /// [ThemeData.primaryColorLight] is used with dark foreground colors, and /// [ThemeData.primaryColorDark] with light foreground colors. public readonly Color backgroundColor; /// The background image of the circle. Changing the background /// image will cause the avatar to animate to the new image. /// /// Typically used as a fallback image for [foregroundImage]. /// /// If the [CircleAvatar] is to have the user's initials, use [child] instead. public readonly ImageProvider backgroundImage; /// The widget below this widget in the tree. /// /// Typically a [Text] widget. If the [CircleAvatar] is to have an image, use /// [backgroundImage] instead. public readonly Widget child; /// The default text color for text in the circle. /// /// Defaults to the primary text theme color if no [backgroundColor] is /// specified. /// /// Defaults to [ThemeData.primaryColorLight] for dark background colors, and /// [ThemeData.primaryColorDark] for light background colors. public readonly Color foregroundColor; /// The foreground image of the circle. /// /// Typically used as profile image. For fallback use [backgroundImage]. public readonly ImageProvider foregroundImage; /// The maximum size of the avatar, expressed as the radius (half the /// diameter). /// /// If [maxRadius] is specified, then [radius] must not also be specified. /// /// Defaults to [double.infinity]. /// /// Constraint changes are animated, but size changes due to the environment /// itself changing are not. For example, changing the [maxRadius] from 10 to /// 20 when the [CircleAvatar] is in an unconstrained environment will cause /// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter. /// However, if the [maxRadius] is 40 and the [CircleAvatar] has a parent /// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels, /// the size will snap to 40 pixels instantly. public readonly float? maxRadius; /// The minimum size of the avatar, expressed as the radius (half the /// diameter). /// /// If [minRadius] is specified, then [radius] must not also be specified. /// /// Defaults to zero. /// /// Constraint changes are animated, but size changes due to the environment /// itself changing are not. For example, changing the [minRadius] from 10 to /// 20 when the [CircleAvatar] is in an unconstrained environment will cause /// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter. /// However, if the [minRadius] is 40 and the [CircleAvatar] has a parent /// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels, /// the size will snap to 40 pixels instantly. public readonly float? minRadius; /// An optional error callback for errors emitted when loading /// [backgroundImage]. public readonly ImageErrorListener onBackgroundImageError; /// An optional error callback for errors emitted when loading /// [foregroundImage]. public readonly ImageErrorListener onForegroundImageError; /// The size of the avatar, expressed as the radius (half the diameter). /// /// If [radius] is specified, then neither [minRadius] nor [maxRadius] may be /// specified. Specifying [radius] is equivalent to specifying a [minRadius] /// and [maxRadius], both with the value of [radius]. /// /// If neither [minRadius] nor [maxRadius] are specified, defaults to 20 /// logical pixels. This is the appropriate size for use with /// [ListTile.leading]. /// /// Changes to the [radius] are animated (including changing from an explicit /// [radius] to a [minRadius]/[maxRadius] pair or vice versa). public readonly float? radius; /// Creates a circle that represents a user. public CircleAvatar( Key key = null, Widget child = null, Color backgroundColor = null, Color foregroundColor = null, ImageProvider backgroundImage = null, ImageProvider foregroundImage = null, ImageErrorListener onBackgroundImageError = null, ImageErrorListener onForegroundImageError = null, float radius = 0.0f, float minRadius = 0.0f, float maxRadius = 0.0f ) : base(key) { D.assert(radius == null || minRadius == null && maxRadius == null); D.assert(backgroundImage != null || onBackgroundImageError == null); D.assert(foregroundImage != null || onForegroundImageError == null); this.child = child; this.backgroundColor = backgroundColor; this.backgroundImage = backgroundImage; this.foregroundImage = foregroundImage; this.onBackgroundImageError = onBackgroundImageError; this.onForegroundImageError = onForegroundImageError; this.foregroundColor = foregroundColor; this.radius = radius; this.minRadius = minRadius; this.maxRadius = maxRadius; } private float _minDiameter { get { if (radius == null && minRadius == null && maxRadius == null) return CircleAvatarUtils._defaultRadius * 2.0f; return 2.0f * (radius ?? minRadius ?? CircleAvatarUtils._defaultMinRadius); } } private float _maxDiameter { get { if (radius == null && minRadius == null && maxRadius == null) return CircleAvatarUtils._defaultRadius * 2.0f; return 2.0f * (radius ?? maxRadius ?? CircleAvatarUtils._defaultMaxRadius); } } public override Widget build(BuildContext context) { D.assert(WidgetsD.debugCheckHasMediaQuery(context)); var theme = Theme.of(context); var textStyle = theme.primaryTextTheme.subtitle1.copyWith(color: foregroundColor); var effectiveBackgroundColor = backgroundColor; if (effectiveBackgroundColor == null) switch (ThemeData.estimateBrightnessForColor(textStyle.color)) { case Brightness.dark: effectiveBackgroundColor = theme.primaryColorLight; break; case Brightness.light: effectiveBackgroundColor = theme.primaryColorDark; break; } else if (foregroundColor == null) switch (ThemeData.estimateBrightnessForColor(backgroundColor)) { case Brightness.dark: textStyle = textStyle.copyWith(color: theme.primaryColorLight); break; case Brightness.light: textStyle = textStyle.copyWith(color: theme.primaryColorDark); break; } var minDiameter = _minDiameter; var maxDiameter = _maxDiameter; return new AnimatedContainer( constraints: new BoxConstraints( minHeight: minDiameter, minWidth: minDiameter, maxWidth: maxDiameter, maxHeight: maxDiameter ), duration: material_.kThemeChangeDuration, decoration: new BoxDecoration( effectiveBackgroundColor, backgroundImage != null ? new DecorationImage( backgroundImage, onBackgroundImageError, fit: BoxFit.cover ) : null, shape: BoxShape.circle ), foregroundDecoration: foregroundImage != null ? new BoxDecoration( image: new DecorationImage( foregroundImage, onForegroundImageError, fit: BoxFit.cover ), shape: BoxShape.circle ) : null, child: child == null ? null : new Center( child: new MediaQuery( // Need to ignore the ambient textScaleFactor here so that the // text doesn't escape the avatar when the textScaleFactor is large. data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0f), child: new IconTheme( data: theme.iconTheme.copyWith(textStyle.color), child: new DefaultTextStyle( style: textStyle, child: child ) ) ) ) ); } } }