diff --git a/packages/flutter/lib/src/material/search_anchor.dart b/packages/flutter/lib/src/material/search_anchor.dart index 212774273cd4..4c813cf2697f 100644 --- a/packages/flutter/lib/src/material/search_anchor.dart +++ b/packages/flutter/lib/src/material/search_anchor.dart @@ -35,6 +35,7 @@ const Curve _kViewFadeOnInterval = Interval(0.0, 1/2); const Curve _kViewIconsFadeOnInterval = Interval(1/6, 2/6); const Curve _kViewDividerFadeOnInterval = Interval(0.0, 1/6); const Curve _kViewListFadeOnInterval = Interval(133 / _kOpenViewMilliseconds, 233 / _kOpenViewMilliseconds); +const double _kDisableSearchBarOpacity = 0.38; /// Signature for a function that creates a [Widget] which is used to open a search view. /// @@ -1115,6 +1116,7 @@ class SearchBar extends StatefulWidget { this.textStyle, this.hintStyle, this.textCapitalization, + this.enabled = true, this.autoFocus = false, this.textInputAction, this.keyboardType, @@ -1239,6 +1241,9 @@ class SearchBar extends StatefulWidget { /// {@macro flutter.widgets.editableText.textCapitalization} final TextCapitalization? textCapitalization; + /// If false the text field is "disabled" so the SearchBar will ignore taps. + final bool enabled; + /// {@macro flutter.widgets.editableText.autofocus} final bool autoFocus; @@ -1340,62 +1345,69 @@ class _SearchBarState extends State { return 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(); - if (!_focusNode.hasFocus) { - _focusNode.requestFocus(); - } - }, - overlayColor: effectiveOverlayColor, - customBorder: effectiveShape?.copyWith(side: effectiveSide), - statesController: _internalStatesController, - child: Padding( - padding: effectivePadding!, - child: Row( - textDirection: textDirection, - children: [ - if (leading != null) leading, - Expanded( - child: Padding( - padding: effectivePadding, - child: TextField( - autofocus: widget.autoFocus, - onTap: widget.onTap, - onTapAlwaysCalled: true, - focusNode: _focusNode, - onChanged: widget.onChanged, - onSubmitted: widget.onSubmitted, - controller: widget.controller, - style: effectiveTextStyle, - decoration: InputDecoration( - hintText: widget.hintText, - ).applyDefaults(InputDecorationTheme( - hintStyle: effectiveHintStyle, - // The configuration below is to make sure that the text field - // in `SearchBar` will not be overridden by the overall `InputDecorationTheme` - enabledBorder: InputBorder.none, - border: InputBorder.none, - focusedBorder: InputBorder.none, - contentPadding: EdgeInsets.zero, - // Setting `isDense` to true to allow the text field height to be - // smaller than 48.0 - isDense: true, - )), - textCapitalization: effectiveTextCapitalization, - textInputAction: widget.textInputAction, - keyboardType: widget.keyboardType, + child: Opacity( + opacity: widget.enabled ? 1 : _kDisableSearchBarOpacity, + child: Material( + elevation: effectiveElevation!, + shadowColor: effectiveShadowColor, + color: effectiveBackgroundColor, + surfaceTintColor: effectiveSurfaceTintColor, + shape: effectiveShape?.copyWith(side: effectiveSide), + child: IgnorePointer( + ignoring: !widget.enabled, + child: InkWell( + onTap: () { + widget.onTap?.call(); + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + }, + overlayColor: effectiveOverlayColor, + customBorder: effectiveShape?.copyWith(side: effectiveSide), + statesController: _internalStatesController, + child: Padding( + padding: effectivePadding!, + child: Row( + textDirection: textDirection, + children: [ + if (leading != null) leading, + Expanded( + child: Padding( + padding: effectivePadding, + child: TextField( + autofocus: widget.autoFocus, + onTap: widget.onTap, + onTapAlwaysCalled: true, + focusNode: _focusNode, + onChanged: widget.onChanged, + onSubmitted: widget.onSubmitted, + controller: widget.controller, + style: effectiveTextStyle, + enabled: widget.enabled, + decoration: InputDecoration( + hintText: widget.hintText, + ).applyDefaults(InputDecorationTheme( + hintStyle: effectiveHintStyle, + // The configuration below is to make sure that the text field + // in `SearchBar` will not be overridden by the overall `InputDecorationTheme` + enabledBorder: InputBorder.none, + border: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, + // Setting `isDense` to true to allow the text field height to be + // smaller than 48.0 + isDense: true, + )), + textCapitalization: effectiveTextCapitalization, + textInputAction: widget.textInputAction, + keyboardType: widget.keyboardType, + ), + ), ), - ), + if (trailing != null) ...trailing, + ], ), - if (trailing != null) ...trailing, - ], + ), ), ), ), diff --git a/packages/flutter/test/material/search_anchor_test.dart b/packages/flutter/test/material/search_anchor_test.dart index c9ab11648353..a0d41cd93b24 100644 --- a/packages/flutter/test/material/search_anchor_test.dart +++ b/packages/flutter/test/material/search_anchor_test.dart @@ -10,6 +10,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/semantics_tester.dart'; + void main() { // Returns the RenderEditable at the given index, or the first if not given. RenderEditable findRenderEditable(WidgetTester tester, {int index = 0}) { @@ -2945,6 +2947,72 @@ void main() { final TextField textField = tester.widget(find.byType(TextField)); expect(textField.textInputAction, TextInputAction.previous); }); + + testWidgets('Block entering text on disabled widget', (WidgetTester tester) async { + const String initValue = 'init'; + final TextEditingController controller = TextEditingController(text: initValue); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: SearchBar( + controller: controller, + enabled: false, + ), + ), + ), + ), + ); + + const String testValue = 'abcdefghi'; + await tester.enterText(find.byType(SearchBar), testValue); + expect(controller.value.text, initValue); + }); + + testWidgets('Disabled SearchBar semantics node still contains value', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: SearchBar( + controller: TextEditingController(text: 'text'), + enabled: false, + ), + ), + ), + ), + ); + + expect(semantics, includesNodeWith(actions: [], value: 'text')); + semantics.dispose(); + }); + + testWidgets('Check SearchBar opacity when disabled', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Material( + child: Center( + child: SearchBar( + enabled: false, + ), + ), + ), + ), + ); + + final Finder searchBarFinder = find.byType(SearchBar); + expect(searchBarFinder, findsOneWidget); + final Finder opacityFinder = find.descendant( + of: searchBarFinder, + matching: find.byType(Opacity), + ); + expect(opacityFinder, findsOneWidget); + final Opacity opacityWidget = tester.widget(opacityFinder); + expect(opacityWidget.opacity, 0.38); + }); } Future checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async {