Unverified Commit 5180e450 authored by Qun Cheng's avatar Qun Cheng Committed by GitHub

Create `SearchBar` and `SearchBarTheme` (#122309)

parent 7e151b46
...@@ -45,6 +45,7 @@ import 'package:gen_defaults/navigation_rail_template.dart'; ...@@ -45,6 +45,7 @@ import 'package:gen_defaults/navigation_rail_template.dart';
import 'package:gen_defaults/popup_menu_template.dart'; import 'package:gen_defaults/popup_menu_template.dart';
import 'package:gen_defaults/progress_indicator_template.dart'; import 'package:gen_defaults/progress_indicator_template.dart';
import 'package:gen_defaults/radio_template.dart'; import 'package:gen_defaults/radio_template.dart';
import 'package:gen_defaults/search_bar_template.dart';
import 'package:gen_defaults/segmented_button_template.dart'; import 'package:gen_defaults/segmented_button_template.dart';
import 'package:gen_defaults/slider_template.dart'; import 'package:gen_defaults/slider_template.dart';
import 'package:gen_defaults/snackbar_template.dart'; import 'package:gen_defaults/snackbar_template.dart';
...@@ -107,6 +108,8 @@ Future<void> main(List<String> args) async { ...@@ -107,6 +108,8 @@ Future<void> main(List<String> args) async {
'progress_indicator_circular.json', 'progress_indicator_circular.json',
'progress_indicator_linear.json', 'progress_indicator_linear.json',
'radio_button.json', 'radio_button.json',
'search_bar.json',
'search_view.json',
'segmented_button_outlined.json', 'segmented_button_outlined.json',
'shape.json', 'shape.json',
'sheet_bottom.json', 'sheet_bottom.json',
...@@ -173,6 +176,7 @@ Future<void> main(List<String> args) async { ...@@ -173,6 +176,7 @@ Future<void> main(List<String> args) async {
PopupMenuTemplate('PopupMenu', '$materialLib/popup_menu.dart', tokens).updateFile(); PopupMenuTemplate('PopupMenu', '$materialLib/popup_menu.dart', tokens).updateFile();
ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile(); ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile();
RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile(); RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile();
SearchBarTemplate('SearchBar', '$materialLib/search_anchor.dart', tokens).updateFile();
SegmentedButtonTemplate('md.comp.outlined-segmented-button', 'SegmentedButton', '$materialLib/segmented_button.dart', tokens).updateFile(); SegmentedButtonTemplate('md.comp.outlined-segmented-button', 'SegmentedButton', '$materialLib/segmented_button.dart', tokens).updateFile();
SnackbarTemplate('md.comp.snackbar', 'Snackbar', '$materialLib/snack_bar.dart', tokens).updateFile(); SnackbarTemplate('md.comp.snackbar', 'Snackbar', '$materialLib/snack_bar.dart', tokens).updateFile();
SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile(); SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile();
......
// 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 SearchBarTemplate extends TokenTemplate {
const SearchBarTemplate(super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.',
super.textThemePrefix = '_textTheme.'
});
@override
String generate() => '''
class _SearchBarDefaultsM3 extends SearchBarThemeData {
_SearchBarDefaultsM3(this.context);
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
MaterialStateProperty<Color?>? get backgroundColor =>
MaterialStatePropertyAll<Color>(${componentColor("md.comp.search-bar.container")});
@override
MaterialStateProperty<double>? get elevation =>
const MaterialStatePropertyAll<double>(${elevation("md.comp.search-bar.container")});
@override
MaterialStateProperty<Color>? get shadowColor =>
MaterialStatePropertyAll<Color>(_colors.shadow);
@override
MaterialStateProperty<Color>? get surfaceTintColor =>
MaterialStatePropertyAll<Color>(${colorOrTransparent("md.comp.search-bar.container.surface-tint-layer.color")});
@override
MaterialStateProperty<Color?>? get overlayColor =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return ${componentColor("md.comp.search-bar.pressed.state-layer")};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor("md.comp.search-bar.hover.state-layer")};
}
if (states.contains(MaterialState.focused)) {
return ${colorOrTransparent("md.comp.search-bar.focused.state-layer")};
}
return Colors.transparent;
});
// No default side
@override
MaterialStateProperty<OutlinedBorder>? get shape =>
const MaterialStatePropertyAll<OutlinedBorder>(${shape('md.comp.search-bar.container', '')});
@override
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
const MaterialStatePropertyAll<EdgeInsetsGeometry>(EdgeInsets.symmetric(horizontal: 8.0));
@override
MaterialStateProperty<TextStyle?> get textStyle =>
MaterialStatePropertyAll<TextStyle?>(${textStyleWithColor('md.comp.search-bar.input-text')});
@override
MaterialStateProperty<TextStyle?> get hintStyle =>
MaterialStatePropertyAll<TextStyle?>(${textStyleWithColor('md.comp.search-bar.supporting-text')});
@override
BoxConstraints get constraints =>
const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: ${tokens['md.comp.search-bar.container.height']});
}
''';
}
...@@ -236,4 +236,15 @@ abstract class TokenTemplate { ...@@ -236,4 +236,15 @@ abstract class TokenTemplate {
String textStyle(String componentToken) { String textStyle(String componentToken) {
return '$textThemePrefix${tokens["$componentToken.text-style"]}'; return '$textThemePrefix${tokens["$componentToken.text-style"]}';
} }
String textStyleWithColor(String componentToken) {
if (!tokens.containsKey('$componentToken.text-style')) {
return 'null';
}
String style = textStyle(componentToken);
if (tokens.containsKey('$componentToken.color')) {
style = '$style?.copyWith(color: ${componentColor(componentToken)})';
}
return style;
}
} }
...@@ -152,6 +152,8 @@ export 'src/material/scaffold.dart'; ...@@ -152,6 +152,8 @@ export 'src/material/scaffold.dart';
export 'src/material/scrollbar.dart'; export 'src/material/scrollbar.dart';
export 'src/material/scrollbar_theme.dart'; export 'src/material/scrollbar_theme.dart';
export 'src/material/search.dart'; export 'src/material/search.dart';
export 'src/material/search_anchor.dart';
export 'src/material/search_bar_theme.dart';
export 'src/material/segmented_button.dart'; export 'src/material/segmented_button.dart';
export 'src/material/segmented_button_theme.dart'; export 'src/material/segmented_button_theme.dart';
export 'src/material/selectable_text.dart'; export 'src/material/selectable_text.dart';
......
// 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 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
import 'ink_well.dart';
import 'input_border.dart';
import 'input_decorator.dart';
import 'material.dart';
import 'material_state.dart';
import 'search_bar_theme.dart';
import 'text_field.dart';
import 'text_theme.dart';
import 'theme.dart';
/// A Material Design search bar.
///
/// Search bars include a [leading] Search icon, a text input field and optional
/// [trailing] icons. A search bar is typically used to open a search view.
/// It is the default trigger for a search view.
///
/// For [TextDirection.ltr], the [leading] widget is on the left side of the bar.
/// It should contain either a navigational action (such as a menu or up-arrow)
/// or a non-functional search icon.
///
/// The [trailing] is an optional list that appears at the other end of
/// the search bar. Typically only one or two action icons are included.
/// These actions can represent additional modes of searching (like voice search),
/// a separate high-level action (such as current location) or an overflow menu.
class SearchBar extends StatefulWidget {
/// Creates a Material Design search bar.
const SearchBar({
super.key,
this.controller,
this.focusNode,
this.hintText,
this.leading,
this.trailing,
this.onTap,
this.onChanged,
this.constraints,
this.elevation,
this.backgroundColor,
this.shadowColor,
this.surfaceTintColor,
this.overlayColor,
this.side,
this.shape,
this.padding,
this.textStyle,
this.hintStyle,
});
/// Controls the text being edited in the search bar's text field.
///
/// If null, this widget will create its own [TextEditingController].
final TextEditingController? controller;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode? focusNode;
/// Text that suggests what sort of input the field accepts.
///
/// Displayed at the same location on the screen where text may be entered
/// when the input is empty.
///
/// Defaults to null.
final String? hintText;
/// A widget to display before the text input field.
///
/// Typically the [leading] widget is an [Icon] or an [IconButton].
final Widget? leading;
/// A list of Widgets to display in a row after the text field.
///
/// Typically these actions can represent additional modes of searching
/// (like voice search), an avatar, a separate high-level action (such as
/// current location) or an overflow menu. There should not be more than
/// two trailing actions.
final Iterable<Widget>? trailing;
/// Called when the user taps this search bar.
final GestureTapCallback? onTap;
/// Invoked upon user input.
final ValueChanged<String>? onChanged;
/// Optional size constraints for the search bar.
///
/// If null, the value of [SearchBarThemeData.constraints] will be used. If
/// this is also null, then the constraints defaults to:
/// ```dart
/// const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: 56.0)
/// ```
final BoxConstraints? constraints;
/// The elevation of the search bar's [Material].
///
/// If null, the value of [SearchBarThemeData.elevation] will be used. If this
/// is also null, then default value is 8.0.
final MaterialStateProperty<double?>? elevation;
/// The search bar's background fill color.
///
/// If null, the value of [SearchBarThemeData.backgroundColor] will be used.
/// If this is also null, then the default value is [ColorScheme.surface].
final MaterialStateProperty<Color?>? backgroundColor;
/// The shadow color of the search bar's [Material].
///
/// If null, the value of [SearchBarThemeData.shadowColor] will be used.
/// If this is also null, then the default value is [ColorScheme.shadow].
final MaterialStateProperty<Color?>? shadowColor;
/// The surface tint color of the search bar's [Material].
///
/// See [Material.surfaceTintColor] for more details.
///
/// If null, the value of [SearchBarThemeData.surfaceTintColor] will be used.
/// If this is also null, then the default value is [ColorScheme.surfaceTint].
final MaterialStateProperty<Color?>? surfaceTintColor;
/// The highlight color that's typically used to indicate that
/// the search bar is focused, hovered, or pressed.
final MaterialStateProperty<Color?>? overlayColor;
/// The color and weight of the search bar's outline.
///
/// This value is combined with [shape] to create a shape decorated
/// with an outline.
///
/// If null, the value of [SearchBarThemeData.side] will be used. If this is
/// also null, the search bar doesn't have a side by default.
final MaterialStateProperty<BorderSide?>? side;
/// The shape of the search bar's underlying [Material].
///
/// This shape is combined with [side] to create a shape decorated
/// with an outline.
///
/// If null, the value of [SearchBarThemeData.shape] will be used.
/// If this is also null, then the default value is 16.0 horizontally.
/// Defaults to [StadiumBorder].
final MaterialStateProperty<OutlinedBorder?>? shape;
/// The padding between the search bar's boundary and its contents.
///
/// If null, the value of [SearchBarThemeData.padding] will be used.
/// If this is also null, then the default value is 16.0 horizontally.
final MaterialStateProperty<EdgeInsetsGeometry?>? padding;
/// The style to use for the text being edited.
///
/// If null, defaults to the `bodyLarge` text style from the current [Theme].
/// The default text color is [ColorScheme.onSurface].
final MaterialStateProperty<TextStyle?>? textStyle;
/// The style to use for the [hintText].
///
/// If null, the value of [SearchBarThemeData.hintStyle] will be used. If this
/// is also null, the value of [textStyle] will be used. If this is also null,
/// defaults to the `bodyLarge` text style from the current [Theme].
/// The default text color is [ColorScheme.onSurfaceVariant].
final MaterialStateProperty<TextStyle?>? hintStyle;
@override
State<SearchBar> createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
late final MaterialStatesController _internalStatesController;
late final FocusNode _focusNode;
@override
void initState() {
super.initState();
_internalStatesController = MaterialStatesController();
_internalStatesController.addListener(() {
setState(() {});
});
_focusNode = widget.focusNode ?? FocusNode();
}
@override
void dispose() {
_internalStatesController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final TextDirection textDirection = Directionality.of(context);
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final IconThemeData iconTheme = IconTheme.of(context);
final SearchBarThemeData searchBarTheme = SearchBarTheme.of(context);
final SearchBarThemeData defaults = _SearchBarDefaultsM3(context);
T? resolve<T>(
MaterialStateProperty<T>? widgetValue,
MaterialStateProperty<T>? themeValue,
MaterialStateProperty<T>? defaultValue,
) {
final Set<MaterialState> states = _internalStatesController.value;
return widgetValue?.resolve(states) ?? themeValue?.resolve(states) ?? defaultValue?.resolve(states);
}
final TextStyle? effectiveTextStyle = resolve<TextStyle?>(widget.textStyle, searchBarTheme.textStyle, defaults.textStyle);
final double? effectiveElevation = resolve<double?>(widget.elevation, searchBarTheme.elevation, defaults.elevation);
final Color? effectiveShadowColor = resolve<Color?>(widget.shadowColor, searchBarTheme.shadowColor, defaults.shadowColor);
final Color? effectiveBackgroundColor = resolve<Color?>(widget.backgroundColor, searchBarTheme.backgroundColor, defaults.backgroundColor);
final Color? effectiveSurfaceTintColor = resolve<Color?>(widget.surfaceTintColor, searchBarTheme.surfaceTintColor, defaults.surfaceTintColor);
final OutlinedBorder? effectiveShape = resolve<OutlinedBorder?>(widget.shape, searchBarTheme.shape, defaults.shape);
final BorderSide? effectiveSide = resolve<BorderSide?>(widget.side, searchBarTheme.side, defaults.side);
final EdgeInsetsGeometry? effectivePadding = resolve<EdgeInsetsGeometry?>(widget.padding, searchBarTheme.padding, defaults.padding);
final MaterialStateProperty<Color?>? effectiveOverlayColor = widget.overlayColor ?? searchBarTheme.overlayColor ?? defaults.overlayColor;
final Set<MaterialState> states = _internalStatesController.value;
final TextStyle? effectiveHintStyle = widget.hintStyle?.resolve(states)
?? searchBarTheme.hintStyle?.resolve(states)
?? widget.textStyle?.resolve(states)
?? searchBarTheme.textStyle?.resolve(states)
?? defaults.hintStyle?.resolve(states);
final bool isDark = Theme.of(context).brightness == Brightness.dark;
bool isIconThemeColorDefault(Color? color) {
if (isDark) {
return color == kDefaultIconLightColor;
}
return color == kDefaultIconDarkColor;
}
Widget? leading;
if (widget.leading != null) {
leading = IconTheme.merge(
data: isIconThemeColorDefault(iconTheme.color)
? IconThemeData(color: colorScheme.onSurface)
: iconTheme,
child: widget.leading!,
);
}
List<Widget>? trailing;
if (widget.trailing != null) {
trailing = widget.trailing?.map((Widget trailing) => IconTheme.merge(
data: isIconThemeColorDefault(iconTheme.color)
? IconThemeData(color: colorScheme.onSurfaceVariant)
: iconTheme,
child: trailing,
)).toList();
}
return SafeArea(
child: ConstrainedBox(
constraints: widget.constraints ?? searchBarTheme.constraints ?? defaults.constraints!,
child: Material(
elevation: effectiveElevation!,
shadowColor: effectiveShadowColor,
color: effectiveBackgroundColor,
surfaceTintColor: effectiveSurfaceTintColor,
shape: effectiveShape?.copyWith(side: effectiveSide),
child: InkWell(
onTap: () {
widget.onTap?.call();
_focusNode.requestFocus();
},
overlayColor: effectiveOverlayColor,
customBorder: effectiveShape?.copyWith(side: effectiveSide),
statesController: _internalStatesController,
child: Padding(
padding: effectivePadding!,
child: Row(
textDirection: textDirection,
children: <Widget>[
if (leading != null) leading,
Expanded(
child: IgnorePointer(
child: Padding(
padding: effectivePadding,
child: TextField(
focusNode: _focusNode,
onChanged: widget.onChanged,
controller: widget.controller,
style: effectiveTextStyle,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.hintText,
hintStyle: effectiveHintStyle,
),
),
),
)
),
if (trailing != null) ...trailing,
],
),
),
),
),
),
);
}
}
// BEGIN GENERATED TOKEN PROPERTIES - SearchBar
// 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_158
class _SearchBarDefaultsM3 extends SearchBarThemeData {
_SearchBarDefaultsM3(this.context);
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
MaterialStateProperty<Color?>? get backgroundColor =>
MaterialStatePropertyAll<Color>(_colors.surface);
@override
MaterialStateProperty<double>? get elevation =>
const MaterialStatePropertyAll<double>(6.0);
@override
MaterialStateProperty<Color>? get shadowColor =>
MaterialStatePropertyAll<Color>(_colors.shadow);
@override
MaterialStateProperty<Color>? get surfaceTintColor =>
MaterialStatePropertyAll<Color>(_colors.surfaceTint);
@override
MaterialStateProperty<Color?>? get overlayColor =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return _colors.onSurface.withOpacity(0.12);
}
if (states.contains(MaterialState.hovered)) {
return _colors.onSurface.withOpacity(0.08);
}
if (states.contains(MaterialState.focused)) {
return Colors.transparent;
}
return Colors.transparent;
});
// No default side
@override
MaterialStateProperty<OutlinedBorder>? get shape =>
const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder());
@override
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
const MaterialStatePropertyAll<EdgeInsetsGeometry>(EdgeInsets.symmetric(horizontal: 8.0));
@override
MaterialStateProperty<TextStyle?> get textStyle =>
MaterialStatePropertyAll<TextStyle?>(_textTheme.bodyLarge?.copyWith(color: _colors.onSurface));
@override
MaterialStateProperty<TextStyle?> get hintStyle =>
MaterialStatePropertyAll<TextStyle?>(_textTheme.bodyLarge?.copyWith(color: _colors.onSurfaceVariant));
@override
BoxConstraints get constraints =>
const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: 56.0);
}
// END GENERATED TOKEN PROPERTIES - SearchBar
// 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 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'material_state.dart';
import 'theme.dart';
// Examples can assume:
// late BuildContext context;
/// Defines default property values for descendant [SearchBar] widgets.
///
/// Descendant widgets obtain the current [SearchBarThemeData] object using
/// `SearchBarTheme.of(context)`. Instances of [SearchBarThemeData] can be customized
/// with [SearchBarThemeData.copyWith].
///
/// Typically a [SearchBarThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.searchBarTheme].
///
/// All [SearchBarThemeData] properties are `null` by default. When null, the
/// [SearchBar] will use the values from [ThemeData] if they exist, otherwise it
/// will provide its own defaults based on the overall [Theme]'s colorScheme.
/// See the individual [SearchBar] properties for details.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class SearchBarThemeData with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.searchBarTheme].
const SearchBarThemeData({
this.elevation,
this.backgroundColor,
this.shadowColor,
this.surfaceTintColor,
this.overlayColor,
this.side,
this.shape,
this.padding,
this.textStyle,
this.hintStyle,
this.constraints,
});
/// Overrides the default value of the [SearchBar.elevation].
final MaterialStateProperty<double?>? elevation;
/// Overrides the default value of the [SearchBar.backgroundColor].
final MaterialStateProperty<Color?>? backgroundColor;
/// Overrides the default value of the [SearchBar.shadowColor].
final MaterialStateProperty<Color?>? shadowColor;
/// Overrides the default value of the [SearchBar.surfaceTintColor].
final MaterialStateProperty<Color?>? surfaceTintColor;
/// Overrides the default value of the [SearchBar.overlayColor].
final MaterialStateProperty<Color?>? overlayColor;
/// Overrides the default value of the [SearchBar.side].
final MaterialStateProperty<BorderSide?>? side;
/// Overrides the default value of the [SearchBar.shape].
final MaterialStateProperty<OutlinedBorder?>? shape;
/// Overrides the default value for [SearchBar.padding].
final MaterialStateProperty<EdgeInsetsGeometry?>? padding;
/// Overrides the default value for [SearchBar.textStyle].
final MaterialStateProperty<TextStyle?>? textStyle;
/// Overrides the default value for [SearchBar.hintStyle].
final MaterialStateProperty<TextStyle?>? hintStyle;
/// Overrides the value of size constraints for [SearchBar].
final BoxConstraints? constraints;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
SearchBarThemeData copyWith({
MaterialStateProperty<double?>? elevation,
MaterialStateProperty<Color?>? backgroundColor,
MaterialStateProperty<Color?>? shadowColor,
MaterialStateProperty<Color?>? surfaceTintColor,
MaterialStateProperty<Color?>? overlayColor,
MaterialStateProperty<BorderSide?>? side,
MaterialStateProperty<OutlinedBorder?>? shape,
MaterialStateProperty<EdgeInsetsGeometry?>? padding,
MaterialStateProperty<TextStyle?>? textStyle,
MaterialStateProperty<TextStyle?>? hintStyle,
BoxConstraints? constraints,
}) {
return SearchBarThemeData(
elevation: elevation ?? this.elevation,
backgroundColor: backgroundColor ?? this.backgroundColor,
shadowColor: shadowColor ?? this.shadowColor,
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
overlayColor: overlayColor ?? this.overlayColor,
side: side ?? this.side,
shape: shape ?? this.shape,
padding: padding ?? this.padding,
textStyle: textStyle ?? this.textStyle,
hintStyle: hintStyle ?? this.hintStyle,
constraints: constraints ?? this.constraints,
);
}
/// Linearly interpolate between two [SearchBarThemeData]s.
///
/// {@macro dart.ui.shadow.lerp}
static SearchBarThemeData? lerp(SearchBarThemeData? a, SearchBarThemeData? b, double t) {
if (identical(a, b)) {
return a;
}
return SearchBarThemeData(
elevation: MaterialStateProperty.lerp<double?>(a?.elevation, b?.elevation, t, lerpDouble),
backgroundColor: MaterialStateProperty.lerp<Color?>(a?.backgroundColor, b?.backgroundColor, t, Color.lerp),
shadowColor: MaterialStateProperty.lerp<Color?>(a?.shadowColor, b?.shadowColor, t, Color.lerp),
surfaceTintColor: MaterialStateProperty.lerp<Color?>(a?.surfaceTintColor, b?.surfaceTintColor, t, Color.lerp),
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
side: _lerpSides(a?.side, b?.side, t),
shape: MaterialStateProperty.lerp<OutlinedBorder?>(a?.shape, b?.shape, t, OutlinedBorder.lerp),
padding: MaterialStateProperty.lerp<EdgeInsetsGeometry?>(a?.padding, b?.padding, t, EdgeInsetsGeometry.lerp),
textStyle: MaterialStateProperty.lerp<TextStyle?>(a?.textStyle, b?.textStyle, t, TextStyle.lerp),
hintStyle: MaterialStateProperty.lerp<TextStyle?>(a?.hintStyle, b?.hintStyle, t, TextStyle.lerp),
constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t),
);
}
@override
int get hashCode => Object.hash(
elevation,
backgroundColor,
shadowColor,
surfaceTintColor,
overlayColor,
side,
shape,
padding,
textStyle,
hintStyle,
constraints,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is SearchBarThemeData
&& other.elevation == elevation
&& other.backgroundColor == backgroundColor
&& other.shadowColor == shadowColor
&& other.surfaceTintColor == surfaceTintColor
&& other.overlayColor == overlayColor
&& other.side == side
&& other.shape == shape
&& other.padding == padding
&& other.textStyle == textStyle
&& other.hintStyle == hintStyle
&& other.constraints == constraints;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('backgroundColor', backgroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('shadowColor', shadowColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('surfaceTintColor', surfaceTintColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide?>>('side', side, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<OutlinedBorder?>>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<EdgeInsetsGeometry?>>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('textStyle', textStyle, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('hintStyle', hintStyle, defaultValue: null));
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
}
// Special case because BorderSide.lerp() doesn't support null arguments
static MaterialStateProperty<BorderSide?>? _lerpSides(MaterialStateProperty<BorderSide?>? a, MaterialStateProperty<BorderSide?>? b, double t) {
if (identical(a, b)) {
return a;
}
return _LerpSides(a, b, t);
}
}
class _LerpSides implements MaterialStateProperty<BorderSide?> {
const _LerpSides(this.a, this.b, this.t);
final MaterialStateProperty<BorderSide?>? a;
final MaterialStateProperty<BorderSide?>? b;
final double t;
@override
BorderSide? resolve(Set<MaterialState> states) {
final BorderSide? resolvedA = a?.resolve(states);
final BorderSide? resolvedB = b?.resolve(states);
if (identical(resolvedA, resolvedB)) {
return resolvedA;
}
if (resolvedA == null) {
return BorderSide.lerp(BorderSide(width: 0, color: resolvedB!.color.withAlpha(0)), resolvedB, t);
}
if (resolvedB == null) {
return BorderSide.lerp(resolvedA, BorderSide(width: 0, color: resolvedA.color.withAlpha(0)), t);
}
return BorderSide.lerp(resolvedA, resolvedB, t);
}
}
/// Applies a search bar theme to descendant [SearchBar] widgets.
///
/// Descendant widgets obtain the current theme's [SearchBarTheme] object using
/// [SearchBarTheme.of]. When a widget uses [SearchBarTheme.of], it is automatically
/// rebuilt if the theme later changes.
///
/// A search bar theme can be specified as part of the overall Material theme using
/// [ThemeData.searchBarTheme].
///
/// See also:
///
/// * [SearchBarThemeData], which describes the actual configuration of a search bar
/// theme.
class SearchBarTheme extends InheritedWidget {
/// Constructs a search bar theme that configures all descendant [SearchBar] widgets.
const SearchBarTheme({
super.key,
required this.data,
required super.child,
});
/// The properties used for all descendant [SearchBar] widgets.
final SearchBarThemeData data;
/// Returns the configuration [data] from the closest [SearchBarTheme] ancestor.
/// If there is no ancestor, it returns [ThemeData.searchBarTheme].
///
/// Typical usage is as follows:
///
/// ```dart
/// SearchBarThemeData theme = SearchBarTheme.of(context);
/// ```
static SearchBarThemeData of(BuildContext context) {
final SearchBarTheme? searchBarTheme = context.dependOnInheritedWidgetOfExactType<SearchBarTheme>();
return searchBarTheme?.data ?? Theme.of(context).searchBarTheme;
}
@override
bool updateShouldNotify(SearchBarTheme oldWidget) => data != oldWidget.data;
}
...@@ -53,6 +53,7 @@ import 'popup_menu_theme.dart'; ...@@ -53,6 +53,7 @@ import 'popup_menu_theme.dart';
import 'progress_indicator_theme.dart'; import 'progress_indicator_theme.dart';
import 'radio_theme.dart'; import 'radio_theme.dart';
import 'scrollbar_theme.dart'; import 'scrollbar_theme.dart';
import 'search_bar_theme.dart';
import 'segmented_button_theme.dart'; import 'segmented_button_theme.dart';
import 'slider_theme.dart'; import 'slider_theme.dart';
import 'snack_bar_theme.dart'; import 'snack_bar_theme.dart';
...@@ -373,6 +374,7 @@ class ThemeData with Diagnosticable { ...@@ -373,6 +374,7 @@ class ThemeData with Diagnosticable {
PopupMenuThemeData? popupMenuTheme, PopupMenuThemeData? popupMenuTheme,
ProgressIndicatorThemeData? progressIndicatorTheme, ProgressIndicatorThemeData? progressIndicatorTheme,
RadioThemeData? radioTheme, RadioThemeData? radioTheme,
SearchBarThemeData? searchBarTheme,
SegmentedButtonThemeData? segmentedButtonTheme, SegmentedButtonThemeData? segmentedButtonTheme,
SliderThemeData? sliderTheme, SliderThemeData? sliderTheme,
SnackBarThemeData? snackBarTheme, SnackBarThemeData? snackBarTheme,
...@@ -589,6 +591,7 @@ class ThemeData with Diagnosticable { ...@@ -589,6 +591,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme ??= const PopupMenuThemeData(); popupMenuTheme ??= const PopupMenuThemeData();
progressIndicatorTheme ??= const ProgressIndicatorThemeData(); progressIndicatorTheme ??= const ProgressIndicatorThemeData();
radioTheme ??= const RadioThemeData(); radioTheme ??= const RadioThemeData();
searchBarTheme ??= const SearchBarThemeData();
segmentedButtonTheme ??= const SegmentedButtonThemeData(); segmentedButtonTheme ??= const SegmentedButtonThemeData();
sliderTheme ??= const SliderThemeData(); sliderTheme ??= const SliderThemeData();
snackBarTheme ??= const SnackBarThemeData(); snackBarTheme ??= const SnackBarThemeData();
...@@ -686,6 +689,7 @@ class ThemeData with Diagnosticable { ...@@ -686,6 +689,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme: popupMenuTheme, popupMenuTheme: popupMenuTheme,
progressIndicatorTheme: progressIndicatorTheme, progressIndicatorTheme: progressIndicatorTheme,
radioTheme: radioTheme, radioTheme: radioTheme,
searchBarTheme: searchBarTheme,
segmentedButtonTheme: segmentedButtonTheme, segmentedButtonTheme: segmentedButtonTheme,
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
snackBarTheme: snackBarTheme, snackBarTheme: snackBarTheme,
...@@ -797,6 +801,7 @@ class ThemeData with Diagnosticable { ...@@ -797,6 +801,7 @@ class ThemeData with Diagnosticable {
required this.popupMenuTheme, required this.popupMenuTheme,
required this.progressIndicatorTheme, required this.progressIndicatorTheme,
required this.radioTheme, required this.radioTheme,
required this.searchBarTheme,
required this.segmentedButtonTheme, required this.segmentedButtonTheme,
required this.sliderTheme, required this.sliderTheme,
required this.snackBarTheme, required this.snackBarTheme,
...@@ -1486,6 +1491,9 @@ class ThemeData with Diagnosticable { ...@@ -1486,6 +1491,9 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the appearance and layout of [Radio] widgets. /// A theme for customizing the appearance and layout of [Radio] widgets.
final RadioThemeData radioTheme; final RadioThemeData radioTheme;
/// A theme for customizing the appearance and layout of [SearchBar] widgets.
final SearchBarThemeData searchBarTheme;
/// A theme for customizing the appearance and layout of [SegmentedButton] widgets. /// A theme for customizing the appearance and layout of [SegmentedButton] widgets.
final SegmentedButtonThemeData segmentedButtonTheme; final SegmentedButtonThemeData segmentedButtonTheme;
...@@ -1697,6 +1705,7 @@ class ThemeData with Diagnosticable { ...@@ -1697,6 +1705,7 @@ class ThemeData with Diagnosticable {
PopupMenuThemeData? popupMenuTheme, PopupMenuThemeData? popupMenuTheme,
ProgressIndicatorThemeData? progressIndicatorTheme, ProgressIndicatorThemeData? progressIndicatorTheme,
RadioThemeData? radioTheme, RadioThemeData? radioTheme,
SearchBarThemeData? searchBarTheme,
SegmentedButtonThemeData? segmentedButtonTheme, SegmentedButtonThemeData? segmentedButtonTheme,
SliderThemeData? sliderTheme, SliderThemeData? sliderTheme,
SnackBarThemeData? snackBarTheme, SnackBarThemeData? snackBarTheme,
...@@ -1831,6 +1840,7 @@ class ThemeData with Diagnosticable { ...@@ -1831,6 +1840,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme: popupMenuTheme ?? this.popupMenuTheme, popupMenuTheme: popupMenuTheme ?? this.popupMenuTheme,
progressIndicatorTheme: progressIndicatorTheme ?? this.progressIndicatorTheme, progressIndicatorTheme: progressIndicatorTheme ?? this.progressIndicatorTheme,
radioTheme: radioTheme ?? this.radioTheme, radioTheme: radioTheme ?? this.radioTheme,
searchBarTheme: searchBarTheme ?? this.searchBarTheme,
segmentedButtonTheme: segmentedButtonTheme ?? this.segmentedButtonTheme, segmentedButtonTheme: segmentedButtonTheme ?? this.segmentedButtonTheme,
sliderTheme: sliderTheme ?? this.sliderTheme, sliderTheme: sliderTheme ?? this.sliderTheme,
snackBarTheme: snackBarTheme ?? this.snackBarTheme, snackBarTheme: snackBarTheme ?? this.snackBarTheme,
...@@ -2025,6 +2035,7 @@ class ThemeData with Diagnosticable { ...@@ -2025,6 +2035,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme: PopupMenuThemeData.lerp(a.popupMenuTheme, b.popupMenuTheme, t)!, popupMenuTheme: PopupMenuThemeData.lerp(a.popupMenuTheme, b.popupMenuTheme, t)!,
progressIndicatorTheme: ProgressIndicatorThemeData.lerp(a.progressIndicatorTheme, b.progressIndicatorTheme, t)!, progressIndicatorTheme: ProgressIndicatorThemeData.lerp(a.progressIndicatorTheme, b.progressIndicatorTheme, t)!,
radioTheme: RadioThemeData.lerp(a.radioTheme, b.radioTheme, t), radioTheme: RadioThemeData.lerp(a.radioTheme, b.radioTheme, t),
searchBarTheme: SearchBarThemeData.lerp(a.searchBarTheme, b.searchBarTheme, t)!,
segmentedButtonTheme: SegmentedButtonThemeData.lerp(a.segmentedButtonTheme, b.segmentedButtonTheme, t), segmentedButtonTheme: SegmentedButtonThemeData.lerp(a.segmentedButtonTheme, b.segmentedButtonTheme, t),
sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t), sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
snackBarTheme: SnackBarThemeData.lerp(a.snackBarTheme, b.snackBarTheme, t), snackBarTheme: SnackBarThemeData.lerp(a.snackBarTheme, b.snackBarTheme, t),
...@@ -2131,6 +2142,7 @@ class ThemeData with Diagnosticable { ...@@ -2131,6 +2142,7 @@ class ThemeData with Diagnosticable {
other.popupMenuTheme == popupMenuTheme && other.popupMenuTheme == popupMenuTheme &&
other.progressIndicatorTheme == progressIndicatorTheme && other.progressIndicatorTheme == progressIndicatorTheme &&
other.radioTheme == radioTheme && other.radioTheme == radioTheme &&
other.searchBarTheme == searchBarTheme &&
other.segmentedButtonTheme == segmentedButtonTheme && other.segmentedButtonTheme == segmentedButtonTheme &&
other.sliderTheme == sliderTheme && other.sliderTheme == sliderTheme &&
other.snackBarTheme == snackBarTheme && other.snackBarTheme == snackBarTheme &&
...@@ -2234,6 +2246,7 @@ class ThemeData with Diagnosticable { ...@@ -2234,6 +2246,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme, popupMenuTheme,
progressIndicatorTheme, progressIndicatorTheme,
radioTheme, radioTheme,
searchBarTheme,
segmentedButtonTheme, segmentedButtonTheme,
sliderTheme, sliderTheme,
snackBarTheme, snackBarTheme,
...@@ -2339,6 +2352,7 @@ class ThemeData with Diagnosticable { ...@@ -2339,6 +2352,7 @@ class ThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<PopupMenuThemeData>('popupMenuTheme', popupMenuTheme, defaultValue: defaultData.popupMenuTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<PopupMenuThemeData>('popupMenuTheme', popupMenuTheme, defaultValue: defaultData.popupMenuTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<ProgressIndicatorThemeData>('progressIndicatorTheme', progressIndicatorTheme, defaultValue: defaultData.progressIndicatorTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<ProgressIndicatorThemeData>('progressIndicatorTheme', progressIndicatorTheme, defaultValue: defaultData.progressIndicatorTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<RadioThemeData>('radioTheme', radioTheme, defaultValue: defaultData.radioTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<RadioThemeData>('radioTheme', radioTheme, defaultValue: defaultData.radioTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SearchBarThemeData>('searchBarTheme', searchBarTheme, defaultValue: defaultData.searchBarTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SegmentedButtonThemeData>('segmentedButtonTheme', segmentedButtonTheme, defaultValue: defaultData.segmentedButtonTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<SegmentedButtonThemeData>('segmentedButtonTheme', segmentedButtonTheme, defaultValue: defaultData.segmentedButtonTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SnackBarThemeData>('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<SnackBarThemeData>('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme, level: DiagnosticLevel.debug));
......
// 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 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
testWidgets('SearchBar defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
final ColorScheme colorScheme = theme.colorScheme;
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: SearchBar(
hintText: 'hint text',
)
),
),
);
final Finder searchBarMaterial = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(Material),
);
final Material material = tester.widget<Material>(searchBarMaterial);
expect(material.animationDuration, const Duration(milliseconds: 200));
expect(material.borderOnForeground, true);
expect(material.borderRadius, null);
expect(material.clipBehavior, Clip.none);
expect(material.color, colorScheme.surface);
expect(material.elevation, 6.0);
expect(material.shadowColor, colorScheme.shadow);
expect(material.surfaceTintColor, colorScheme.surfaceTint);
expect(material.shape, const StadiumBorder());
final Text helperText = tester.widget(find.text('hint text'));
expect(helperText.style?.color, colorScheme.onSurfaceVariant);
expect(helperText.style?.fontSize, 16.0);
expect(helperText.style?.fontFamily, 'Roboto');
expect(helperText.style?.fontWeight, FontWeight.w400);
const String input = 'entered text';
await tester.enterText(find.byType(SearchBar), input);
final EditableText inputText = tester.widget(find.text(input));
expect(inputText.style.color, colorScheme.onSurface);
expect(inputText.style.fontSize, 16.0);
expect(helperText.style?.fontFamily, 'Roboto');
expect(inputText.style.fontWeight, FontWeight.w400);
});
testWidgets('SearchBar respects controller property', (WidgetTester tester) async {
const String defaultText = 'default text';
final TextEditingController controller = TextEditingController(text: defaultText);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchBar(
controller: controller,
),
),
),
);
expect(controller.value.text, defaultText);
expect(find.text(defaultText), findsOneWidget);
const String updatedText = 'updated text';
await tester.enterText(find.byType(SearchBar), updatedText);
expect(controller.value.text, updatedText);
expect(find.text(defaultText), findsNothing);
expect(find.text(updatedText), findsOneWidget);
});
testWidgets('SearchBar respects focusNode property', (WidgetTester tester) async {
final FocusNode node = FocusNode();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchBar(
focusNode: node,
),
),
),
);
expect(node.hasFocus, false);
node.requestFocus();
await tester.pump();
expect(node.hasFocus, true);
node.unfocus();
await tester.pump();
expect(node.hasFocus, false);
});
testWidgets('SearchBar has correct default layout and padding LTR', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: SearchBar(
leading: IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
trailing: <Widget>[
IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
)
],
),
),
),
);
final Rect barRect = tester.getRect(find.byType(SearchBar));
expect(barRect.size, const Size(800.0, 56.0));
expect(barRect, equals(const Rect.fromLTRB(0.0, 272.0, 800.0, 328.0)));
final Rect leadingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.search));
// Default left padding is 8.0, and icon button has 8.0 padding, so in total the padding between
// the edge of the bar and the icon of the button is 16.0, which matches the spec.
expect(leadingIcon.left, equals(barRect.left + 8.0));
final Rect textField = tester.getRect(find.byType(TextField));
expect(textField.left, equals(leadingIcon.right + 8.0));
final Rect trailingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.menu));
expect(trailingIcon.left, equals(textField.right + 8.0));
expect(trailingIcon.right, equals(barRect.right - 8.0));
});
testWidgets('SearchBar has correct default layout and padding - RTL', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: SearchBar(
leading: IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
trailing: <Widget>[
IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
)
],
),
),
),
),
);
final Rect barRect = tester.getRect(find.byType(SearchBar));
expect(barRect.size, const Size(800.0, 56.0));
expect(barRect, equals(const Rect.fromLTRB(0.0, 272.0, 800.0, 328.0)));
// The default padding is set to 8.0 so the distance between the icon of the button
// and the edge of the bar is 16.0, which matches the spec.
final Rect leadingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.search));
expect(leadingIcon.right, equals(barRect.right - 8.0));
final Rect textField = tester.getRect(find.byType(TextField));
expect(textField.right, equals(leadingIcon.left - 8.0));
final Rect trailingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.menu));
expect(trailingIcon.right, equals(textField.left - 8.0));
expect(trailingIcon.left, equals(barRect.left + 8.0));
});
testWidgets('SearchBar respects hintText property', (WidgetTester tester) async {
const String hintText = 'hint text';
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: SearchBar(
hintText: hintText,
),
),
),
);
expect(find.text(hintText), findsOneWidget);
});
testWidgets('SearchBar respects leading property', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
final ColorScheme colorScheme = theme.colorScheme;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchBar(
leading: IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
),
),
),
);
expect(find.widgetWithIcon(IconButton, Icons.search), findsOneWidget);
final Color? iconColor = _iconStyle(tester, Icons.search)?.color;
expect(iconColor, colorScheme.onSurface); // Default icon color.
});
testWidgets('SearchBar respects trailing property', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
final ColorScheme colorScheme = theme.colorScheme;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchBar(
trailing: <Widget>[
IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
],
),
),
),
);
expect(find.widgetWithIcon(IconButton, Icons.menu), findsOneWidget);
final Color? iconColor = _iconStyle(tester, Icons.menu)?.color;
expect(iconColor, colorScheme.onSurfaceVariant); // Default icon color.
});
testWidgets('SearchBar respects onTap property', (WidgetTester tester) async {
int tapCount = 0;
await tester.pumpWidget(
MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: SearchBar(
onTap: () {
setState(() {
tapCount++;
});
}
),
);
}
),
),
);
expect(tapCount, 0);
await tester.tap(find.byType(SearchBar));
expect(tapCount, 1);
await tester.tap(find.byType(SearchBar));
expect(tapCount, 2);
});
testWidgets('SearchBar respects onChanged property', (WidgetTester tester) async {
int changeCount = 0;
await tester.pumpWidget(
MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: SearchBar(
onChanged: (_) {
setState(() {
changeCount++;
});
}
),
);
}
),
),
);
expect(changeCount, 0);
await tester.enterText(find.byType(SearchBar), 'a');
expect(changeCount, 1);
await tester.enterText(find.byType(SearchBar), 'b');
expect(changeCount, 2);
});
testWidgets('SearchBar respects constraints property', (WidgetTester tester) async {
const BoxConstraints constraints = BoxConstraints(maxWidth: 350.0, minHeight: 80);
await tester.pumpWidget(
const MaterialApp(
home: Center(
child: Material(
child: SearchBar(
constraints: constraints,
),
),
),
),
);
final Rect barRect = tester.getRect(find.byType(SearchBar));
expect(barRect.size, const Size(350.0, 80.0));
});
testWidgets('SearchBar respects elevation property', (WidgetTester tester) async {
const double pressedElevation = 0.0;
const double hoveredElevation = 1.0;
const double focusedElevation = 2.0;
const double defaultElevation = 3.0;
double getElevation(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedElevation;
}
if (states.contains(MaterialState.hovered)) {
return hoveredElevation;
}
if (states.contains(MaterialState.focused)) {
return focusedElevation;
}
return defaultElevation;
}
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
elevation: MaterialStateProperty.resolveWith<double>(getElevation),
),
),
),
),
);
final Finder searchBarMaterial = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(Material),
);
Material material = tester.widget<Material>(searchBarMaterial);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.elevation, hoveredElevation);
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
material = tester.widget<Material>(searchBarMaterial);
expect(material.elevation, pressedElevation);
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.elevation, focusedElevation);
});
testWidgets('SearchBar respects backgroundColor property', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
backgroundColor: MaterialStateProperty.resolveWith<Color>(_getColor),
),
),
),
),
);
final Finder searchBarMaterial = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(Material),
);
Material material = tester.widget<Material>(searchBarMaterial);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.color, hoveredColor);
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
material = tester.widget<Material>(searchBarMaterial);
expect(material.color, pressedColor);
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.color, focusedColor);
});
testWidgets('SearchBar respects shadowColor property', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
shadowColor: MaterialStateProperty.resolveWith<Color>(_getColor),
),
),
),
),
);
final Finder searchBarMaterial = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(Material),
);
Material material = tester.widget<Material>(searchBarMaterial);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.shadowColor, hoveredColor);
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
material = tester.widget<Material>(searchBarMaterial);
expect(material.shadowColor, pressedColor);
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.shadowColor, focusedColor);
});
testWidgets('SearchBar respects surfaceTintColor property', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
surfaceTintColor: MaterialStateProperty.resolveWith<Color>(_getColor),
),
),
),
),
);
final Finder searchBarMaterial = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(Material),
);
Material material = tester.widget<Material>(searchBarMaterial);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.surfaceTintColor, hoveredColor);
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
material = tester.widget<Material>(searchBarMaterial);
expect(material.surfaceTintColor, pressedColor);
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.surfaceTintColor, focusedColor);
});
testWidgets('SearchBar respects overlayColor property', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
focusNode: focusNode,
overlayColor: MaterialStateProperty.resolveWith<Color>(_getColor),
),
),
),
),
);
RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pumpAndSettle();
expect(inkFeatures, paints..rect(color: hoveredColor.withOpacity(1.0)));
// On pressed.
await tester.pumpAndSettle();
await tester.startGesture(tester.getCenter(find.byType(SearchBar)));
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..rect()..rect(color: pressedColor.withOpacity(1.0)));
await gesture.removePointer();
// On focused.
await tester.pumpAndSettle();
focusNode.requestFocus();
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..rect()..rect(color: focusedColor.withOpacity(1.0)));
});
testWidgets('SearchBar respects side and shape properties', (WidgetTester tester) async {
const BorderSide pressedSide = BorderSide(width: 2.0);
const BorderSide hoveredSide = BorderSide(width: 3.0);
const BorderSide focusedSide = BorderSide(width: 4.0);
const BorderSide defaultSide = BorderSide(width: 5.0);
const OutlinedBorder pressedShape = RoundedRectangleBorder();
const OutlinedBorder hoveredShape = ContinuousRectangleBorder();
const OutlinedBorder focusedShape = CircleBorder();
const OutlinedBorder defaultShape = StadiumBorder();
BorderSide getSide(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedSide;
}
if (states.contains(MaterialState.hovered)) {
return hoveredSide;
}
if (states.contains(MaterialState.focused)) {
return focusedSide;
}
return defaultSide;
}
OutlinedBorder getShape(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedShape;
}
if (states.contains(MaterialState.hovered)) {
return hoveredShape;
}
if (states.contains(MaterialState.focused)) {
return focusedShape;
}
return defaultShape;
}
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
side: MaterialStateProperty.resolveWith<BorderSide>(getSide),
shape: MaterialStateProperty.resolveWith<OutlinedBorder>(getShape),
),
),
),
),
);
final Finder searchBarMaterial = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(Material),
);
Material material = tester.widget<Material>(searchBarMaterial);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.shape, hoveredShape.copyWith(side: hoveredSide));
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
material = tester.widget<Material>(searchBarMaterial);
expect(material.shape, pressedShape.copyWith(side: pressedSide));
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
material = tester.widget<Material>(searchBarMaterial);
expect(material.shape, focusedShape.copyWith(side: focusedSide));
});
testWidgets('SearchBar respects padding property', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Center(
child: Material(
child: SearchBar(
leading: Icon(Icons.search),
padding: MaterialStatePropertyAll<EdgeInsets>(EdgeInsets.all(16.0)),
trailing: <Widget>[
Icon(Icons.menu),
]
),
),
),
),
);
final Rect barRect = tester.getRect(find.byType(SearchBar));
final Rect leadingRect = tester.getRect(find.byIcon(Icons.search));
final Rect textFieldRect = tester.getRect(find.byType(TextField));
final Rect trailingRect = tester.getRect(find.byIcon(Icons.menu));
expect(barRect.left, leadingRect.left - 16.0);
expect(leadingRect.right, textFieldRect.left - 16.0);
expect(textFieldRect.right, trailingRect.left - 16.0);
expect(trailingRect.right, barRect.right - 16.0);
});
testWidgets('SearchBar respects hintStyle property', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
hintText: 'hint text',
hintStyle: MaterialStateProperty.resolveWith<TextStyle?>(_getTextStyle),
),
),
),
),
);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
final Text helperText = tester.widget(find.text('hint text'));
expect(helperText.style?.color, hoveredColor);
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
expect(helperText.style?.color, hoveredColor);
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
expect(helperText.style?.color, hoveredColor);
});
testWidgets('SearchBar respects textStyle property', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: 'input text');
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
controller: controller,
textStyle: MaterialStateProperty.resolveWith<TextStyle?>(_getTextStyle),
),
),
),
),
);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
final EditableText inputText = tester.widget(find.text('input text'));
expect(inputText.style.color, hoveredColor);
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
expect(inputText.style.color, hoveredColor);
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
expect(inputText.style.color, hoveredColor);
});
testWidgets('hintStyle can override textStyle for hintText', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Material(
child: SearchBar(
hintText: 'hint text',
hintStyle: MaterialStateProperty.resolveWith<TextStyle?>(_getTextStyle),
textStyle: const MaterialStatePropertyAll<TextStyle>(TextStyle(color: Colors.pink)),
),
),
),
),
);
// On hovered.
final TestGesture gesture = await _pointGestureToSearchBar(tester);
await tester.pump();
final Text helperText = tester.widget(find.text('hint text'));
expect(helperText.style?.color, hoveredColor);
// On pressed.
await gesture.down(tester.getCenter(find.byType(SearchBar)));
await tester.pump();
await gesture.removePointer();
expect(helperText.style?.color, hoveredColor);
// On focused.
await tester.tap(find.byType(SearchBar));
await tester.pump();
expect(helperText.style?.color, hoveredColor);
});
}
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
final RichText iconRichText = tester.widget<RichText>(
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
);
return iconRichText.text.style;
}
const Color pressedColor = Colors.red;
const Color hoveredColor = Colors.orange;
const Color focusedColor = Colors.yellow;
const Color defaultColor = Colors.green;
Color _getColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
if (states.contains(MaterialState.hovered)) {
return hoveredColor;
}
if (states.contains(MaterialState.focused)) {
return focusedColor;
}
return defaultColor;
}
final ThemeData theme = ThemeData();
final TextStyle? pressedStyle = theme.textTheme.bodyLarge?.copyWith(color: pressedColor);
final TextStyle? hoveredStyle = theme.textTheme.bodyLarge?.copyWith(color: hoveredColor);
final TextStyle? focusedStyle = theme.textTheme.bodyLarge?.copyWith(color: focusedColor);
TextStyle? _getTextStyle(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedStyle;
}
if (states.contains(MaterialState.hovered)) {
return hoveredStyle;
}
if (states.contains(MaterialState.focused)) {
return focusedStyle;
}
return null;
}
Future<TestGesture> _pointGestureToSearchBar(WidgetTester tester) async {
final Offset center = tester.getCenter(find.byType(SearchBar));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
// On hovered.
await gesture.addPointer();
await gesture.moveTo(center);
return gesture;
}
// 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('SearchBarThemeData copyWith, ==, hashCode basics', () {
expect(const SearchBarThemeData(), const SearchBarThemeData().copyWith());
expect(const SearchBarThemeData().hashCode, const SearchBarThemeData()
.copyWith()
.hashCode);
});
test('SearchBarThemeData lerp special cases', () {
expect(SearchBarThemeData.lerp(null, null, 0), null);
const SearchBarThemeData data = SearchBarThemeData();
expect(identical(SearchBarThemeData.lerp(data, data, 0.5), data), true);
});
test('SearchBarThemeData defaults', () {
const SearchBarThemeData themeData = SearchBarThemeData();
expect(themeData.elevation, null);
expect(themeData.backgroundColor, null);
expect(themeData.shadowColor, null);
expect(themeData.surfaceTintColor, null);
expect(themeData.overlayColor, null);
expect(themeData.side, null);
expect(themeData.shape, null);
expect(themeData.padding, null);
expect(themeData.textStyle, null);
expect(themeData.hintStyle, null);
expect(themeData.constraints, null);
const SearchBarTheme theme = SearchBarTheme(data: SearchBarThemeData(), child: SizedBox());
expect(theme.data.elevation, null);
expect(theme.data.backgroundColor, null);
expect(theme.data.shadowColor, null);
expect(theme.data.surfaceTintColor, null);
expect(theme.data.overlayColor, null);
expect(theme.data.side, null);
expect(theme.data.shape, null);
expect(theme.data.padding, null);
expect(theme.data.textStyle, null);
expect(theme.data.hintStyle, null);
expect(theme.data.constraints, null);
});
testWidgets('Default SearchBarThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SearchBarThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('SearchBarThemeData implements debugFillProperties', (
WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SearchBarThemeData(
elevation: MaterialStatePropertyAll<double>(3.0),
backgroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffff1)),
shadowColor: MaterialStatePropertyAll<Color>(Color(0xfffffff2)),
surfaceTintColor: MaterialStatePropertyAll<Color>(Color(0xfffffff3)),
overlayColor: MaterialStatePropertyAll<Color>(Color(0xfffffff4)),
side: MaterialStatePropertyAll<BorderSide>(BorderSide(width: 2.0, color: Color(0xfffffff5))),
shape: MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder()),
padding: MaterialStatePropertyAll<EdgeInsets>(EdgeInsets.all(16.0)),
textStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 24.0)),
hintStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 16.0)),
constraints: BoxConstraints(minWidth: 350, maxWidth: 850),
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description[0], 'elevation: MaterialStatePropertyAll(3.0)');
expect(description[1], 'backgroundColor: MaterialStatePropertyAll(Color(0xfffffff1))');
expect(description[2], 'shadowColor: MaterialStatePropertyAll(Color(0xfffffff2))');
expect(description[3], 'surfaceTintColor: MaterialStatePropertyAll(Color(0xfffffff3))');
expect(description[4], 'overlayColor: MaterialStatePropertyAll(Color(0xfffffff4))');
expect(description[5], 'side: MaterialStatePropertyAll(BorderSide(color: Color(0xfffffff5), width: 2.0))');
expect(description[6], 'shape: MaterialStatePropertyAll(StadiumBorder(BorderSide(width: 0.0, style: none)))');
expect(description[7], 'padding: MaterialStatePropertyAll(EdgeInsets.all(16.0))');
expect(description[8], 'textStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 24.0))');
expect(description[9], 'hintStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 16.0))');
expect(description[10], 'constraints: BoxConstraints(350.0<=w<=850.0, 0.0<=h<=Infinity)');
});
group('[Theme, SearchBarTheme, SearchBar properties overrides]', () {
const double elevationValue = 5.0;
const Color backgroundColorValue = Color(0xff000001);
const Color shadowColorValue = Color(0xff000001);
const Color surfaceTintColorValue = Color(0xff000001);
const Color overlayColorValue = Color(0xff000001);
const BorderSide sideValue = BorderSide(color: Color(0xff000004), width: 2.0);
const OutlinedBorder shapeValue = RoundedRectangleBorder(side: sideValue, borderRadius: BorderRadius.all(Radius.circular(2.0)));
const EdgeInsets paddingValue = EdgeInsets.symmetric(horizontal: 16.0);
const TextStyle textStyleValue = TextStyle(color: Color(0xff000005), fontSize: 20.0);
const TextStyle hintStyleValue = TextStyle(color: Color(0xff000006), fontSize: 18.0);
const MaterialStateProperty<double?> elevation = MaterialStatePropertyAll<double>(elevationValue);
const MaterialStateProperty<Color?> backgroundColor = MaterialStatePropertyAll<Color>(backgroundColorValue);
const MaterialStateProperty<Color?> shadowColor = MaterialStatePropertyAll<Color>(shadowColorValue);
const MaterialStateProperty<Color?> surfaceTintColor = MaterialStatePropertyAll<Color>(surfaceTintColorValue);
const MaterialStateProperty<Color?> overlayColor = MaterialStatePropertyAll<Color>(overlayColorValue);
const MaterialStateProperty<BorderSide?> side = MaterialStatePropertyAll<BorderSide>(sideValue);
const MaterialStateProperty<OutlinedBorder?> shape = MaterialStatePropertyAll<OutlinedBorder>(shapeValue);
const MaterialStateProperty<EdgeInsetsGeometry?> padding = MaterialStatePropertyAll<EdgeInsets>(paddingValue);
const MaterialStateProperty<TextStyle?> textStyle = MaterialStatePropertyAll<TextStyle>(textStyleValue);
const MaterialStateProperty<TextStyle?> hintStyle = MaterialStatePropertyAll<TextStyle>(hintStyleValue);
const BoxConstraints constraints = BoxConstraints(minWidth: 250.0, maxWidth: 300.0, minHeight: 80.0);
const SearchBarThemeData searchBarTheme = SearchBarThemeData(
elevation: elevation,
backgroundColor: backgroundColor,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
overlayColor: overlayColor,
side: side,
shape: shape,
padding: padding,
textStyle: textStyle,
hintStyle: hintStyle,
constraints: constraints,
);
Widget buildFrame({
bool useSearchBarProperties = false,
SearchBarThemeData? searchBarThemeData,
SearchBarThemeData? overallTheme
}) {
final Widget child = Builder(
builder: (BuildContext context) {
if (!useSearchBarProperties) {
return const SearchBar(
hintText: 'hint',
leading: Icon(Icons.search),
trailing: <Widget>[ Icon(Icons.menu)],
);
}
return const SearchBar(
hintText: 'hint',
leading: Icon(Icons.search),
trailing: <Widget>[ Icon(Icons.menu)],
elevation: elevation,
backgroundColor: backgroundColor,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
overlayColor: overlayColor,
side: side,
shape: shape,
padding: padding,
textStyle: textStyle,
hintStyle: hintStyle,
constraints: constraints,
);
},
);
return MaterialApp(
theme: ThemeData.from(
colorScheme: const ColorScheme.light(), useMaterial3: true)
.copyWith(
searchBarTheme: overallTheme,
),
home: Scaffold(
body: Center(
// If the SearchBarThemeData widget is present, it's used
// instead of the Theme's ThemeData.searchBarTheme.
child: searchBarThemeData == null ? child : SearchBarTheme(
data: searchBarThemeData,
child: child,
),
),
),
);
}
final Finder findMaterial = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(Material),
);
final Finder findInkWell = find.descendant(
of: find.byType(SearchBar),
matching: find.byType(InkWell),
);
const Set<MaterialState> hovered = <MaterialState>{ MaterialState.hovered};
const Set<MaterialState> focused = <MaterialState>{ MaterialState.focused};
const Set<MaterialState> pressed = <MaterialState>{ MaterialState.pressed};
Future<void> checkSearchBar(WidgetTester tester) async {
final Material material = tester.widget<Material>(findMaterial);
final InkWell inkWell = tester.widget<InkWell>(findInkWell);
expect(material.elevation, elevationValue);
expect(material.color, backgroundColorValue);
expect(material.shadowColor, shadowColorValue);
expect(material.surfaceTintColor, surfaceTintColorValue);
expect(material.shape, shapeValue);
expect(inkWell.overlayColor!.resolve(hovered), overlayColor.resolve(hovered));
expect(inkWell.overlayColor!.resolve(focused), overlayColor.resolve(focused));
expect(inkWell.overlayColor!.resolve(pressed), overlayColor.resolve(pressed));
expect(inkWell.customBorder, shapeValue);
expect(tester.getSize(find.byType(SearchBar)), const Size(300.0, 80.0));
final Text hintText = tester.widget(find.text('hint'));
expect(hintText.style?.color, hintStyleValue.color);
expect(hintText.style?.fontSize, hintStyleValue.fontSize);
await tester.enterText(find.byType(TextField), 'input');
final EditableText inputText = tester.widget(find.text('input'));
expect(inputText.style.color, textStyleValue.color);
expect(inputText.style.fontSize, textStyleValue.fontSize);
final Rect barRect = tester.getRect(find.byType(SearchBar));
final Rect leadingRect = tester.getRect(find.byIcon(Icons.search));
final Rect textFieldRect = tester.getRect(find.byType(TextField));
final Rect trailingRect = tester.getRect(find.byIcon(Icons.menu));
expect(barRect.left, leadingRect.left - 16.0);
expect(leadingRect.right, textFieldRect.left - 16.0);
expect(textFieldRect.right, trailingRect.left - 16.0);
expect(trailingRect.right, barRect.right - 16.0);
}
testWidgets('SearchBar properties overrides defaults', (WidgetTester tester) async {
await tester.pumpWidget(buildFrame(useSearchBarProperties: true));
await tester.pumpAndSettle(); // allow the animations to finish
checkSearchBar(tester);
});
testWidgets('SearchBar theme data overrides defaults', (WidgetTester tester) async {
await tester.pumpWidget(buildFrame(searchBarThemeData: searchBarTheme));
await tester.pumpAndSettle();
checkSearchBar(tester);
});
testWidgets('Overall Theme SearchBar theme overrides defaults', (WidgetTester tester) async {
await tester.pumpWidget(buildFrame(overallTheme: searchBarTheme));
await tester.pumpAndSettle();
checkSearchBar(tester);
});
// Same as the previous tests with empty SearchBarThemeData's instead of null.
testWidgets('SearchBar properties overrides defaults, empty theme and overall theme', (WidgetTester tester) async {
await tester.pumpWidget(buildFrame(useSearchBarProperties: true,
searchBarThemeData: const SearchBarThemeData(),
overallTheme: const SearchBarThemeData()));
await tester.pumpAndSettle(); // allow the animations to finish
checkSearchBar(tester);
});
testWidgets('SearchBar theme overrides defaults and overall theme', (WidgetTester tester) async {
await tester.pumpWidget(buildFrame(searchBarThemeData: searchBarTheme,
overallTheme: const SearchBarThemeData()));
await tester.pumpAndSettle(); // allow the animations to finish
checkSearchBar(tester);
});
testWidgets('Overall Theme SearchBar theme overrides defaults and null theme', (WidgetTester tester) async {
await tester.pumpWidget(buildFrame(overallTheme: searchBarTheme));
await tester.pumpAndSettle(); // allow the animations to finish
checkSearchBar(tester);
});
});
}
...@@ -791,6 +791,7 @@ void main() { ...@@ -791,6 +791,7 @@ void main() {
popupMenuTheme: const PopupMenuThemeData(color: Colors.black), popupMenuTheme: const PopupMenuThemeData(color: Colors.black),
progressIndicatorTheme: const ProgressIndicatorThemeData(), progressIndicatorTheme: const ProgressIndicatorThemeData(),
radioTheme: const RadioThemeData(), radioTheme: const RadioThemeData(),
searchBarTheme: const SearchBarThemeData(),
segmentedButtonTheme: const SegmentedButtonThemeData(), segmentedButtonTheme: const SegmentedButtonThemeData(),
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
snackBarTheme: const SnackBarThemeData(backgroundColor: Colors.black), snackBarTheme: const SnackBarThemeData(backgroundColor: Colors.black),
...@@ -909,6 +910,7 @@ void main() { ...@@ -909,6 +910,7 @@ void main() {
popupMenuTheme: const PopupMenuThemeData(color: Colors.white), popupMenuTheme: const PopupMenuThemeData(color: Colors.white),
progressIndicatorTheme: const ProgressIndicatorThemeData(), progressIndicatorTheme: const ProgressIndicatorThemeData(),
radioTheme: const RadioThemeData(), radioTheme: const RadioThemeData(),
searchBarTheme: const SearchBarThemeData(),
segmentedButtonTheme: const SegmentedButtonThemeData(), segmentedButtonTheme: const SegmentedButtonThemeData(),
sliderTheme: otherSliderTheme, sliderTheme: otherSliderTheme,
snackBarTheme: const SnackBarThemeData(backgroundColor: Colors.white), snackBarTheme: const SnackBarThemeData(backgroundColor: Colors.white),
...@@ -1012,6 +1014,7 @@ void main() { ...@@ -1012,6 +1014,7 @@ void main() {
popupMenuTheme: otherTheme.popupMenuTheme, popupMenuTheme: otherTheme.popupMenuTheme,
progressIndicatorTheme: otherTheme.progressIndicatorTheme, progressIndicatorTheme: otherTheme.progressIndicatorTheme,
radioTheme: otherTheme.radioTheme, radioTheme: otherTheme.radioTheme,
searchBarTheme: otherTheme.searchBarTheme,
sliderTheme: otherTheme.sliderTheme, sliderTheme: otherTheme.sliderTheme,
snackBarTheme: otherTheme.snackBarTheme, snackBarTheme: otherTheme.snackBarTheme,
switchTheme: otherTheme.switchTheme, switchTheme: otherTheme.switchTheme,
...@@ -1112,6 +1115,7 @@ void main() { ...@@ -1112,6 +1115,7 @@ void main() {
expect(themeDataCopy.popupMenuTheme, equals(otherTheme.popupMenuTheme)); expect(themeDataCopy.popupMenuTheme, equals(otherTheme.popupMenuTheme));
expect(themeDataCopy.progressIndicatorTheme, equals(otherTheme.progressIndicatorTheme)); expect(themeDataCopy.progressIndicatorTheme, equals(otherTheme.progressIndicatorTheme));
expect(themeDataCopy.radioTheme, equals(otherTheme.radioTheme)); expect(themeDataCopy.radioTheme, equals(otherTheme.radioTheme));
expect(themeDataCopy.searchBarTheme, equals(otherTheme.searchBarTheme));
expect(themeDataCopy.sliderTheme, equals(otherTheme.sliderTheme)); expect(themeDataCopy.sliderTheme, equals(otherTheme.sliderTheme));
expect(themeDataCopy.snackBarTheme, equals(otherTheme.snackBarTheme)); expect(themeDataCopy.snackBarTheme, equals(otherTheme.snackBarTheme));
expect(themeDataCopy.switchTheme, equals(otherTheme.switchTheme)); expect(themeDataCopy.switchTheme, equals(otherTheme.switchTheme));
...@@ -1249,6 +1253,7 @@ void main() { ...@@ -1249,6 +1253,7 @@ void main() {
'popupMenuTheme', 'popupMenuTheme',
'progressIndicatorTheme', 'progressIndicatorTheme',
'radioTheme', 'radioTheme',
'searchBarTheme',
'segmentedButtonTheme', 'segmentedButtonTheme',
'sliderTheme', 'sliderTheme',
'snackBarTheme', 'snackBarTheme',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment