blob: 33aeeca055b85b8280cafedeb26c51ad8d6b916e [file] [log] [blame]
// 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