Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avatar Improvements, documentation changes #118

Merged
merged 21 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0a23c6c
improvements to avatar, docs revamp
Daviiddoo Jul 24, 2024
6a0ac1f
Removed references to constructor properties
Daviiddoo Jul 24, 2024
09136f9
Fixed pr issues, need to verify if path works when deployed
Daviiddoo Jul 25, 2024
996ed1d
Commit from GitHub Actions (Forui Samples Presubmit)
Daviiddoo Jul 25, 2024
6f94efb
Commit from GitHub Actions (Forui Presubmit)
Daviiddoo Jul 25, 2024
edb56f1
dart analysis fixes
Daviiddoo Jul 25, 2024
b1c2395
Merge branch 'fix/avatar' of https://github.com/forus-labs/forui into…
Daviiddoo Jul 25, 2024
414d764
Commit from GitHub Actions (Forui Presubmit)
Daviiddoo Jul 25, 2024
1295f4a
Update avatar.dart
Daviiddoo Jul 25, 2024
f3970f2
Merge branch 'fix/avatar' of https://github.com/forus-labs/forui into…
Daviiddoo Jul 25, 2024
cc33634
Commit from GitHub Actions (Forui Samples Presubmit)
Daviiddoo Jul 25, 2024
fdc9c5a
Update avatar.dart
Daviiddoo Jul 25, 2024
e5cfa7a
Merge branch 'fix/avatar' of https://github.com/forus-labs/forui into…
Daviiddoo Jul 25, 2024
69e204d
Fixed pr issues
Daviiddoo Jul 25, 2024
3365b4c
Removed center widget
Daviiddoo Jul 25, 2024
1b35823
Merge branch 'main' into fix/avatar
Daviiddoo Jul 25, 2024
8d6cacb
Apply suggestions from code review
Daviiddoo Jul 26, 2024
c284986
Added more examples to Avatar docs, fixed BoxFit.cover issue
Daviiddoo Jul 26, 2024
beebd9b
Update avatar.dart
Daviiddoo Jul 26, 2024
87ed30d
Final changes?
Daviiddoo Jul 26, 2024
f21bbef
Update forui/lib/src/widgets/avatar/avatar.dart
Daviiddoo Jul 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 65 additions & 8 deletions docs/pages/docs/avatar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,28 @@ An image element with a fallback for representing the user.

<Tabs items={['Preview', 'Code']}>
<Tabs.Tab>
<Widget name='avatar' query={{}}/>
<Widget name='avatar' variant='default' query={{}}/>
</Tabs.Tab>
<Tabs.Tab>
```dart
FAvatar(
image: const NetworkImage('https://picsum.photos/250?image=9'),
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(''),
),
],
);
```
</Tabs.Tab>
</Tabs>
Expand All @@ -23,10 +37,53 @@ An image element with a fallback for representing the user.
### `FAvatar(...)`

```dart
// Default constructor - Valid image
FAvatar(
image: const NetworkImage('https://picsum.photos/250?image=9'),
size: 60,
placeholderBuilder: (_) => const Text('MN'),
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'),
),
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved

// Default constructor - Invalid image without placeholder
FAvatar.raw(
image: const NetworkImage(''),
),
```
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved

### `FAvatar.raw(...)`
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved
<Tabs items={['Preview', 'Code']}>
<Tabs.Tab>
<Widget name='avatar' variant='raw' query={{}}/>
</Tabs.Tab>
<Tabs.Tab>
```dart
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [

// 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')),
],
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved
);
```
</Tabs.Tab>
</Tabs>
Original file line number Diff line number Diff line change
Expand Up @@ -5,87 +5,74 @@ 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.
/// 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].
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.
/// 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.
final Widget Function(BuildContext)? placeholderBuilder;
/// Use image parameter to display an image; use [placeholderBuilder] for initials.
final Widget Function(BuildContext, FAvatarStyle) placeholderBuilder;

/// Creates an [FAvatar].
const FAvatar({
required this.image,
FAvatar({
required ImageProvider image,
this.style,
this.size = 40.0,
this.placeholderBuilder,
Widget? placeholder,
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved
super.key,
});
}) : placeholderBuilder = ((context, style) => _AvatarContent(
image: image,
style: style,
size: size,
placeholder: placeholder,
));

/// Creates a [FAvatar] with custom child.
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved
FAvatar.raw({
Widget? child,
this.style,
this.size = 40.0,
super.key,
}) : placeholderBuilder = ((context, style) =>
child ??
_IconPlaceholder(
style: style,
size: size,
));

@override
Widget build(BuildContext context) {
final style = this.style ?? context.theme.avatarStyle;

return Container(
alignment: Alignment.center,
height: size,
width: size,
decoration: BoxDecoration(
color: style.backgroundColor,
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: DefaultTextStyle(
style: style.text,
child: placeholderBuilder(context, style),
),
);
}
Expand All @@ -94,7 +81,6 @@ 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));
Expand All @@ -106,22 +92,27 @@ 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;

/// The text style for the placeholder text.
final TextStyle text;

/// Creates a [FAvatarStyle].
FAvatarStyle({
const FAvatarStyle({
required this.backgroundColor,
required this.foregroundColor,
required this.fadeInDuration,
required this.text,
});

/// 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,
Expand All @@ -144,11 +135,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,
);
Expand All @@ -158,6 +151,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));
}
Expand All @@ -168,31 +162,10 @@ 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;
}

class _Placeholder extends StatelessWidget {
final double size;

const _Placeholder({required this.size});

@override
Widget build(BuildContext context) {
final style = context.theme;

return FAssets.icons.userRound(
height: size / 2,
colorFilter: ColorFilter.mode(style.colorScheme.mutedForeground, BlendMode.srcIn),
);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DoubleProperty('size', size));
}
int get hashCode => backgroundColor.hashCode ^ foregroundColor.hashCode ^ fadeInDuration.hashCode ^ text.hashCode;
}
88 changes: 88 additions & 0 deletions forui/lib/src/widgets/avatar/avatar_content.dart
Original file line number Diff line number Diff line change
@@ -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;
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved

const _AvatarContent({
required this.image,
required this.style,
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved
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 {
Daviiddoo marked this conversation as resolved.
Show resolved Hide resolved
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));
}
}
Loading