Skip to content

Commit

Permalink
Add badge (#24)
Browse files Browse the repository at this point in the history
* Fix PR issues

* Adjust separator default values

* Tweak separator even more

* Tidy up separator implementation

* Refactor widget styles

* Add golden tests

* Further refine golden tests

* Commit from GitHub Actions (Forui Presubmit)

* Add badge

* Fix diagnostic_describe_all_properties lint

* Tidy up badge

* Commit from GitHub Actions (Forui Presubmit)

* Fix PR issues

* Fix PR issues

* Commit from GitHub Actions (Forui Presubmit)

---------

Co-authored-by: Pante <[email protected]>
  • Loading branch information
Pante and Pante authored May 26, 2024
1 parent 18f2bdc commit d6a3245
Show file tree
Hide file tree
Showing 34 changed files with 509 additions and 29 deletions.
2 changes: 1 addition & 1 deletion forui/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46

PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796

Expand Down
31 changes: 18 additions & 13 deletions forui/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,27 @@ class Application extends StatelessWidget {
home: FTheme(
data: FThemes.zinc.light,
child: Scaffold(
backgroundColor: Colors.white,
backgroundColor: FThemes.zinc.light.colorScheme.background,
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
FCard(
title: 'Notification',
subtitle: 'You have 3 unread messages.',
child: const SizedBox(
width: double.infinity,
child: FBox(
text: 'BODY',
child: Center(
child: FBadge.raw(
design: FBadgeVariant.outline,
builder: (context, style) => Padding(
padding: const EdgeInsets.all(50),
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: style.background,
border: Border.all(
color: Colors.blueAccent,
width: 2,
),
),
),
),
],
)
)
),
),
),
),
Expand Down
4 changes: 2 additions & 2 deletions forui/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@ packages:
dependency: "direct dev"
description:
name: flint
sha256: b2a1021a21f01d7e87ce4527d63b9fc7018bc00f3ef7f2fb5a1f88eb97a8790a
sha256: "47d2c84e11bf93fd0d257883a4aa6483d094b70672ec26e33a56faf8e4c04671"
url: "https://pub.dev"
source: hosted
version: "2.9.0"
version: "2.10.0"
flutter:
dependency: "direct main"
description: flutter
Expand Down
1 change: 1 addition & 0 deletions forui/lib/forui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export 'src/theme/themes.dart';

// Widgets
export 'src/widgets/box.dart';
export 'src/widgets/badge/badge.dart' hide FBadgeContent;
export 'src/widgets/card/card.dart' hide FCardContent;
export 'src/widgets/separator.dart';
10 changes: 9 additions & 1 deletion forui/lib/src/theme/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ class FTheme extends StatelessWidget {
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<FThemeData>('data', data, showName: false));
properties
..add(DiagnosticsProperty<FThemeData>('data', data, showName: false))
..add(EnumProperty<TextDirection?>('textDirection', textDirection));
}

}
Expand All @@ -66,6 +68,12 @@ class _InheritedTheme extends InheritedWidget {

@override
bool updateShouldNotify(covariant _InheritedTheme old) => data != old.data;

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<FThemeData>('data', data));
}
}

