Skip to content

Commit

Permalink
Reland: Adds support for the Material Badge widget, BadgeTheme, Badge…
Browse files Browse the repository at this point in the history
…ThemeData (#114560) (#115002)

Requires G3Fix cl/486997831
  • Loading branch information
HansMuller committed Nov 10, 2022
1 parent 19b351b commit 5a60045
Show file tree
Hide file tree
Showing 9 changed files with 796 additions and 0 deletions.
4 changes: 4 additions & 0 deletions dev/tools/gen_defaults/bin/gen_defaults.dart
Expand Up @@ -19,6 +19,7 @@ import 'dart:io';

import 'package:gen_defaults/action_chip_template.dart';
import 'package:gen_defaults/app_bar_template.dart';
import 'package:gen_defaults/badge_template.dart';
import 'package:gen_defaults/banner_template.dart';
import 'package:gen_defaults/bottom_app_bar_template.dart';
import 'package:gen_defaults/bottom_sheet_template.dart';
Expand Down Expand Up @@ -54,6 +55,7 @@ Future<void> main(List<String> args) async {
const List<String> tokenFiles = <String>[
'badge.json',
'banner.json',
'badge.json',
'bottom_app_bar.json',
'button_elevated.json',
'button_filled.json',
Expand Down Expand Up @@ -125,6 +127,8 @@ Future<void> main(List<String> args) async {
ActionChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile();
ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile();
AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile();
BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile();
BadgeTemplate('Badge', '$materialLib/badge.dart', tokens).updateFile();
BannerTemplate('Banner', '$materialLib/banner.dart', tokens).updateFile();
BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile();
BottomSheetTemplate('BottomSheet', '$materialLib/bottom_sheet.dart', tokens).updateFile();
Expand Down
36 changes: 36 additions & 0 deletions dev/tools/gen_defaults/lib/badge_template.dart
@@ -0,0 +1,36 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'template.dart';

class BadgeTemplate extends TokenTemplate {
const BadgeTemplate(super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.',
});

@override
String generate() => '''
class _${blockName}DefaultsM3 extends BadgeThemeData {
_${blockName}DefaultsM3(this.context) : super(
smallSize: ${tokens["md.comp.badge.size"]},
largeSize: ${tokens["md.comp.badge.large.size"]},
padding: const EdgeInsets.symmetric(horizontal: 4),
alignment: const AlignmentDirectional(12, -4),
);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
Color? get backgroundColor => ${color("md.comp.badge.color")};
@override
Color? get textColor => ${color("md.comp.badge.large.label-text.color")};
@override
TextStyle? get textStyle => ${textStyle("md.comp.badge.large.label-text")};
}
''';
}
2 changes: 2 additions & 0 deletions packages/flutter/lib/material.dart
Expand Up @@ -30,6 +30,8 @@ export 'src/material/app_bar_theme.dart';
export 'src/material/arc.dart';
export 'src/material/autocomplete.dart';
export 'src/material/back_button.dart';
export 'src/material/badge.dart';
export 'src/material/badge_theme.dart';
export 'src/material/banner.dart';
export 'src/material/banner_theme.dart';
export 'src/material/bottom_app_bar.dart';
Expand Down
190 changes: 190 additions & 0 deletions packages/flutter/lib/src/material/badge.dart
@@ -0,0 +1,190 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';

import 'badge_theme.dart';
import 'color_scheme.dart';
import 'theme.dart';

/// A Material Design "badge".
///
/// A badge's [label] conveys a small amount of information about its
/// [child], like a count or status. If the label is null then this is
/// a "small" badge that's displayed as a [smallSize] diameter filled
/// circle. Otherwise this is a [StadiumBorder] shaped "large" badge
/// with height [largeSize].
///
/// Badges are typically used to decorate the icon within a
/// BottomNavigationBarItem] or a [NavigationRailDestination]
/// or a button's icon, as in [TextButton.icon]. The badge's default
/// configuration is intended to work well with a default sized (24)
/// [Icon].
class Badge extends StatelessWidget {
/// Create a Badge that stacks [label] on top of [child].
///
/// If [label] is null then just a filled circle is displayed. Otherwise
/// the [label] is displayed within a [StadiumBorder] shaped area.
const Badge({
super.key,
this.backgroundColor,
this.textColor,
this.smallSize,
this.largeSize,
this.textStyle,
this.padding,
this.alignment,
this.label,
this.child,
});

/// The badge's fill color.
///
/// Defaults to the [BadgeTheme]'s background color, or
/// [ColorScheme.errorColor] if the theme value is null.
final Color? backgroundColor;

/// The color of the badge's [label] text.
///
/// This color overrides the color of the label's [textStyle].
///
/// Defaults to the [BadgeTheme]'s foreground color, or
/// [ColorScheme.onError] if the theme value is null.
final Color? textColor;

/// The diameter of the badge if [label] is null.
///
/// Defaults to the [BadgeTheme]'s small size, or 6 if the theme value
/// is null.
final double? smallSize;

/// The badge's height if [label] is non-null.
///
/// Defaults to the [BadgeTheme]'s large size, or 16 if the theme value
/// is null. If the default value is overridden then it may be useful to
/// also override [padding] and [alignment].
final double? largeSize;

/// The [DefaultTextStyle] for the badge's label.
///
/// The text style's color is overwritten by the [textColor].
///
/// This value is only used if [label] is non-null.
///
/// Defaults to the [BadgeTheme]'s text style, or the overall theme's
/// [TextTheme.labelSmall] if the badge theme's value is null. If
/// the default text style is overridden then it may be useful to
/// also override [largeSize], [padding], and [alignment].
final TextStyle? textStyle;

/// The padding added to the badge's label.
///
/// This value is only used if [label] is non-null.
///
/// Defaults to the [BadgeTheme]'s padding, or 4 pixels on the
/// left and right if the theme's value is null.
final EdgeInsetsGeometry? padding;

/// The location of the [label] relative to the [child].
///
/// This value is only used if [label] is non-null.
///
/// Defaults to the [BadgeTheme]'s alignment, or `start = 12`
/// and `top = -4` if the theme's value is null.
final AlignmentDirectional? alignment;

/// The badge's content, typically a [Text] widget that contains 1 to 4
/// characters.
///
/// If the label is null then this is a "small" badge that's
/// displayed as a [smallSize] diameter filled circle. Otherwise
/// this is a [StadiumBorder] shaped "large" badge with height [largeSize].
final Widget? label;

/// The widget that the badge is stacked on top of.
///
/// Typically this is an default sized [Icon] that's part of a
/// [BottomNavigationBarItem] or a [NavigationRailDestination].
final Widget? child;

@override
Widget build(BuildContext context) {
final BadgeThemeData badgeTheme = BadgeTheme.of(context);
final BadgeThemeData defaults = _BadgeDefaultsM3(context);
final double effectiveSmallSize = smallSize ?? badgeTheme.smallSize ?? defaults.smallSize!;
final double effectiveLargeSize = largeSize ?? badgeTheme.largeSize ?? defaults.largeSize!;

final Widget badge = DefaultTextStyle(
style: (textStyle ?? badgeTheme.textStyle ?? defaults.textStyle!).copyWith(
color: textColor ?? badgeTheme.textColor ?? defaults.textColor!,
),
child: IntrinsicWidth(
child: Container(
height: label == null ? effectiveSmallSize : effectiveLargeSize,
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: backgroundColor ?? badgeTheme.backgroundColor ?? defaults.backgroundColor!,
shape: const StadiumBorder(),
),
padding: label == null ? null : (padding ?? badgeTheme.padding ?? defaults.padding!),
alignment: label == null ? null : Alignment.center,
child: label ?? SizedBox(width: effectiveSmallSize, height: effectiveSmallSize),
),
),
);

if (child == null) {
return badge;
}

final AlignmentDirectional effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!;
return
Stack(
clipBehavior: Clip.none,
children: <Widget>[
child!,
Positioned.directional(
textDirection: Directionality.of(context),
start: label == null ? null : effectiveAlignment.start,
end: label == null ? 0 : null,
top: label == null ? 0 : effectiveAlignment.y,
child: badge,
),
],
);
}
}

// BEGIN GENERATED TOKEN PROPERTIES - Badge

// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.

// Token database version: v0_137

class _BadgeDefaultsM3 extends BadgeThemeData {
_BadgeDefaultsM3(this.context) : super(
smallSize: 6.0,
largeSize: 16.0,
padding: const EdgeInsets.symmetric(horizontal: 4),
alignment: const AlignmentDirectional(12, -4),
);

final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;

@override
Color? get backgroundColor => _colors.error;

@override
Color? get textColor => _colors.onError;

@override
TextStyle? get textStyle => Theme.of(context).textTheme.labelSmall;
}

// END GENERATED TOKEN PROPERTIES - Badge

0 comments on commit 5a60045

Please sign in to comment.