From 0a23c6cd2d07f684af61bd2fe5ad51fad47fcb8b Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Wed, 24 Jul 2024 23:52:06 +0800 Subject: [PATCH 01/17] improvements to avatar, docs revamp --- docs/pages/docs/avatar.mdx | 47 ++++++++- forui/lib/src/widgets/avatar.dart | 152 +++++++++++++++++++----------- samples/lib/widgets/avatar.dart | 19 +++- 3 files changed, 159 insertions(+), 59 deletions(-) diff --git a/docs/pages/docs/avatar.mdx b/docs/pages/docs/avatar.mdx index be032f404..1789213e0 100644 --- a/docs/pages/docs/avatar.mdx +++ b/docs/pages/docs/avatar.mdx @@ -11,7 +11,7 @@ An image element with a fallback for representing the user. ```dart FAvatar( - image: const NetworkImage('https://picsum.photos/250?image=9'), + image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), placeholderBuilder: (_) => const Text('MN'), ), ``` @@ -24,9 +24,50 @@ An image element with a fallback for representing the user. ```dart FAvatar( - image: const NetworkImage('https://picsum.photos/250?image=9'), - size: 60, + image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), placeholderBuilder: (_) => const Text('MN'), ), ``` +### `FAvatar.raw(...)` + +```dart +FButton.raw( + child: const Text('MN'), + size: 60, +); +``` + + +## Examples + +### Invalid image with Placeholder + + + + + + ```dart + FAvatar( + image: const NetworkImage(''), + placeholderBuilder: (_) => const Text('MN'), + ), + ``` + + + +### Invalid image without Placeholder + + + + + + ```dart + FAvatar( + image: const NetworkImage(''), + ), + ``` + + + + diff --git a/forui/lib/src/widgets/avatar.dart b/forui/lib/src/widgets/avatar.dart index f1a5c0830..dc1ba23c9 100644 --- a/forui/lib/src/widgets/avatar.dart +++ b/forui/lib/src/widgets/avatar.dart @@ -7,6 +7,7 @@ import 'package:forui/forui.dart'; /// An image element with a fallback for representing the user. /// +/// use [image] to provide a profile image displayed within the circle. /// Typically used with a user's profile image. If the image fails to load, /// [placeholderBuilder] is used instead, which usually displays the user's initials. /// @@ -16,28 +17,31 @@ class FAvatar extends StatelessWidget { /// The style. Defaults to [FThemeData.avatarStyle]. final FAvatarStyle? style; - /// The profile image displayed within the circle. - /// - /// If the user's initials are used, use [placeholderBuilder] instead. - final ImageProvider image; - /// The circle's size. final double size; - /// The fallback widget displayed if [image] fails to load. - /// - /// Typically used to display the user's initials using a [Text] widget - /// styled with [FAvatarStyle.backgroundColor]. - /// - /// Use [image] to display an image; use [placeholderBuilder] for initials. - final Widget Function(BuildContext)? placeholderBuilder; + /// The child. + final Widget child; /// Creates an [FAvatar]. - const FAvatar({ - required this.image, + FAvatar({ + required ImageProvider image, + Widget Function(BuildContext)? placeholderBuilder, + this.size = 40.0, this.style, + super.key, + }) : child = _Avatar( + style: style, + size: size, + image: image, + placeholderBuilder: placeholderBuilder, + ); + + /// Creates a [FAvatar] with custom child. + const FAvatar.raw({ + required this.child, this.size = 40.0, - this.placeholderBuilder, + this.style, super.key, }); @@ -53,40 +57,7 @@ class FAvatar extends StatelessWidget { shape: BoxShape.circle, ), clipBehavior: Clip.hardEdge, - child: Center( - child: Image( - filterQuality: FilterQuality.medium, - image: image, - errorBuilder: (context, exception, stacktrace) => DefaultTextStyle( - style: style.text, - child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), - ), - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded) { - return child; - } - return AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - child: frame == null - ? DefaultTextStyle( - style: style.text, - child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), - ) - : child, - ); - }, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; - } - return DefaultTextStyle( - style: style.text, - child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), - ); - }, - fit: BoxFit.cover, - ), - ), + child: child, ); } @@ -94,10 +65,8 @@ class FAvatar extends StatelessWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty('image', image)) ..add(DoubleProperty('size', size)) - ..add(DiagnosticsProperty('style', style)) - ..add(ObjectFlagProperty.has('placeholderBuilder', placeholderBuilder)); + ..add(DiagnosticsProperty('style', style)); } } @@ -113,7 +82,7 @@ final class FAvatarStyle with Diagnosticable { final TextStyle text; /// Creates a [FAvatarStyle]. - FAvatarStyle({ + const FAvatarStyle({ required this.backgroundColor, required this.fadeInDuration, required this.text, @@ -175,6 +144,83 @@ final class FAvatarStyle with Diagnosticable { int get hashCode => backgroundColor.hashCode ^ fadeInDuration.hashCode ^ text.hashCode; } +class _Avatar extends StatelessWidget { + final FAvatarStyle? style; + + /// The circle's size. + final double size; + + /// The profile image displayed within the circle. + /// + /// If the user's initials are used, use [placeholderBuilder] instead. + final ImageProvider image; + + /// The fallback widget displayed if [image] fails to load. + /// + /// Typically used to display the user's initials using a [Text] widget + /// styled with [FAvatarStyle.backgroundColor]. + /// + /// Use [image] to display an image; use [placeholderBuilder] for initials. + final Widget Function(BuildContext)? placeholderBuilder; + + const _Avatar({ + required this.style, + required this.size, + required this.image, + required this.placeholderBuilder, + }); + + @override + Widget build(BuildContext context) { + final style = this.style ?? context.theme.avatarStyle; + return Center( + child: Image( + filterQuality: FilterQuality.medium, + image: image, + errorBuilder: (context, exception, stacktrace) => DefaultTextStyle( + style: style.text, + child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), + ), + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: frame == null + ? DefaultTextStyle( + style: style.text, + child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), + ) + : child, + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return DefaultTextStyle( + style: style.text, + child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), + ); + }, + fit: BoxFit.cover, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('style', style)) + ..add(ObjectFlagProperty.has('placeholderBuilder', placeholderBuilder)) + ..add(DiagnosticsProperty('image', image)) + ..add(DoubleProperty('size', size)) + ..add(DiagnosticsProperty('style', style)); + } +} + class _Placeholder extends StatelessWidget { final double size; diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index 5ada65882..300948bf7 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -5,11 +5,24 @@ import 'package:forui/forui.dart'; import 'package:forui_samples/sample_scaffold.dart'; +final images = { + 'default': const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), + 'error': const NetworkImage(''), +}; + +final placeholders = {'text': const Text('MN')}; + @RoutePage() class AvatarPage extends SampleScaffold { + final ImageProvider image; + final Widget? placeholder; + AvatarPage({ @queryParam super.theme, - }); + @queryParam String image = 'default', + @queryParam String child = 'text', + }) : image = images[image] ?? const NetworkImage(''), + placeholder = placeholders[child]; @override Widget child(BuildContext context) => Column( @@ -17,8 +30,8 @@ class AvatarPage extends SampleScaffold { children: [ FAvatar( size: 70, - image: const AssetImage('avatar.png'), - placeholderBuilder: (_) => const Text('MN'), + image: image, + placeholderBuilder: placeholder != null ? (_) => placeholder! : null, ), ], ); From 6a0ac1f18e641062b2f20f8325c5650d9c919d0f Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Wed, 24 Jul 2024 23:54:56 +0800 Subject: [PATCH 02/17] Removed references to constructor properties --- forui/lib/src/widgets/avatar.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forui/lib/src/widgets/avatar.dart b/forui/lib/src/widgets/avatar.dart index dc1ba23c9..6ad88c896 100644 --- a/forui/lib/src/widgets/avatar.dart +++ b/forui/lib/src/widgets/avatar.dart @@ -7,11 +7,11 @@ import 'package:forui/forui.dart'; /// An image element with a fallback for representing the user. /// -/// use [image] to provide a profile image displayed within the circle. +/// use image property to provide a profile image displayed within the circle. /// Typically used with a user's profile image. If the image fails to load, -/// [placeholderBuilder] is used instead, which usually displays the user's initials. +/// the placeholderBuilder property is used instead, which usually displays the user's initials. /// -/// If the user's profile has no image, use [placeholderBuilder] to provide +/// If the user's profile has no image, use the placeholderBuilder property to provide /// the initials using a [Text] widget styled with [FAvatarStyle.backgroundColor]. class FAvatar extends StatelessWidget { /// The style. Defaults to [FThemeData.avatarStyle]. From 09136f9908bfa060b1f5911ce09d662cf87d5235 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Thu, 25 Jul 2024 16:04:20 +0800 Subject: [PATCH 03/17] Fixed pr issues, need to verify if path works when deployed --- docs/pages/docs/avatar.mdx | 78 +++++----- forui/lib/src/widgets/avatar.dart | 136 +++++++++--------- .../golden/avatar/zinc-dark-raw-content.png | Bin 0 -> 26557 bytes .../golden/avatar/zinc-dark-with-image.png | Bin 7858 -> 7606 bytes .../golden/avatar/zinc-light-raw-content.png | Bin 0 -> 25955 bytes .../golden/avatar/zinc-light-with-image.png | Bin 7714 -> 7478 bytes .../test/src/widgets/avatar_golden_test.dart | 32 ++++- samples/lib/main.dart | 4 + samples/lib/widgets/avatar.dart | 65 ++++++--- 9 files changed, 188 insertions(+), 127 deletions(-) create mode 100644 forui/test/golden/avatar/zinc-dark-raw-content.png create mode 100644 forui/test/golden/avatar/zinc-light-raw-content.png diff --git a/docs/pages/docs/avatar.mdx b/docs/pages/docs/avatar.mdx index 1789213e0..29ab1a1f0 100644 --- a/docs/pages/docs/avatar.mdx +++ b/docs/pages/docs/avatar.mdx @@ -6,14 +6,28 @@ An image element with a fallback for representing the user. - + ```dart - FAvatar( - image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), - placeholderBuilder: (_) => const Text('MN'), - ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FAvatar( + image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const NetworkImage(''), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const NetworkImage(''), + ), + ], + ); ``` @@ -23,51 +37,45 @@ An image element with a fallback for representing the user. ### `FAvatar(...)` ```dart +// Default constructor - Valid image FAvatar( image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), - placeholderBuilder: (_) => const Text('MN'), + placeholder: const Text('MN'), ), -``` -### `FAvatar.raw(...)` +// Default constructor - Invalid image with placeholder +FAvatar( + image: const NetworkImage(''), + placeholder: const Text('MN'), +), -```dart -FButton.raw( - child: const Text('MN'), - size: 60, -); +// Default constructor - Invalid image without placeholder +FAvatar.raw( + image: const NetworkImage(''), +), ``` - -## Examples - -### Invalid image with Placeholder +### `FAvatar.raw(...)` - + ```dart - FAvatar( - image: const NetworkImage(''), - placeholderBuilder: (_) => const Text('MN'), + // Raw constructor - without child + FAvatar.raw(), + + // Raw constructor - with child + FAvatar.raw( + child: FAssets.icons.baby( + colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), + ), ), - ``` - - -### Invalid image without Placeholder - - - - - - ```dart - FAvatar( - image: const NetworkImage(''), + // Raw constructor - with text + FAvatar.raw( + child: const Text('MN'), ), ``` - - diff --git a/forui/lib/src/widgets/avatar.dart b/forui/lib/src/widgets/avatar.dart index 6ad88c896..b594b13b6 100644 --- a/forui/lib/src/widgets/avatar.dart +++ b/forui/lib/src/widgets/avatar.dart @@ -20,30 +20,35 @@ class FAvatar extends StatelessWidget { /// The circle's size. final double size; - /// The child. - final Widget child; + /// The fallback widget displayed if [image] fails to load. + /// + /// Typically used to display the user's initials using a [Text] widget + /// styled with [FAvatarStyle.backgroundColor]. + /// + /// Use [image] to display an image; use [placeholderBuilder] for initials. + final Widget Function(BuildContext, FAvatarStyle) placeholderBuilder; /// Creates an [FAvatar]. FAvatar({ required ImageProvider image, - Widget Function(BuildContext)? placeholderBuilder, + Widget? placeholder, this.size = 40.0, this.style, super.key, - }) : child = _Avatar( - style: style, - size: size, - image: image, - placeholderBuilder: placeholderBuilder, - ); + }) : placeholderBuilder = ((context, style) => _AvatarContent( + style: style, + size: size, + image: image, + placeholder: placeholder, + )); /// Creates a [FAvatar] with custom child. - const FAvatar.raw({ - required this.child, + FAvatar.raw({ + Widget? child, this.size = 40.0, this.style, super.key, - }); + }) : placeholderBuilder = ((context, style) => child ?? _IconPlaceholder(style: style, size: size,)); @override Widget build(BuildContext context) { @@ -57,7 +62,12 @@ class FAvatar extends StatelessWidget { shape: BoxShape.circle, ), clipBehavior: Clip.hardEdge, - child: child, + child: Center( + child: DefaultTextStyle( + style: style.text, + child: placeholderBuilder(context, style), + ), + ), ); } @@ -66,7 +76,8 @@ class FAvatar extends StatelessWidget { super.debugFillProperties(properties); properties ..add(DoubleProperty('size', size)) - ..add(DiagnosticsProperty('style', style)); + ..add(DiagnosticsProperty('style', style)) + ..add(ObjectFlagProperty.has('placeholderBuilder', placeholderBuilder)); } } @@ -75,6 +86,9 @@ final class FAvatarStyle with Diagnosticable { /// The placeholder's background color. final Color backgroundColor; + /// The placeholder's color. + final Color foregroundColor; + /// Duration for the transition animation. final Duration fadeInDuration; @@ -84,6 +98,7 @@ final class FAvatarStyle with Diagnosticable { /// Creates a [FAvatarStyle]. const FAvatarStyle({ required this.backgroundColor, + required this.foregroundColor, required this.fadeInDuration, required this.text, }); @@ -91,6 +106,7 @@ final class FAvatarStyle with Diagnosticable { /// Creates a [FCardStyle] that inherits its properties from [colorScheme] and [typography]. FAvatarStyle.inherit({required FColorScheme colorScheme, required FTypography typography}) : backgroundColor = colorScheme.muted, + foregroundColor = colorScheme.mutedForeground, fadeInDuration = const Duration(milliseconds: 500), text = typography.base.copyWith( color: colorScheme.mutedForeground, @@ -113,11 +129,13 @@ final class FAvatarStyle with Diagnosticable { @useResult FAvatarStyle copyWith({ Color? backgroundColor, + Color? foregroundColor, Duration? fadeInDuration, TextStyle? text, }) => FAvatarStyle( backgroundColor: backgroundColor ?? this.backgroundColor, + foregroundColor: foregroundColor ?? this.foregroundColor, fadeInDuration: fadeInDuration ?? this.fadeInDuration, text: text ?? this.text, ); @@ -127,6 +145,7 @@ final class FAvatarStyle with Diagnosticable { super.debugFillProperties(properties); properties ..add(ColorProperty('backgroundColor', backgroundColor)) + ..add(ColorProperty('foregroundColor', foregroundColor)) ..add(DiagnosticsProperty('fadeInDuration', fadeInDuration)) ..add(DiagnosticsProperty('text', text)); } @@ -137,14 +156,15 @@ final class FAvatarStyle with Diagnosticable { other is FAvatarStyle && runtimeType == other.runtimeType && backgroundColor == other.backgroundColor && + foregroundColor == other.foregroundColor && fadeInDuration == other.fadeInDuration && text == other.text; @override - int get hashCode => backgroundColor.hashCode ^ fadeInDuration.hashCode ^ text.hashCode; + int get hashCode => backgroundColor.hashCode ^ foregroundColor.hashCode ^ fadeInDuration.hashCode ^ text.hashCode; } -class _Avatar extends StatelessWidget { +class _AvatarContent extends StatelessWidget { final FAvatarStyle? style; /// The circle's size. @@ -152,60 +172,48 @@ class _Avatar extends StatelessWidget { /// The profile image displayed within the circle. /// - /// If the user's initials are used, use [placeholderBuilder] instead. + /// If the user's initials are used, use [placeholder] instead. final ImageProvider image; /// The fallback widget displayed if [image] fails to load. /// /// Typically used to display the user's initials using a [Text] widget /// styled with [FAvatarStyle.backgroundColor]. - /// - /// Use [image] to display an image; use [placeholderBuilder] for initials. - final Widget Function(BuildContext)? placeholderBuilder; + final Widget? placeholder; - const _Avatar({ + const _AvatarContent({ required this.style, required this.size, required this.image, - required this.placeholderBuilder, + this.placeholder, }); @override Widget build(BuildContext context) { final style = this.style ?? context.theme.avatarStyle; - return Center( - child: Image( - filterQuality: FilterQuality.medium, - image: image, - errorBuilder: (context, exception, stacktrace) => DefaultTextStyle( - style: style.text, - child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), - ), - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded) { - return child; - } - return AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - child: frame == null - ? DefaultTextStyle( - style: style.text, - child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), - ) - : child, - ); - }, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; - } - return DefaultTextStyle( - style: style.text, - child: placeholderBuilder != null ? placeholderBuilder!(context) : _Placeholder(size: size), - ); - }, - fit: BoxFit.cover, - ), + + final placeholder = this.placeholder ?? _IconPlaceholder(style: style, size: size); + + return Image( + filterQuality: FilterQuality.medium, + image: image, + errorBuilder: (context, exception, stacktrace) => placeholder, + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: frame == null ? placeholder : child, + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return placeholder; + }, + fit: BoxFit.cover, ); } @@ -214,31 +222,31 @@ class _Avatar extends StatelessWidget { super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('style', style)) - ..add(ObjectFlagProperty.has('placeholderBuilder', placeholderBuilder)) - ..add(DiagnosticsProperty('image', image)) ..add(DoubleProperty('size', size)) - ..add(DiagnosticsProperty('style', style)); + ..add(DiagnosticsProperty('image', image)); } } -class _Placeholder extends StatelessWidget { +class _IconPlaceholder extends StatelessWidget { final double size; + final FAvatarStyle? style; - const _Placeholder({required this.size}); + const _IconPlaceholder({required this.size, this.style}); @override Widget build(BuildContext context) { - final style = context.theme; - + final style = this.style ?? context.theme.avatarStyle; return FAssets.icons.userRound( height: size / 2, - colorFilter: ColorFilter.mode(style.colorScheme.mutedForeground, BlendMode.srcIn), + colorFilter: ColorFilter.mode(style.foregroundColor, BlendMode.srcIn), ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(DoubleProperty('size', size)); + properties + ..add(DoubleProperty('size', size)) + ..add(DiagnosticsProperty('style', style)); } } diff --git a/forui/test/golden/avatar/zinc-dark-raw-content.png b/forui/test/golden/avatar/zinc-dark-raw-content.png new file mode 100644 index 0000000000000000000000000000000000000000..a8fb9339906a425cecaa1abff0183b1b967d75a1 GIT binary patch literal 26557 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 I(*4K(0#bbni~s-t literal 0 HcmV?d00001 diff --git a/forui/test/golden/avatar/zinc-dark-with-image.png b/forui/test/golden/avatar/zinc-dark-with-image.png index c9fe7e488be5e1650d956051e910aed783a02ce1..fa2fc93919c5fd098d970bfce6c50dc2b82d70c5 100644 GIT binary patch literal 7606 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 HUw-{RUQQ~X literal 7858 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 diff --git a/forui/test/golden/avatar/zinc-light-raw-content.png b/forui/test/golden/avatar/zinc-light-raw-content.png new file mode 100644 index 0000000000000000000000000000000000000000..eca73164d1cdbcf95f3ebdcd45834bffe4e61578 GIT binary patch literal 25955 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 literal 0 HcmV?d00001 diff --git a/forui/test/golden/avatar/zinc-light-with-image.png b/forui/test/golden/avatar/zinc-light-with-image.png index 858fcc52e805a76000e9fc71f9763ed3a648b4c4..d2b7cd860265d40892a2d0eb6c78c5d1707413d8 100644 GIT binary patch literal 7478 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 literal 7714 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 P1yJ9_f8F?1+AseLGXUu1 diff --git a/forui/test/src/widgets/avatar_golden_test.dart b/forui/test/src/widgets/avatar_golden_test.dart index d4b4759e6..f027dd7f3 100644 --- a/forui/test/src/widgets/avatar_golden_test.dart +++ b/forui/test/src/widgets/avatar_golden_test.dart @@ -3,11 +3,13 @@ library; import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:forui/forui.dart'; +import 'package:forui/src/widgets/avatar.dart'; import '../test_scaffold.dart'; void main() { @@ -19,12 +21,9 @@ void main() { final testWidget = MaterialApp( home: TestScaffold( data: theme, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: FAvatar( - image: FileImage(File('./test/resources/pante.jpg')), - placeholderBuilder: (_) => const Text('MN'), - ), + child: FAvatar( + image: FileImage(File('./test/resources/pante.jpg')), + placeholder: const Text('MN'), ), ), ); @@ -48,6 +47,27 @@ void main() { /// We will not be testing for the fallback behavior due to this issue on flutter /// https://github.com/flutter/flutter/issues/107416 + + testWidgets('$name with raw content', (tester) async { + await tester.pumpWidget( + TestScaffold( + data: theme, + child: FAvatar.raw( + child: Padding( + padding: const EdgeInsets.all(10), + child: FAssets.icons.baby( + colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), + ), + ), + ), + ), + ); + + await expectLater( + find.byType(TestScaffold), + matchesGoldenFile('avatar/$name-raw-content.png'), + ); + }); } }, ); diff --git a/samples/lib/main.dart b/samples/lib/main.dart index fc7c6e686..94dc9c9bd 100644 --- a/samples/lib/main.dart +++ b/samples/lib/main.dart @@ -47,6 +47,10 @@ class _AppRouter extends $_AppRouter { path: '/avatar/default', page: AvatarRoute.page, ), + AutoRoute( + path: '/avatar/raw', + page: AvatarRawRoute.page, + ), AutoRoute( path: '/badge/default', page: BadgeRoute.page, diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index 300948bf7..295b2cb1a 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:auto_route/auto_route.dart'; @@ -5,34 +6,54 @@ import 'package:forui/forui.dart'; import 'package:forui_samples/sample_scaffold.dart'; -final images = { - 'default': const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), - 'error': const NetworkImage(''), -}; - -final placeholders = {'text': const Text('MN')}; +String path(String str) => kIsWeb ? 'assets/$str' : str; @RoutePage() class AvatarPage extends SampleScaffold { - final ImageProvider image; - final Widget? placeholder; - AvatarPage({ @queryParam super.theme, - @queryParam String image = 'default', - @queryParam String child = 'text', - }) : image = images[image] ?? const NetworkImage(''), - placeholder = placeholders[child]; + }); + + @override + Widget child(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FAvatar( + image: AssetImage(path('avatar.png')), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const AssetImage(''), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const AssetImage(''), + ), + ], + ); +} + +@RoutePage() +class AvatarRawPage extends SampleScaffold { + AvatarRawPage({ + @queryParam super.theme, + }); @override - Widget child(BuildContext context) => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FAvatar( - size: 70, - image: image, - placeholderBuilder: placeholder != null ? (_) => placeholder! : null, + Widget child(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FAvatar.raw(), + const SizedBox(width: 10), + FAvatar.raw( + child: FAssets.icons.baby( + colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), ), - ], - ); + ), + const SizedBox(width: 10), + FAvatar.raw(child: const Text('MN')), + ], + ); } From 996ed1d869810da0009022b95e27cd4e7d4749cf Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Thu, 25 Jul 2024 08:05:47 +0000 Subject: [PATCH 04/17] Commit from GitHub Actions (Forui Samples Presubmit) --- samples/lib/widgets/avatar.dart | 58 ++++++++++++++++----------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index 295b2cb1a..51fb558ec 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -16,23 +16,23 @@ class AvatarPage extends SampleScaffold { @override Widget child(BuildContext context) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FAvatar( - image: AssetImage(path('avatar.png')), - placeholder: const Text('MN'), - ), - const SizedBox(width: 10), - FAvatar( - image: const AssetImage(''), - placeholder: const Text('MN'), - ), - const SizedBox(width: 10), - FAvatar( - image: const AssetImage(''), - ), - ], - ); + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FAvatar( + image: AssetImage(path('avatar.png')), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const AssetImage(''), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const AssetImage(''), + ), + ], + ); } @RoutePage() @@ -43,17 +43,17 @@ class AvatarRawPage extends SampleScaffold { @override Widget child(BuildContext context) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FAvatar.raw(), - const SizedBox(width: 10), - FAvatar.raw( - child: FAssets.icons.baby( - colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FAvatar.raw(), + const SizedBox(width: 10), + FAvatar.raw( + child: FAssets.icons.baby( + colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), + ), ), - ), - const SizedBox(width: 10), - FAvatar.raw(child: const Text('MN')), - ], - ); + const SizedBox(width: 10), + FAvatar.raw(child: const Text('MN')), + ], + ); } From 6f94efb0a2165e3619ee3e007667d8ad6bc80850 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Thu, 25 Jul 2024 08:05:51 +0000 Subject: [PATCH 05/17] Commit from GitHub Actions (Forui Presubmit) --- forui/lib/src/widgets/avatar.dart | 7 ++++++- forui/test/src/widgets/avatar_golden_test.dart | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/forui/lib/src/widgets/avatar.dart b/forui/lib/src/widgets/avatar.dart index b594b13b6..344e247a8 100644 --- a/forui/lib/src/widgets/avatar.dart +++ b/forui/lib/src/widgets/avatar.dart @@ -48,7 +48,12 @@ class FAvatar extends StatelessWidget { this.size = 40.0, this.style, super.key, - }) : placeholderBuilder = ((context, style) => child ?? _IconPlaceholder(style: style, size: size,)); + }) : placeholderBuilder = ((context, style) => + child ?? + _IconPlaceholder( + style: style, + size: size, + )); @override Widget build(BuildContext context) { diff --git a/forui/test/src/widgets/avatar_golden_test.dart b/forui/test/src/widgets/avatar_golden_test.dart index f027dd7f3..d1f2a958f 100644 --- a/forui/test/src/widgets/avatar_golden_test.dart +++ b/forui/test/src/widgets/avatar_golden_test.dart @@ -3,13 +3,11 @@ library; import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:forui/forui.dart'; -import 'package:forui/src/widgets/avatar.dart'; import '../test_scaffold.dart'; void main() { From edb56f1c2868e1e0ea499db547555dbbac5696de Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Thu, 25 Jul 2024 16:06:04 +0800 Subject: [PATCH 06/17] dart analysis fixes --- forui/lib/src/widgets/avatar.dart | 4 ++-- forui/test/src/widgets/avatar_golden_test.dart | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/forui/lib/src/widgets/avatar.dart b/forui/lib/src/widgets/avatar.dart index b594b13b6..a166186cd 100644 --- a/forui/lib/src/widgets/avatar.dart +++ b/forui/lib/src/widgets/avatar.dart @@ -20,12 +20,12 @@ class FAvatar extends StatelessWidget { /// The circle's size. final double size; - /// The fallback widget displayed if [image] fails to load. + /// The fallback widget displayed if image parameter fails to load. /// /// Typically used to display the user's initials using a [Text] widget /// styled with [FAvatarStyle.backgroundColor]. /// - /// Use [image] to display an image; use [placeholderBuilder] for initials. + /// Use image parameter to display an image; use [placeholderBuilder] for initials. final Widget Function(BuildContext, FAvatarStyle) placeholderBuilder; /// Creates an [FAvatar]. diff --git a/forui/test/src/widgets/avatar_golden_test.dart b/forui/test/src/widgets/avatar_golden_test.dart index f027dd7f3..b2e60eb98 100644 --- a/forui/test/src/widgets/avatar_golden_test.dart +++ b/forui/test/src/widgets/avatar_golden_test.dart @@ -3,13 +3,12 @@ library; import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:forui/forui.dart'; -import 'package:forui/src/widgets/avatar.dart'; + import '../test_scaffold.dart'; void main() { From 414d7644afccc21db0aab120726908d1f4e38f38 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Thu, 25 Jul 2024 08:07:51 +0000 Subject: [PATCH 07/17] Commit from GitHub Actions (Forui Presubmit) --- forui/test/src/widgets/avatar_golden_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/forui/test/src/widgets/avatar_golden_test.dart b/forui/test/src/widgets/avatar_golden_test.dart index b2e60eb98..d1f2a958f 100644 --- a/forui/test/src/widgets/avatar_golden_test.dart +++ b/forui/test/src/widgets/avatar_golden_test.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:forui/forui.dart'; - import '../test_scaffold.dart'; void main() { From 1295f4af29c21e98cbdd2723fc070323b726a5e5 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 00:25:35 +0800 Subject: [PATCH 08/17] Update avatar.dart --- samples/lib/widgets/avatar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index 51fb558ec..fb35584ca 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -19,7 +19,7 @@ class AvatarPage extends SampleScaffold { mainAxisAlignment: MainAxisAlignment.center, children: [ FAvatar( - image: AssetImage(path('avatar.png')), + image: AssetImage('avatar.png'), placeholder: const Text('MN'), ), const SizedBox(width: 10), From cc336348afe0f67ba63b72a1f5e73483359347da Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Thu, 25 Jul 2024 16:26:53 +0000 Subject: [PATCH 09/17] Commit from GitHub Actions (Forui Samples Presubmit) --- samples/lib/widgets/avatar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index fb35584ca..360447cfb 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -19,7 +19,7 @@ class AvatarPage extends SampleScaffold { mainAxisAlignment: MainAxisAlignment.center, children: [ FAvatar( - image: AssetImage('avatar.png'), + image: const AssetImage('avatar.png'), placeholder: const Text('MN'), ), const SizedBox(width: 10), From fdc9c5a3293f2b94f5bf73f3ddc27389b98b7578 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 00:36:41 +0800 Subject: [PATCH 10/17] Update avatar.dart --- samples/lib/widgets/avatar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index fb35584ca..51fb558ec 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -19,7 +19,7 @@ class AvatarPage extends SampleScaffold { mainAxisAlignment: MainAxisAlignment.center, children: [ FAvatar( - image: AssetImage('avatar.png'), + image: AssetImage(path('avatar.png')), placeholder: const Text('MN'), ), const SizedBox(width: 10), From 69e204dc74622247937744c55d04676a405d71e0 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 01:07:55 +0800 Subject: [PATCH 11/17] Fixed pr issues --- docs/pages/docs/avatar.mdx | 32 +++--- .../lib/src/widgets/{ => avatar}/avatar.dart | 97 ++----------------- .../src/widgets/avatar/avatar_content.dart | 88 +++++++++++++++++ forui/lib/widgets.dart | 2 +- 4 files changed, 115 insertions(+), 104 deletions(-) rename forui/lib/src/widgets/{ => avatar}/avatar.dart (68%) create mode 100644 forui/lib/src/widgets/avatar/avatar_content.dart diff --git a/docs/pages/docs/avatar.mdx b/docs/pages/docs/avatar.mdx index 29ab1a1f0..efe5ec8b4 100644 --- a/docs/pages/docs/avatar.mdx +++ b/docs/pages/docs/avatar.mdx @@ -62,20 +62,28 @@ FAvatar.raw( ```dart - // Raw constructor - without child - FAvatar.raw(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ - // Raw constructor - with child - FAvatar.raw( - child: FAssets.icons.baby( - colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), - ), - ), + // Raw constructor - without child + FAvatar.raw(), + + const SizedBox(width: 10), + + // Raw constructor - with child + FAvatar.raw( + child: FAssets.icons.baby( + colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), + ), + ), + + const SizedBox(width: 10), - // Raw constructor - with text - FAvatar.raw( - child: const Text('MN'), - ), + // Raw constructor - with text + FAvatar.raw(child: const Text('MN')), + ], + ); ``` diff --git a/forui/lib/src/widgets/avatar.dart b/forui/lib/src/widgets/avatar/avatar.dart similarity index 68% rename from forui/lib/src/widgets/avatar.dart rename to forui/lib/src/widgets/avatar/avatar.dart index 803fcd407..8d0420ece 100644 --- a/forui/lib/src/widgets/avatar.dart +++ b/forui/lib/src/widgets/avatar/avatar.dart @@ -5,6 +5,8 @@ import 'package:meta/meta.dart'; import 'package:forui/forui.dart'; +part 'avatar_content.dart'; + /// An image element with a fallback for representing the user. /// /// use image property to provide a profile image displayed within the circle. @@ -31,22 +33,22 @@ class FAvatar extends StatelessWidget { /// Creates an [FAvatar]. FAvatar({ required ImageProvider image, - Widget? placeholder, - this.size = 40.0, this.style, + this.size = 40.0, + Widget? placeholder, super.key, }) : placeholderBuilder = ((context, style) => _AvatarContent( + image: image, style: style, size: size, - image: image, placeholder: placeholder, )); /// Creates a [FAvatar] with custom child. FAvatar.raw({ Widget? child, - this.size = 40.0, this.style, + this.size = 40.0, super.key, }) : placeholderBuilder = ((context, style) => child ?? @@ -168,90 +170,3 @@ final class FAvatarStyle with Diagnosticable { @override int get hashCode => backgroundColor.hashCode ^ foregroundColor.hashCode ^ fadeInDuration.hashCode ^ text.hashCode; } - -class _AvatarContent extends StatelessWidget { - final FAvatarStyle? style; - - /// The circle's size. - final double size; - - /// The profile image displayed within the circle. - /// - /// If the user's initials are used, use [placeholder] instead. - final ImageProvider image; - - /// The fallback widget displayed if [image] fails to load. - /// - /// Typically used to display the user's initials using a [Text] widget - /// styled with [FAvatarStyle.backgroundColor]. - final Widget? placeholder; - - const _AvatarContent({ - required this.style, - required this.size, - required this.image, - this.placeholder, - }); - - @override - Widget build(BuildContext context) { - final style = this.style ?? context.theme.avatarStyle; - - final placeholder = this.placeholder ?? _IconPlaceholder(style: style, size: size); - - return Image( - filterQuality: FilterQuality.medium, - image: image, - errorBuilder: (context, exception, stacktrace) => placeholder, - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded) { - return child; - } - return AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - child: frame == null ? placeholder : child, - ); - }, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; - } - return placeholder; - }, - fit: BoxFit.cover, - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('style', style)) - ..add(DoubleProperty('size', size)) - ..add(DiagnosticsProperty('image', image)); - } -} - -class _IconPlaceholder extends StatelessWidget { - final double size; - final FAvatarStyle? style; - - const _IconPlaceholder({required this.size, this.style}); - - @override - Widget build(BuildContext context) { - final style = this.style ?? context.theme.avatarStyle; - return FAssets.icons.userRound( - height: size / 2, - colorFilter: ColorFilter.mode(style.foregroundColor, BlendMode.srcIn), - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DoubleProperty('size', size)) - ..add(DiagnosticsProperty('style', style)); - } -} diff --git a/forui/lib/src/widgets/avatar/avatar_content.dart b/forui/lib/src/widgets/avatar/avatar_content.dart new file mode 100644 index 000000000..dde373d6b --- /dev/null +++ b/forui/lib/src/widgets/avatar/avatar_content.dart @@ -0,0 +1,88 @@ +part of 'avatar.dart'; + +class _AvatarContent extends StatelessWidget { + /// The profile image displayed within the circle. + /// + /// If the user's initials are used, use [placeholder] instead. + final ImageProvider image; + + final FAvatarStyle? style; + + /// The circle's size. + final double size; + + /// The fallback widget displayed if [image] fails to load. + /// + /// Typically used to display the user's initials using a [Text] widget + /// styled with [FAvatarStyle.backgroundColor]. + final Widget? placeholder; + + const _AvatarContent({ + required this.image, + required this.style, + required this.size, + this.placeholder, + }); + + @override + Widget build(BuildContext context) { + final style = this.style ?? context.theme.avatarStyle; + + final placeholder = this.placeholder ?? _IconPlaceholder(style: style, size: size); + + return Image( + filterQuality: FilterQuality.medium, + image: image, + errorBuilder: (context, exception, stacktrace) => placeholder, + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: frame == null ? placeholder : child, + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return placeholder; + }, + fit: BoxFit.cover, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('style', style)) + ..add(DoubleProperty('size', size)) + ..add(DiagnosticsProperty('image', image)); + } +} + +class _IconPlaceholder extends StatelessWidget { + final double size; + final FAvatarStyle? style; + + const _IconPlaceholder({required this.size, this.style}); + + @override + Widget build(BuildContext context) { + final style = this.style ?? context.theme.avatarStyle; + return FAssets.icons.userRound( + height: size / 2, + colorFilter: ColorFilter.mode(style.foregroundColor, BlendMode.srcIn), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DoubleProperty('size', size)) + ..add(DiagnosticsProperty('style', style)); + } +} diff --git a/forui/lib/widgets.dart b/forui/lib/widgets.dart index 985949896..88561dc9c 100644 --- a/forui/lib/widgets.dart +++ b/forui/lib/widgets.dart @@ -19,7 +19,7 @@ library forui.widgets; import 'package:forui/forui.dart'; export 'src/widgets/alert/alert.dart' hide Variant; -export 'src/widgets/avatar.dart'; +export 'src/widgets/avatar/avatar.dart'; export 'src/widgets/badge/badge.dart' hide Variant; export 'src/widgets/bottom_navigation_bar/bottom_navigation_bar.dart'; export 'src/widgets/button/button.dart' hide Variant; From 3365b4c14fdf2a4a13b3dbf6a5c097cd4250fda9 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 01:14:03 +0800 Subject: [PATCH 12/17] Removed center widget --- forui/lib/src/widgets/avatar/avatar.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/forui/lib/src/widgets/avatar/avatar.dart b/forui/lib/src/widgets/avatar/avatar.dart index 8d0420ece..d63a8558e 100644 --- a/forui/lib/src/widgets/avatar/avatar.dart +++ b/forui/lib/src/widgets/avatar/avatar.dart @@ -62,6 +62,7 @@ class FAvatar extends StatelessWidget { final style = this.style ?? context.theme.avatarStyle; return Container( + alignment: Alignment.center, height: size, width: size, decoration: BoxDecoration( @@ -69,11 +70,9 @@ class FAvatar extends StatelessWidget { shape: BoxShape.circle, ), clipBehavior: Clip.hardEdge, - child: Center( - child: DefaultTextStyle( - style: style.text, - child: placeholderBuilder(context, style), - ), + child: DefaultTextStyle( + style: style.text, + child: placeholderBuilder(context, style), ), ); } From 8d6cacb0fa9660c1cefbf7744a623a97c574b3a1 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 12:24:33 +0800 Subject: [PATCH 13/17] Apply suggestions from code review Co-authored-by: Joe Kawai --- docs/pages/docs/avatar.mdx | 3 --- forui/lib/src/widgets/avatar/avatar_content.dart | 13 +------------ 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/docs/pages/docs/avatar.mdx b/docs/pages/docs/avatar.mdx index efe5ec8b4..61a5abd53 100644 --- a/docs/pages/docs/avatar.mdx +++ b/docs/pages/docs/avatar.mdx @@ -65,10 +65,8 @@ FAvatar.raw( Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - // Raw constructor - without child FAvatar.raw(), - const SizedBox(width: 10), // Raw constructor - with child @@ -77,7 +75,6 @@ FAvatar.raw( colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), ), ), - const SizedBox(width: 10), // Raw constructor - with text diff --git a/forui/lib/src/widgets/avatar/avatar_content.dart b/forui/lib/src/widgets/avatar/avatar_content.dart index dde373d6b..f7bb0e3cc 100644 --- a/forui/lib/src/widgets/avatar/avatar_content.dart +++ b/forui/lib/src/widgets/avatar/avatar_content.dart @@ -1,20 +1,9 @@ part of 'avatar.dart'; class _AvatarContent extends StatelessWidget { - /// The profile image displayed within the circle. - /// - /// If the user's initials are used, use [placeholder] instead. - final ImageProvider image; - final FAvatarStyle? style; - - /// The circle's size. + final ImageProvider image; final double size; - - /// The fallback widget displayed if [image] fails to load. - /// - /// Typically used to display the user's initials using a [Text] widget - /// styled with [FAvatarStyle.backgroundColor]. final Widget? placeholder; const _AvatarContent({ From c284986f62d52dc1fd31cb7be0a8350d673d921c Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 14:07:20 +0800 Subject: [PATCH 14/17] Added more examples to Avatar docs, fixed BoxFit.cover issue --- docs/pages/docs/avatar.mdx | 68 +++++++++++++----- forui/lib/src/widgets/avatar/avatar.dart | 28 +++----- .../src/widgets/avatar/avatar_content.dart | 2 + .../golden/avatar/zinc-dark-with-image.png | Bin 7606 -> 7916 bytes .../golden/avatar/zinc-light-with-image.png | Bin 7478 -> 7864 bytes samples/lib/main.dart | 4 ++ samples/lib/widgets/avatar.dart | 22 ++++++ 7 files changed, 89 insertions(+), 35 deletions(-) diff --git a/docs/pages/docs/avatar.mdx b/docs/pages/docs/avatar.mdx index 61a5abd53..b917a76dc 100644 --- a/docs/pages/docs/avatar.mdx +++ b/docs/pages/docs/avatar.mdx @@ -13,19 +13,22 @@ An image element with a fallback for representing the user. Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + // With Valid image. FAvatar( image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), placeholder: const Text('MN'), ), const SizedBox(width: 10), + + // With Invalid image and placeholder. FAvatar( image: const NetworkImage(''), placeholder: const Text('MN'), ), const SizedBox(width: 10), - FAvatar( - image: const NetworkImage(''), - ), + + // With Invalid image without placeholder. + FAvatar(image: const NetworkImage('')), ], ); ``` @@ -37,25 +40,54 @@ An image element with a fallback for representing the user. ### `FAvatar(...)` ```dart -// Default constructor - Valid image FAvatar( image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), placeholder: const Text('MN'), -), +); +``` -// Default constructor - Invalid image with placeholder -FAvatar( - image: const NetworkImage(''), - placeholder: const Text('MN'), -), +### `FAvatar.raw(...)` -// Default constructor - Invalid image without placeholder +```dart +// Raw constructor - with icon FAvatar.raw( - image: const NetworkImage(''), -), + child: FAssets.icons.baby( + colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), + ), +); + +// Raw constructor - with text +FAvatar.raw(child: const Text('MN')); ``` -### `FAvatar.raw(...)` +## Examples + +### Invalid image + + + + + + ```dart + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // With placeholder widget. + FAvatar( + image: const AssetImage(''), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + + // Without placeholder widget. + FAvatar(image: const AssetImage('')), + ], + ); + ``` + + + +### With Child widget without fallback @@ -65,19 +97,19 @@ FAvatar.raw( Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - // Raw constructor - without child + // Raw constructor - without child. FAvatar.raw(), const SizedBox(width: 10), - // Raw constructor - with child + // Raw constructor - with child. FAvatar.raw( child: FAssets.icons.baby( - colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), + colorFilter: ColorFilter.mode(theme.colorScheme.mutedForeground, BlendMode.srcIn), ), ), const SizedBox(width: 10), - // Raw constructor - with text + // Raw constructor - with text. FAvatar.raw(child: const Text('MN')), ], ); diff --git a/forui/lib/src/widgets/avatar/avatar.dart b/forui/lib/src/widgets/avatar/avatar.dart index d63a8558e..d1eb4630f 100644 --- a/forui/lib/src/widgets/avatar/avatar.dart +++ b/forui/lib/src/widgets/avatar/avatar.dart @@ -27,8 +27,8 @@ class FAvatar extends StatelessWidget { /// Typically used to display the user's initials using a [Text] widget /// styled with [FAvatarStyle.backgroundColor]. /// - /// Use image parameter to display an image; use [placeholderBuilder] for initials. - final Widget Function(BuildContext, FAvatarStyle) placeholderBuilder; + /// Use image parameter to display an image; use [placeholder] for initials. + final Widget placeholder; /// Creates an [FAvatar]. FAvatar({ @@ -37,12 +37,12 @@ class FAvatar extends StatelessWidget { this.size = 40.0, Widget? placeholder, super.key, - }) : placeholderBuilder = ((context, style) => _AvatarContent( - image: image, - style: style, - size: size, - placeholder: placeholder, - )); + }) : placeholder = _AvatarContent( + image: image, + style: style, + size: size, + placeholder: placeholder, + ); /// Creates a [FAvatar] with custom child. FAvatar.raw({ @@ -50,12 +50,7 @@ class FAvatar extends StatelessWidget { this.style, this.size = 40.0, super.key, - }) : placeholderBuilder = ((context, style) => - child ?? - _IconPlaceholder( - style: style, - size: size, - )); + }) : placeholder = child ?? _IconPlaceholder(style: style, size: size); @override Widget build(BuildContext context) { @@ -72,7 +67,7 @@ class FAvatar extends StatelessWidget { clipBehavior: Clip.hardEdge, child: DefaultTextStyle( style: style.text, - child: placeholderBuilder(context, style), + child: placeholder, ), ); } @@ -82,8 +77,7 @@ class FAvatar extends StatelessWidget { super.debugFillProperties(properties); properties ..add(DoubleProperty('size', size)) - ..add(DiagnosticsProperty('style', style)) - ..add(ObjectFlagProperty.has('placeholderBuilder', placeholderBuilder)); + ..add(DiagnosticsProperty('style', style)); } } diff --git a/forui/lib/src/widgets/avatar/avatar_content.dart b/forui/lib/src/widgets/avatar/avatar_content.dart index f7bb0e3cc..93410ccb5 100644 --- a/forui/lib/src/widgets/avatar/avatar_content.dart +++ b/forui/lib/src/widgets/avatar/avatar_content.dart @@ -20,6 +20,8 @@ class _AvatarContent extends StatelessWidget { final placeholder = this.placeholder ?? _IconPlaceholder(style: style, size: size); return Image( + height: size, + width: size, filterQuality: FilterQuality.medium, image: image, errorBuilder: (context, exception, stacktrace) => placeholder, diff --git a/forui/test/golden/avatar/zinc-dark-with-image.png b/forui/test/golden/avatar/zinc-dark-with-image.png index fa2fc93919c5fd098d970bfce6c50dc2b82d70c5..0b7db1fb4ca256c9c8655a3eb18a4a62580fdb91 100644 GIT binary patch literal 7916 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