/// Provides functions for accessing the current [FThemeData].
Expand Down
10 changes: 10 additions & 0 deletions forui/lib/src/theme/theme_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class FThemeData with Diagnosticable {
/// The overarching style.
final FStyle style;

/// The chip styles.
final FBadgeStyles badgeStyles;

/// The box style.
final FBoxStyle boxStyle;

Expand All @@ -27,6 +30,7 @@ class FThemeData with Diagnosticable {
required this.colorScheme,
required this.font,
required this.style,
required this.badgeStyles,
required this.boxStyle,
required this.cardStyle,
required this.separatorStyles,
Expand All @@ -38,6 +42,7 @@ class FThemeData with Diagnosticable {
required this.font,
required this.style,
}):
badgeStyles = FBadgeStyles.inherit(colorScheme: colorScheme, style: style),
boxStyle = FBoxStyle.inherit(colorScheme: colorScheme),
cardStyle = FCardStyle.inherit(colorScheme: colorScheme, style: style),
separatorStyles = (
Expand All @@ -58,13 +63,15 @@ class FThemeData with Diagnosticable {
FColorScheme? colorScheme,
FFont? font,
FStyle? style,
FBadgeStyles? badgeStyles,
FBoxStyle? boxStyle,
FCardStyle? cardStyle,
({FSeparatorStyle horizontal, FSeparatorStyle vertical})? separatorStyles,
}) => FThemeData(
colorScheme: colorScheme ?? this.colorScheme,
font: font ?? this.font,
style: style ?? this.style,
badgeStyles: badgeStyles ?? this.badgeStyles,
boxStyle: boxStyle ?? this.boxStyle,
cardStyle: cardStyle ?? this.cardStyle,
separatorStyles: separatorStyles ?? this.separatorStyles,
Expand All @@ -77,6 +84,7 @@ class FThemeData with Diagnosticable {
..add(DiagnosticsProperty<FColorScheme>('colorScheme', colorScheme, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FFont>('font', font, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FStyle>('style', style, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FBadgeStyles>('badgeStyles', badgeStyles, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FBoxStyle>('boxStyle', boxStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<FCardStyle>('cardStyle', cardStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty<({FSeparatorStyle horizontal, FSeparatorStyle vertical})>('separatorStyles', separatorStyles, level: DiagnosticLevel.debug));
Expand All @@ -90,6 +98,7 @@ class FThemeData with Diagnosticable {
colorScheme == other.colorScheme &&
font == other.font &&
style == other.style &&
badgeStyles == other.badgeStyles &&
boxStyle == other.boxStyle &&
cardStyle == other.cardStyle &&
separatorStyles == other.separatorStyles;
Expand All @@ -99,6 +108,7 @@ class FThemeData with Diagnosticable {
colorScheme.hashCode ^
font.hashCode ^
style.hashCode ^
badgeStyles.hashCode ^
boxStyle.hashCode ^
cardStyle.hashCode ^
separatorStyles.hashCode;
Expand Down
175 changes: 175 additions & 0 deletions forui/lib/src/widgets/badge/badge.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

import 'package:meta/meta.dart';

import 'package:forui/forui.dart';

part 'badge_content.dart';
part 'badge_styles.dart';

/// A badge, or a component that looks like a badge.
class FBadge extends StatelessWidget {

/// The design.
final FBadgeDesign design;

/// A callback for when the badge is pressed.
final void Function(BuildContext)? onPressed;

/// A callback for when the badge is long pressed.
final void Function(BuildContext)? onLongPressed;

/// The builder.
final Widget Function(BuildContext, FBadgeStyle) builder;

/// Creates a [FBadge].
FBadge({required String label, required this.design, this.onPressed, this.onLongPressed, super.key}):
builder = ((context, style) => FBadgeContent(label: label, style: style));

/// Creates a [FBadge].
const FBadge.raw({required this.design, required this.builder, this.onPressed, this.onLongPressed, super.key});

@override
Widget build(BuildContext context) {
final style = switch (design) {
final FBadgeStyle style => style,
FBadgeVariant.primary => context.theme.badgeStyles.primary,
FBadgeVariant.secondary => context.theme.badgeStyles.secondary,
FBadgeVariant.outline => context.theme.badgeStyles.outline,
FBadgeVariant.destructive => context.theme.badgeStyles.destructive,
};

final chip = IntrinsicWidth(
child: IntrinsicHeight(
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: style.border,
width: style.borderWidth,
),
borderRadius: style.borderRadius,
color: style.background,
),
child: builder(context, style),
),
),
);

if (onPressed == null && onLongPressed == null) {
return chip;
}

// TODO: Wrap in FTappable when it's ready.
return chip;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<FBadgeDesign>('design', design))
..add(DiagnosticsProperty<void Function(BuildContext)?>('onPressed', onPressed, defaultValue: null))
..add(DiagnosticsProperty<void Function(BuildContext)?>('onLongPressed', onLongPressed, defaultValue: null))
..add(DiagnosticsProperty<Widget Function(BuildContext, FBadgeStyle)>('builder', builder, defaultValue: null));
}

}

/// The badge design. Either a pre-defined [FBadgeVariant], or a custom [FBadgeStyle].
sealed class FBadgeDesign {}

/// A pre-defined badge variant.
enum FBadgeVariant implements FBadgeDesign {
/// A primary-styled badge.
primary,
/// A secondary-styled badge.
secondary,
/// An outlined badge.
outline,
/// A destructive badge.
destructive,
}

/// A [FBadge]'s style.
final class FBadgeStyle with Diagnosticable implements FBadgeDesign {

/// The background color.
final Color background;

/// The border color.
final Color border;

/// The border radius.
final BorderRadius borderRadius;

/// The border width (thickness). Defaults to 1.
///
/// ## Contract:
/// Throws an [AssertionError] if:
/// * `borderWidth` <= 0.0
/// * `borderWidth` is Nan
final double borderWidth;

/// The button content's style.
final FBadgeContentStyle content;

/// Creates a [FBadgeStyle].
FBadgeStyle({
required this.background,
required this.border,
required this.borderRadius,
required this.content,
this.borderWidth = 1,
}): assert(0 < borderWidth, 'The borderWidth is $borderWidth, but it should be in the range "0 < borderWidth".');

/// Creates a [FBadgeStyle] that inherits its properties from [style].
FBadgeStyle.inherit({
required FStyle style,
required this.background,
required this.border,
required this.content,
}): borderRadius = BorderRadius.circular(100), borderWidth = style.borderWidth;

/// Creates a copy of this [FBadgeStyle] with the given properties replaced.
FBadgeStyle copyWith({
Color? background,
Color? border,
BorderRadius? borderRadius,
double? borderWidth,
FBadgeContentStyle? content,
}) => FBadgeStyle(
background: background ?? this.background,
border: border ?? this.border,
borderRadius: borderRadius ?? this.borderRadius,
borderWidth: borderWidth ?? this.borderWidth,
content: content ?? this.content,
);


@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ColorProperty('background', background))
..add(ColorProperty('border', border))
..add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius))
..add(DoubleProperty('borderWidth', borderWidth))
..add(DiagnosticsProperty<FBadgeContentStyle>('content', content));
}

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is FBadgeStyle &&
runtimeType == other.runtimeType &&
background == other.background &&
border == other.border &&
borderRadius == other.borderRadius &&
borderWidth == other.borderWidth &&
content == other.content;

@override
int get hashCode =>
background.hashCode ^ border.hashCode ^ borderRadius.hashCode ^ borderWidth.hashCode ^ content.hashCode;

}
62 changes: 62 additions & 0 deletions forui/lib/src/widgets/badge/badge_content.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
part of 'badge.dart';

@internal final class FBadgeContent extends StatelessWidget {

final String label;
final FBadgeStyle style;

const FBadgeContent({required this.label, required this.style, super.key});

@override
Widget build(BuildContext context) => Center(
child: Padding(
padding: style.content.padding,
child: Text(label, style: style.content.label.withFont(context.theme.font)),
),
);

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<FBadgeStyle>('style', style))
..add(StringProperty('text', label));
}

}

/// A badge content's style.
final class FBadgeContentStyle with Diagnosticable {

/// The padding.
final EdgeInsets padding;

/// The text.
final TextStyle label;

/// Creates a [FBadgeContentStyle].
FBadgeContentStyle({required this.label, this.padding = const EdgeInsets.symmetric(horizontal: 14, vertical: 2)});

/// Creates a copy of this [FBadgeContentStyle] with the given properties replaced.
FBadgeContentStyle copyWith({TextStyle? label, EdgeInsets? padding}) => FBadgeContentStyle(
label: label ?? this.label,
padding: padding ?? this.padding,
);

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('padding', padding))
..add(DiagnosticsProperty('text', label));
}

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is FBadgeContentStyle && runtimeType == other.runtimeType && padding == other.padding && label == other.label;

@override
int get hashCode => padding.hashCode ^ label.hashCode;

}
Loading

0 comments on commit d6a3245

Please sign in to comment.