From a273ae702a37fd5e6096ef72dc12282a8732f53c Mon Sep 17 00:00:00 2001 From: GittHub-d Date: Thu, 30 Jan 2025 13:20:53 +0200 Subject: [PATCH] [MDS-1564] Avatar component --- .../storybook/stories/primitives/avatar.dart | 22 +- lib/src/widgets/avatar/avatar.dart | 212 ++++++------------ lib/src/widgets/avatar/avatar_clipper.dart | 164 -------------- 3 files changed, 74 insertions(+), 324 deletions(-) delete mode 100644 lib/src/widgets/avatar/avatar_clipper.dart diff --git a/example/lib/src/storybook/stories/primitives/avatar.dart b/example/lib/src/storybook/stories/primitives/avatar.dart index 5681d736..48dc402e 100644 --- a/example/lib/src/storybook/stories/primitives/avatar.dart +++ b/example/lib/src/storybook/stories/primitives/avatar.dart @@ -1,6 +1,7 @@ import 'package:example/src/storybook/common/color_options.dart'; import 'package:example/src/storybook/common/widgets/text_divider.dart'; import 'package:flutter/material.dart'; +import 'package:moon_core/moon_core.dart'; import 'package:moon_design/moon_design.dart'; import 'package:storybook_flutter/storybook_flutter.dart'; @@ -108,6 +109,10 @@ class AvatarStory extends StatelessWidget { initial: true, ); + final BorderRadius? borderRadius = borderRadiusKnob != null + ? BorderRadius.circular(borderRadiusKnob.toDouble()) + : null; + return Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 64.0, horizontal: 16.0), @@ -121,30 +126,23 @@ class AvatarStory extends StatelessWidget { MoonAvatar( avatarSize: avatarSizeKnob, badgeSize: badgeSizeKnob?.toDouble(), - borderRadius: borderRadiusKnob != null - ? BorderRadius.circular(borderRadiusKnob.toDouble()) - : null, + borderRadius: borderRadius, badgeMarginValue: badgeMarginKnob?.toDouble(), backgroundColor: backgroundColor, showBadge: showBadgeKnob, badgeColor: badgeColor, badgeAlignment: avatarBadgeAlignmentKnob ?? MoonBadgeAlignment.bottomRight, - content: Padding( - padding: const EdgeInsets.only(top: 1.0), - child: Text( - customLabelTextKnob, - style: TextStyle(color: textColor), - ), + content: Text( + customLabelTextKnob, + style: TextStyle(color: textColor), ), ), const TextDivider(text: "Custom MoonAvatar with image background"), MoonAvatar( avatarSize: avatarSizeKnob, badgeSize: badgeSizeKnob?.toDouble(), - borderRadius: borderRadiusKnob != null - ? BorderRadius.circular(borderRadiusKnob.toDouble()) - : null, + borderRadius: borderRadius, badgeMarginValue: badgeMarginKnob?.toDouble(), backgroundColor: backgroundColor, showBadge: showBadgeKnob, diff --git a/lib/src/widgets/avatar/avatar.dart b/lib/src/widgets/avatar/avatar.dart index f7c17038..b3240411 100644 --- a/lib/src/widgets/avatar/avatar.dart +++ b/lib/src/widgets/avatar/avatar.dart @@ -1,15 +1,11 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; import 'package:moon_core/moon_core.dart'; import 'package:moon_design/src/theme/avatar/avatar_size_properties.dart'; import 'package:moon_design/src/theme/avatar/avatar_sizes.dart'; -import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/theme/tokens/tokens.dart'; -import 'package:moon_design/src/utils/extensions.dart'; -import 'package:moon_design/src/utils/squircle/squircle_border.dart'; -import 'package:moon_design/src/widgets/avatar/avatar_clipper.dart'; import 'package:moon_tokens/moon_tokens.dart'; @@ -22,13 +18,6 @@ enum MoonAvatarSize { x2l, } -enum MoonBadgeAlignment { - topLeft, - topRight, - bottomLeft, - bottomRight, -} - class MoonAvatar extends StatelessWidget { /// Whether to show the avatar badge. final bool showBadge; @@ -66,6 +55,9 @@ class MoonAvatar extends StatelessWidget { /// The semantic label for the avatar. final String? semanticLabel; + /// The widget to display within the badge. + final Widget? badgeContent; + /// The widget to display within the avatar. final Widget? content; @@ -84,68 +76,23 @@ class MoonAvatar extends StatelessWidget { this.avatarSize, this.badgeAlignment = MoonBadgeAlignment.bottomRight, this.semanticLabel, + this.badgeContent, this.content, }); - Alignment _avatarAlignmentMapper(BuildContext context) { - final bool isRTL = Directionality.of(context) == TextDirection.rtl; - - if (isRTL) { - switch (badgeAlignment) { - case MoonBadgeAlignment.topLeft: - return Alignment.topRight; - case MoonBadgeAlignment.topRight: - return Alignment.topLeft; - case MoonBadgeAlignment.bottomLeft: - return Alignment.bottomRight; - case MoonBadgeAlignment.bottomRight: - return Alignment.bottomLeft; - default: - return Alignment.bottomRight; - } - } else { - switch (badgeAlignment) { - case MoonBadgeAlignment.topLeft: - return Alignment.topLeft; - case MoonBadgeAlignment.topRight: - return Alignment.topRight; - case MoonBadgeAlignment.bottomLeft: - return Alignment.bottomLeft; - case MoonBadgeAlignment.bottomRight: - return Alignment.bottomRight; - default: - return Alignment.bottomRight; - } - } - } - MoonAvatarSizeProperties _getMoonAvatarSize( BuildContext context, MoonAvatarSize? moonAvatarSize, ) { - switch (moonAvatarSize) { - case MoonAvatarSize.xs: - return context.moonTheme?.avatarTheme.sizes.xs ?? - MoonAvatarSizes(tokens: MoonTokens.light).xs; - case MoonAvatarSize.sm: - return context.moonTheme?.avatarTheme.sizes.sm ?? - MoonAvatarSizes(tokens: MoonTokens.light).sm; - case MoonAvatarSize.md: - return context.moonTheme?.avatarTheme.sizes.md ?? - MoonAvatarSizes(tokens: MoonTokens.light).md; - case MoonAvatarSize.lg: - return context.moonTheme?.avatarTheme.sizes.lg ?? - MoonAvatarSizes(tokens: MoonTokens.light).lg; - case MoonAvatarSize.xl: - return context.moonTheme?.avatarTheme.sizes.xl ?? - MoonAvatarSizes(tokens: MoonTokens.light).xl; - case MoonAvatarSize.x2l: - return context.moonTheme?.avatarTheme.sizes.x2l ?? - MoonAvatarSizes(tokens: MoonTokens.light).x2l; - default: - return context.moonTheme?.avatarTheme.sizes.md ?? - MoonAvatarSizes(tokens: MoonTokens.light).md; - } + return switch (moonAvatarSize) { + MoonAvatarSize.xs => MoonAvatarSizes(tokens: MoonTokens.light).xs, + MoonAvatarSize.sm => MoonAvatarSizes(tokens: MoonTokens.light).sm, + MoonAvatarSize.md => MoonAvatarSizes(tokens: MoonTokens.light).md, + MoonAvatarSize.lg => MoonAvatarSizes(tokens: MoonTokens.light).lg, + MoonAvatarSize.xl => MoonAvatarSizes(tokens: MoonTokens.light).xl, + MoonAvatarSize.x2l => MoonAvatarSizes(tokens: MoonTokens.light).x2l, + _ => MoonAvatarSizes(tokens: MoonTokens.light).md, + }; } @override @@ -159,21 +106,14 @@ class MoonAvatar extends StatelessWidget { final resolvedBorderRadius = effectiveBorderRadius.resolve(Directionality.of(context)); - final Color effectiveBackgroundColor = backgroundColor ?? - context.moonTheme?.avatarTheme.colors.backgroundColor ?? - MoonColors.light.goku; + final Color effectiveBackgroundColor = + backgroundColor ?? MoonColors.light.goku; - final Color effectiveBadgeColor = badgeColor ?? - context.moonTheme?.avatarTheme.colors.badgeColor ?? - MoonColors.light.roshi; + final Color effectiveBadgeColor = badgeColor ?? MoonColors.light.roshi; - final Color effectiveTextColor = - context.moonTheme?.avatarTheme.colors.textColor ?? - MoonColors.light.textPrimary; + final Color effectiveTextColor = MoonColors.light.textPrimary; - final Color effectiveIconColor = - context.moonTheme?.avatarTheme.colors.iconColor ?? - MoonColors.light.iconPrimary; + final Color effectiveIconColor = MoonColors.light.iconPrimary; final double effectiveAvatarHeight = height ?? effectiveMoonAvatarSize.avatarSizeValue; @@ -187,77 +127,53 @@ class MoonAvatar extends StatelessWidget { final double effectiveBadgeSize = badgeSize ?? effectiveMoonAvatarSize.badgeSizeValue; - return Semantics( - label: semanticLabel, - button: false, - focusable: false, - image: backgroundImage != null, - child: SizedBox( - width: effectiveAvatarWidth, - height: effectiveAvatarHeight, - child: Stack( - children: [ - Positioned.fill( - child: ClipPath( - // TODO: Since clipper does not work properly on mobile web/PWA, - // we are disabling it. Remove this check when it has been - // fixed from Flutter side. - clipper: kIsWeb && - MediaQueryData.fromView(View.of(context)).size.width < - 500 - ? null - : AvatarClipper( - showBadge: showBadge, - width: effectiveAvatarWidth, - height: effectiveAvatarHeight, - borderRadius: resolvedBorderRadius, - badgeSize: effectiveBadgeSize, - badgeMarginValue: effectiveBadgeMarginValue, - badgeAlignment: badgeAlignment, - textDirection: Directionality.of(context), - ), - child: DefaultTextStyle( - style: effectiveMoonAvatarSize.textStyle - .copyWith(color: effectiveTextColor), - child: IconTheme( - data: IconThemeData( - color: effectiveIconColor, - ), - child: DecoratedBox( - decoration: ShapeDecorationWithPremultipliedAlpha( - color: effectiveBackgroundColor, - image: backgroundImage != null - ? DecorationImage( - image: backgroundImage!, - fit: BoxFit.cover, - ) - : null, - shape: MoonSquircleBorder( - borderRadius: resolvedBorderRadius - .squircleBorderRadius(context), - ), - ), - child: Center(child: content), - ), - ), - ), - ), - ), - if (showBadge) - Align( - alignment: _avatarAlignmentMapper(context), - child: Container( - height: effectiveBadgeSize, - width: effectiveBadgeSize, - decoration: BoxDecoration( - color: effectiveBadgeColor, - borderRadius: BorderRadius.circular(effectiveBadgeSize / 2), - ), - ), - ), - ], + final TextStyle resolvedTextStyle = + effectiveMoonAvatarSize.textStyle.copyWith(color: effectiveTextColor); + + final Style baseStyle = Style( + $box.alignment.center(), + $with.defaultTextStyle.style.as(resolvedTextStyle), + $with.iconTheme.data.color(effectiveIconColor), + ); + + final Style badgeStyle = baseStyle.add( + $box.shapeDecoration.as( + ShapeDecorationWithPremultipliedAlpha( + color: effectiveBadgeColor, + shape: MoonBorder( + borderRadius: BorderRadius.circular(effectiveBadgeSize / 2), + ), ), ), ); + + final Style contentStyle = baseStyle.add( + $box.shapeDecoration.as( + ShapeDecorationWithPremultipliedAlpha( + color: effectiveBackgroundColor, + shape: MoonBorder(borderRadius: resolvedBorderRadius), + image: backgroundImage != null + ? DecorationImage(image: backgroundImage!, fit: BoxFit.cover) + : null, + ), + ), + ); + + return MoonRawAvatar( + showBadge: showBadge, + semanticLabel: semanticLabel, + badgeSizeValue: effectiveBadgeSize, + badgeMarginValue: effectiveBadgeMarginValue, + badgeAlignment: badgeAlignment, + avatarSize: Size(effectiveAvatarWidth, effectiveAvatarHeight), + badge: Box( + style: badgeStyle, + child: badgeContent, + ), + content: Box( + style: contentStyle, + child: content, + ), + ); } } diff --git a/lib/src/widgets/avatar/avatar_clipper.dart b/lib/src/widgets/avatar/avatar_clipper.dart deleted file mode 100644 index 6620f266..00000000 --- a/lib/src/widgets/avatar/avatar_clipper.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'package:flutter/rendering.dart'; - -import 'package:moon_design/src/utils/squircle/squircle_radius.dart'; -import 'package:moon_design/src/widgets/avatar/avatar.dart'; - -class AvatarClipper extends CustomClipper { - final bool showBadge; - final BorderRadius borderRadius; - final double height; - final double width; - final double badgeMarginValue; - final double badgeSize; - final MoonBadgeAlignment badgeAlignment; - final TextDirection textDirection; - - AvatarClipper({ - required this.showBadge, - required this.borderRadius, - required this.height, - required this.width, - required this.badgeMarginValue, - required this.badgeSize, - required this.badgeAlignment, - required this.textDirection, - }); - - Path _getBadgePath() { - final double badgeRadius = badgeSize / 2; - - if (textDirection == TextDirection.rtl) { - switch (badgeAlignment) { - case MoonBadgeAlignment.topLeft: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(width - badgeRadius, 0 + badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - case MoonBadgeAlignment.topRight: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(0 + badgeRadius, 0 + badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - case MoonBadgeAlignment.bottomLeft: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(width - badgeRadius, width - badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - case MoonBadgeAlignment.bottomRight: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(0 + badgeRadius, width - badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - default: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(width - badgeRadius, width - badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - } - } else { - switch (badgeAlignment) { - case MoonBadgeAlignment.topLeft: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(0 + badgeRadius, 0 + badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - case MoonBadgeAlignment.topRight: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(width - badgeRadius, 0 + badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - case MoonBadgeAlignment.bottomLeft: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(0 + badgeRadius, height - badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - case MoonBadgeAlignment.bottomRight: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(width - badgeRadius, height - badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - default: - return Path() - ..addOval( - Rect.fromCircle( - center: Offset(width - badgeRadius, width - badgeRadius), - radius: badgeRadius + badgeMarginValue, - ), - ); - } - } - } - - @override - Path getClip(Size size) { - final Path pathWithBadge = Path.combine( - PathOperation.difference, - // Avatar shape properties - Path() - ..addRRect( - RRect.fromLTRBAndCorners( - 0, - 0, - width, - height, - topLeft: MoonSquircleRadius(cornerRadius: borderRadius.topLeft.x), - topRight: MoonSquircleRadius(cornerRadius: borderRadius.topRight.x), - bottomLeft: - MoonSquircleRadius(cornerRadius: borderRadius.bottomLeft.x), - bottomRight: - MoonSquircleRadius(cornerRadius: borderRadius.bottomRight.x), - ), - ), - - _getBadgePath(), // Badge shape properties. - ); - - final Path pathWithoutBadge = Path() - ..addRRect( - RRect.fromLTRBAndCorners( - 0, - 0, - width, - height, - topLeft: MoonSquircleRadius(cornerRadius: borderRadius.topLeft.x), - topRight: MoonSquircleRadius(cornerRadius: borderRadius.topRight.x), - bottomLeft: - MoonSquircleRadius(cornerRadius: borderRadius.bottomLeft.x), - bottomRight: - MoonSquircleRadius(cornerRadius: borderRadius.bottomRight.x), - ), - ); - - return showBadge ? pathWithBadge : pathWithoutBadge; - } - - @override - bool shouldReclip(CustomClipper oldClipper) => true; -}