(5V(7!w&wD066)`MPJO&yE3p?jFo~RQ&f;- zG6gavrS=w}LF{TDq!aP^%Wm;pWLR$2OgJ1Xd{jr^R7y&(3!f4=P<~}9qX@ThEwBO{ z1D8JA_Cr*F2g*8k4!#p$vxzq|^;@+JQc?o*?~H`NXPq>WN<&@@U5QY%cj_1FiHqiJ z+&s7dt=r0wW=wDW%YoB1iRI|YNid5h2JLUKNT|W6STi=7DR@+tQ4G&g?g*slPD1JT z5)+$iVi`3vk6k_SEVrp(c1sKMdvLh~QQ98p- ziQt*ZA;ZNr(jrYc`Svn%d|dJrPbAh3Jka=DEWzX^g0v+MiB_bSAu8~mrj09 z1%|qLlcecU>p47>w=3tZ$t8eY50pYhKf;48{~_f0?9nG;suzAOLu_nh6lG@t4*ga; zCKAgtyWdR*=VDbP8_8>21`hPU<{6z>bGkW@#VM)x;SkJRTaR6S2PXmT)3bY>rM@r6~d z>Tbi{=VOGu=XJ9NSzszLB(%hlBK0kK0-xMs9)ReJJx{ZXAuqqErYvBYkHT|cmK^Entn!W zMnLv~U*4u4D0uxDfhVk?KrB`%~_xE@9f7H>Pyp>$L`+$QC(Y>*ZT12TKvG~oo zQqyP!(RR7iZz%#DOH(eiOiFgxVOLNSnf6MS9d55=f7tWlD?i83q@)$)g;quY6V4dM zhE!b50$t=Lt4624cB+##M)qp*57@glRBvFV1j6#=_*JWaZaT$(?kARY?CIom2(4*M z&2&bzw@9~md5^58%~hH~fH+RDz96!J3p5L8EVG{GzN|`b=_<*zvcWRV+SB|UcW-FV z!2y7EJIX{`0;Y7S^g$KneQjsP{N=nlk(FG$u#nPQ3P#GSCNGJ4<;19@=6Hy!eKMXL zXW<&qLsZtXR*UWGs4h!l21O;|An2$7bQCem_>VEJYo2`?!wQo1YcJ0yeUV4D324~N zO!d?JZFdh|`>^K?%Zl+Od2ZM_p3f_-zDL^F+~jbjwfGW?<E#Y3LyDfKd*=vlz!=QrpTkX>f-CrubIynF^T6LFO1LZK@J}dAVOD^#R(H z>W9WR+m3!_kAl5RU0{qwZh|m2JG**VNtJOo36GzkR0UWP-tb;Hsp!aDjxTZJ6|6a2 z!OP-FlX2;U5Z5N$aGFMeC; zz#?SelI!8pxM6RCd=1axbyQHZ{l;1_sY>vRuRgRFo!9pgwet4HmSimF81^e*90?-o zm!Ii6$KV4YD~!Q&g@E$|q zy29l+;V%zM_{cmk1&)-b$XCzQ@p2|QkBB!mqH(Pg=L8-A3~1SY7Gjg8$6A$cSLybH z1V`7BH3};uc&ONj=8n@n})b4~@m! zwjiM60Ki=c__Lh=K>w~k0RTVP^?VdR`r#u9J_f_bOz=OF5zWA@y$ueH&3FH5zX%2( NPDPz;K5^~a{|8kGXX5|> literal 7606 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 HUw-{RUQQ~X diff --git a/forui/test/golden/avatar/zinc-light-with-image.png b/forui/test/golden/avatar/zinc-light-with-image.png index d2b7cd860265d40892a2d0eb6c78c5d1707413d8..86cfa19ec29098f34636e12bdd138077bd14de2e 100644 GIT binary patch literal 7864 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 diff --git a/samples/lib/main.dart b/samples/lib/main.dart index 94dc9c9bd..70b41ba9e 100644 --- a/samples/lib/main.dart +++ b/samples/lib/main.dart @@ -51,6 +51,10 @@ class _AppRouter extends $_AppRouter { path: '/avatar/raw', page: AvatarRawRoute.page, ), + AutoRoute( + path: '/avatar/invalid', + page: AvatarInvalidRoute.page, + ), AutoRoute( path: '/badge/default', page: BadgeRoute.page, diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index 51fb558ec..89ad25cea 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -57,3 +57,25 @@ class AvatarRawPage extends SampleScaffold { ], ); } + +@RoutePage() +class AvatarInvalidPage extends SampleScaffold { + AvatarInvalidPage({ + @queryParam super.theme, + }); + + @override + Widget child(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FAvatar( + image: const AssetImage(''), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const AssetImage(''), + ), + ], + ); +} \ No newline at end of file From beebd9b0124b39cc2b21ce90d03ae7d932b77cea Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 14:07:35 +0800 Subject: [PATCH 15/17] Update avatar.dart --- samples/lib/widgets/avatar.dart | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index 89ad25cea..120d1396c 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -66,16 +66,16 @@ class AvatarInvalidPage extends SampleScaffold { @override Widget child(BuildContext context) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FAvatar( - image: const AssetImage(''), - placeholder: const Text('MN'), - ), - const SizedBox(width: 10), - FAvatar( - image: const AssetImage(''), - ), - ], - ); -} \ No newline at end of file + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FAvatar( + image: const AssetImage(''), + placeholder: const Text('MN'), + ), + const SizedBox(width: 10), + FAvatar( + image: const AssetImage(''), + ), + ], + ); +} From 87ed30da4588efa0200eb99c3d5e00f8d9092ef5 Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 15:35:43 +0800 Subject: [PATCH 16/17] Final changes? --- docs/pages/docs/avatar.mdx | 18 +++++++------- forui/lib/src/widgets/avatar/avatar.dart | 24 +++++++++---------- .../src/widgets/avatar/avatar_content.dart | 24 +++++++++---------- .../test/src/widgets/avatar_golden_test.dart | 2 +- samples/lib/widgets/avatar.dart | 6 ++--- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/pages/docs/avatar.mdx b/docs/pages/docs/avatar.mdx index b917a76dc..39476361a 100644 --- a/docs/pages/docs/avatar.mdx +++ b/docs/pages/docs/avatar.mdx @@ -16,18 +16,18 @@ An image element with a fallback for representing the user. // With Valid image. FAvatar( image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), - placeholder: const Text('MN'), + fallback: const Text('MN'), ), const SizedBox(width: 10), - // With Invalid image and placeholder. + // With Invalid image and fallback. FAvatar( image: const NetworkImage(''), - placeholder: const Text('MN'), + fallback: const Text('MN'), ), const SizedBox(width: 10), - // With Invalid image without placeholder. + // With Invalid image without fallback. FAvatar(image: const NetworkImage('')), ], ); @@ -42,7 +42,7 @@ An image element with a fallback for representing the user. ```dart FAvatar( image: const NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'), - placeholder: const Text('MN'), + fallback: const Text('MN'), ); ``` @@ -72,14 +72,14 @@ FAvatar.raw(child: const Text('MN')); Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - // With placeholder widget. + // With fallback widget. FAvatar( image: const AssetImage(''), - placeholder: const Text('MN'), + fallback: const Text('MN'), ), const SizedBox(width: 10), - // Without placeholder widget. + // Without fallback widget. FAvatar(image: const AssetImage('')), ], ); @@ -87,7 +87,7 @@ FAvatar.raw(child: const Text('MN')); -### With Child widget without fallback +### Without fallback diff --git a/forui/lib/src/widgets/avatar/avatar.dart b/forui/lib/src/widgets/avatar/avatar.dart index d1eb4630f..b225c597a 100644 --- a/forui/lib/src/widgets/avatar/avatar.dart +++ b/forui/lib/src/widgets/avatar/avatar.dart @@ -11,9 +11,9 @@ part 'avatar_content.dart'; /// /// use image property to provide a profile image displayed within the circle. /// Typically used with a user's profile image. If the image fails to load, -/// the placeholderBuilder property is used instead, which usually displays the user's initials. +/// the fallback widget is used instead, which usually displays the user's initials. /// -/// If the user's profile has no image, use the placeholderBuilder property to provide +/// If the user's profile has no image, use the fallback property to provide /// the initials using a [Text] widget styled with [FAvatarStyle.backgroundColor]. class FAvatar extends StatelessWidget { /// The style. Defaults to [FThemeData.avatarStyle]. @@ -27,21 +27,21 @@ class FAvatar extends StatelessWidget { /// Typically used to display the user's initials using a [Text] widget /// styled with [FAvatarStyle.backgroundColor]. /// - /// Use image parameter to display an image; use [placeholder] for initials. - final Widget placeholder; + /// Use image parameter to display an image; use [fallback] for initials. + final Widget fallback; /// Creates an [FAvatar]. FAvatar({ required ImageProvider image, this.style, this.size = 40.0, - Widget? placeholder, + Widget? fallback, super.key, - }) : placeholder = _AvatarContent( + }) : fallback = _AvatarContent( image: image, style: style, size: size, - placeholder: placeholder, + fallback: fallback, ); /// Creates a [FAvatar] with custom child. @@ -50,7 +50,7 @@ class FAvatar extends StatelessWidget { this.style, this.size = 40.0, super.key, - }) : placeholder = child ?? _IconPlaceholder(style: style, size: size); + }) : fallback = child ?? _Placeholder(style: style, size: size); @override Widget build(BuildContext context) { @@ -67,7 +67,7 @@ class FAvatar extends StatelessWidget { clipBehavior: Clip.hardEdge, child: DefaultTextStyle( style: style.text, - child: placeholder, + child: fallback, ), ); } @@ -83,16 +83,16 @@ class FAvatar extends StatelessWidget { /// [FAvatar]'s style. final class FAvatarStyle with Diagnosticable { - /// The placeholder's background color. + /// The fallback's background color. final Color backgroundColor; - /// The placeholder's color. + /// The fallback's color. final Color foregroundColor; /// Duration for the transition animation. final Duration fadeInDuration; - /// The text style for the placeholder text. + /// The text style for the fallback text. final TextStyle text; /// Creates a [FAvatarStyle]. diff --git a/forui/lib/src/widgets/avatar/avatar_content.dart b/forui/lib/src/widgets/avatar/avatar_content.dart index 93410ccb5..e9e9096c8 100644 --- a/forui/lib/src/widgets/avatar/avatar_content.dart +++ b/forui/lib/src/widgets/avatar/avatar_content.dart @@ -1,44 +1,44 @@ part of 'avatar.dart'; class _AvatarContent extends StatelessWidget { - final FAvatarStyle? style; final ImageProvider image; final double size; - final Widget? placeholder; + final FAvatarStyle? style; + final Widget? fallback; const _AvatarContent({ required this.image, - required this.style, required this.size, - this.placeholder, + this.style, + this.fallback, }); @override Widget build(BuildContext context) { final style = this.style ?? context.theme.avatarStyle; - final placeholder = this.placeholder ?? _IconPlaceholder(style: style, size: size); + final fallback = this.fallback ?? _Placeholder(style: style, size: size); return Image( height: size, width: size, filterQuality: FilterQuality.medium, image: image, - errorBuilder: (context, exception, stacktrace) => placeholder, + errorBuilder: (context, exception, stacktrace) => fallback, frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { if (wasSynchronouslyLoaded) { return child; } return AnimatedSwitcher( duration: const Duration(milliseconds: 500), - child: frame == null ? placeholder : child, + child: frame == null ? fallback : child, ); }, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) { return child; } - return placeholder; + return fallback; }, fit: BoxFit.cover, ); @@ -48,17 +48,17 @@ class _AvatarContent extends StatelessWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty('style', style)) + ..add(DiagnosticsProperty('image', image)) ..add(DoubleProperty('size', size)) - ..add(DiagnosticsProperty('image', image)); + ..add(DiagnosticsProperty('style', style)); } } -class _IconPlaceholder extends StatelessWidget { +class _Placeholder extends StatelessWidget { final double size; final FAvatarStyle? style; - const _IconPlaceholder({required this.size, this.style}); + const _Placeholder({required this.size, this.style}); @override Widget build(BuildContext context) { diff --git a/forui/test/src/widgets/avatar_golden_test.dart b/forui/test/src/widgets/avatar_golden_test.dart index d1f2a958f..4c90f00b5 100644 --- a/forui/test/src/widgets/avatar_golden_test.dart +++ b/forui/test/src/widgets/avatar_golden_test.dart @@ -21,7 +21,7 @@ void main() { data: theme, child: FAvatar( image: FileImage(File('./test/resources/pante.jpg')), - placeholder: const Text('MN'), + fallback: const Text('MN'), ), ), ); diff --git a/samples/lib/widgets/avatar.dart b/samples/lib/widgets/avatar.dart index 120d1396c..30a253906 100644 --- a/samples/lib/widgets/avatar.dart +++ b/samples/lib/widgets/avatar.dart @@ -20,12 +20,12 @@ class AvatarPage extends SampleScaffold { children: [ FAvatar( image: AssetImage(path('avatar.png')), - placeholder: const Text('MN'), + fallback: const Text('MN'), ), const SizedBox(width: 10), FAvatar( image: const AssetImage(''), - placeholder: const Text('MN'), + fallback: const Text('MN'), ), const SizedBox(width: 10), FAvatar( @@ -70,7 +70,7 @@ class AvatarInvalidPage extends SampleScaffold { children: [ FAvatar( image: const AssetImage(''), - placeholder: const Text('MN'), + fallback: const Text('MN'), ), const SizedBox(width: 10), FAvatar( From f21bbef86ca4f2135cf2f3887ff75d851053ebff Mon Sep 17 00:00:00 2001 From: Daviiddoo Date: Fri, 26 Jul 2024 15:36:01 +0800 Subject: [PATCH 17/17] Update forui/lib/src/widgets/avatar/avatar.dart Co-authored-by: Joe Kawai --- forui/lib/src/widgets/avatar/avatar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forui/lib/src/widgets/avatar/avatar.dart b/forui/lib/src/widgets/avatar/avatar.dart index b225c597a..2dee94eb2 100644 --- a/forui/lib/src/widgets/avatar/avatar.dart +++ b/forui/lib/src/widgets/avatar/avatar.dart @@ -44,7 +44,7 @@ class FAvatar extends StatelessWidget { fallback: fallback, ); - /// Creates a [FAvatar] with custom child. + /// Creates a [FAvatar] without a fallback. FAvatar.raw({ Widget? child, this.style,