Add textCapitalization
property for SearchBar
and SearchAnchor
(#131459)
This is to add `textCapitalization` property for `SearchBar` and `SearchAnchor`. Fixes: #131260
This commit is contained in:
parent
40a3e4817e
commit
0bc5a2bca4
@ -71,6 +71,9 @@ class _SearchBarDefaultsM3 extends SearchBarThemeData {
|
||||
@override
|
||||
BoxConstraints get constraints =>
|
||||
const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: ${getToken('md.comp.search-bar.container.height')});
|
||||
|
||||
@override
|
||||
TextCapitalization get textCapitalization => TextCapitalization.none;
|
||||
}
|
||||
''';
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ class SearchAnchor extends StatefulWidget {
|
||||
this.headerHintStyle,
|
||||
this.dividerColor,
|
||||
this.viewConstraints,
|
||||
this.textCapitalization,
|
||||
required this.builder,
|
||||
required this.suggestionsBuilder,
|
||||
});
|
||||
@ -170,6 +171,7 @@ class SearchAnchor extends StatefulWidget {
|
||||
BoxConstraints? viewConstraints,
|
||||
bool? isFullScreen,
|
||||
SearchController searchController,
|
||||
TextCapitalization textCapitalization,
|
||||
required SuggestionsBuilder suggestionsBuilder
|
||||
}) = _SearchAnchorWithSearchBar;
|
||||
|
||||
@ -286,6 +288,9 @@ class SearchAnchor extends StatefulWidget {
|
||||
/// ```
|
||||
final BoxConstraints? viewConstraints;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.textCapitalization}
|
||||
final TextCapitalization? textCapitalization;
|
||||
|
||||
/// Called to create a widget which can open a search view route when it is tapped.
|
||||
///
|
||||
/// The widget returned by this builder is faded out when it is tapped.
|
||||
@ -361,6 +366,7 @@ class _SearchAnchorState extends State<SearchAnchor> {
|
||||
anchorKey: _anchorKey,
|
||||
searchController: _searchController,
|
||||
suggestionsBuilder: widget.suggestionsBuilder,
|
||||
textCapitalization: widget.textCapitalization,
|
||||
));
|
||||
}
|
||||
|
||||
@ -426,6 +432,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
|
||||
this.viewHeaderHintStyle,
|
||||
this.dividerColor,
|
||||
this.viewConstraints,
|
||||
this.textCapitalization,
|
||||
required this.showFullScreenView,
|
||||
required this.anchorKey,
|
||||
required this.searchController,
|
||||
@ -447,6 +454,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
|
||||
final TextStyle? viewHeaderHintStyle;
|
||||
final Color? dividerColor;
|
||||
final BoxConstraints? viewConstraints;
|
||||
final TextCapitalization? textCapitalization;
|
||||
final bool showFullScreenView;
|
||||
final GlobalKey anchorKey;
|
||||
final SearchController searchController;
|
||||
@ -595,6 +603,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
|
||||
viewBuilder: viewBuilder,
|
||||
searchController: searchController,
|
||||
suggestionsBuilder: suggestionsBuilder,
|
||||
textCapitalization: textCapitalization,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -620,6 +629,7 @@ class _ViewContent extends StatefulWidget {
|
||||
this.viewHeaderTextStyle,
|
||||
this.viewHeaderHintStyle,
|
||||
this.dividerColor,
|
||||
this.textCapitalization,
|
||||
required this.showFullScreenView,
|
||||
required this.topPadding,
|
||||
required this.animation,
|
||||
@ -644,6 +654,7 @@ class _ViewContent extends StatefulWidget {
|
||||
final TextStyle? viewHeaderTextStyle;
|
||||
final TextStyle? viewHeaderHintStyle;
|
||||
final Color? dividerColor;
|
||||
final TextCapitalization? textCapitalization;
|
||||
final bool showFullScreenView;
|
||||
final double topPadding;
|
||||
final Animation<double> animation;
|
||||
@ -824,6 +835,7 @@ class _ViewContentState extends State<_ViewContent> {
|
||||
onChanged: (_) {
|
||||
updateSuggestions();
|
||||
},
|
||||
textCapitalization: widget.textCapitalization,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -884,6 +896,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
|
||||
super.viewConstraints,
|
||||
super.isFullScreen,
|
||||
super.searchController,
|
||||
super.textCapitalization,
|
||||
required super.suggestionsBuilder
|
||||
}) : super(
|
||||
viewHintText: viewHintText ?? barHintText,
|
||||
@ -911,6 +924,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
|
||||
padding: barPadding ?? const MaterialStatePropertyAll<EdgeInsets>(EdgeInsets.symmetric(horizontal: 16.0)),
|
||||
leading: barLeading ?? const Icon(Icons.search),
|
||||
trailing: barTrailing,
|
||||
textCapitalization: textCapitalization,
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -1022,6 +1036,7 @@ class SearchBar extends StatefulWidget {
|
||||
this.padding,
|
||||
this.textStyle,
|
||||
this.hintStyle,
|
||||
this.textCapitalization,
|
||||
});
|
||||
|
||||
/// Controls the text being edited in the search bar's text field.
|
||||
@ -1140,6 +1155,9 @@ class SearchBar extends StatefulWidget {
|
||||
/// The default text color is [ColorScheme.onSurfaceVariant].
|
||||
final MaterialStateProperty<TextStyle?>? hintStyle;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.textCapitalization}
|
||||
final TextCapitalization? textCapitalization;
|
||||
|
||||
@override
|
||||
State<SearchBar> createState() => _SearchBarState();
|
||||
}
|
||||
@ -1190,6 +1208,7 @@ class _SearchBarState extends State<SearchBar> {
|
||||
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 TextCapitalization effectiveTextCapitalization = widget.textCapitalization ?? searchBarTheme.textCapitalization ?? defaults.textCapitalization!;
|
||||
|
||||
final Set<MaterialState> states = _internalStatesController.value;
|
||||
final TextStyle? effectiveHintStyle = widget.hintStyle?.resolve(states)
|
||||
@ -1273,6 +1292,7 @@ class _SearchBarState extends State<SearchBar> {
|
||||
// smaller than 48.0
|
||||
isDense: true,
|
||||
)),
|
||||
textCapitalization: effectiveTextCapitalization,
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -1353,6 +1373,9 @@ class _SearchBarDefaultsM3 extends SearchBarThemeData {
|
||||
@override
|
||||
BoxConstraints get constraints =>
|
||||
const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: 56.0);
|
||||
|
||||
@override
|
||||
TextCapitalization get textCapitalization => TextCapitalization.none;
|
||||
}
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES - SearchBar
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../../services.dart';
|
||||
import 'material_state.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
@ -47,6 +48,7 @@ class SearchBarThemeData with Diagnosticable {
|
||||
this.textStyle,
|
||||
this.hintStyle,
|
||||
this.constraints,
|
||||
this.textCapitalization,
|
||||
});
|
||||
|
||||
/// Overrides the default value of the [SearchBar.elevation].
|
||||
@ -82,6 +84,9 @@ class SearchBarThemeData with Diagnosticable {
|
||||
/// Overrides the value of size constraints for [SearchBar].
|
||||
final BoxConstraints? constraints;
|
||||
|
||||
/// Overrides the value of [SearchBar.textCapitalization].
|
||||
final TextCapitalization? textCapitalization;
|
||||
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
SearchBarThemeData copyWith({
|
||||
@ -96,6 +101,7 @@ class SearchBarThemeData with Diagnosticable {
|
||||
MaterialStateProperty<TextStyle?>? textStyle,
|
||||
MaterialStateProperty<TextStyle?>? hintStyle,
|
||||
BoxConstraints? constraints,
|
||||
TextCapitalization? textCapitalization,
|
||||
}) {
|
||||
return SearchBarThemeData(
|
||||
elevation: elevation ?? this.elevation,
|
||||
@ -109,6 +115,7 @@ class SearchBarThemeData with Diagnosticable {
|
||||
textStyle: textStyle ?? this.textStyle,
|
||||
hintStyle: hintStyle ?? this.hintStyle,
|
||||
constraints: constraints ?? this.constraints,
|
||||
textCapitalization: textCapitalization ?? this.textCapitalization,
|
||||
);
|
||||
}
|
||||
|
||||
@ -131,6 +138,7 @@ class SearchBarThemeData with Diagnosticable {
|
||||
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),
|
||||
textCapitalization: t < 0.5 ? a?.textCapitalization : b?.textCapitalization,
|
||||
);
|
||||
}
|
||||
|
||||
@ -147,6 +155,7 @@ class SearchBarThemeData with Diagnosticable {
|
||||
textStyle,
|
||||
hintStyle,
|
||||
constraints,
|
||||
textCapitalization,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -168,7 +177,8 @@ class SearchBarThemeData with Diagnosticable {
|
||||
&& other.padding == padding
|
||||
&& other.textStyle == textStyle
|
||||
&& other.hintStyle == hintStyle
|
||||
&& other.constraints == constraints;
|
||||
&& other.constraints == constraints
|
||||
&& other.textCapitalization == textCapitalization;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -185,6 +195,7 @@ class SearchBarThemeData with Diagnosticable {
|
||||
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));
|
||||
properties.add(DiagnosticsProperty<TextCapitalization>('textCapitalization', textCapitalization, defaultValue: null));
|
||||
}
|
||||
|
||||
// Special case because BorderSide.lerp() doesn't support null arguments
|
||||
|
@ -691,6 +691,108 @@ void main() {
|
||||
expect(inputText.style.color, focusedColor);
|
||||
});
|
||||
|
||||
testWidgets('SearchBar respects textCapitalization property', (WidgetTester tester) async {
|
||||
Widget buildSearchBar(TextCapitalization textCapitalization) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: Material(
|
||||
child: SearchBar(
|
||||
textCapitalization: textCapitalization,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await tester.pumpWidget(buildSearchBar(TextCapitalization.characters));
|
||||
await tester.pump();
|
||||
TextField textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.textCapitalization, TextCapitalization.characters);
|
||||
|
||||
await tester.pumpWidget(buildSearchBar(TextCapitalization.sentences));
|
||||
await tester.pump();
|
||||
textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.textCapitalization, TextCapitalization.sentences);
|
||||
|
||||
await tester.pumpWidget(buildSearchBar(TextCapitalization.words));
|
||||
await tester.pump();
|
||||
textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.textCapitalization, TextCapitalization.words);
|
||||
|
||||
await tester.pumpWidget(buildSearchBar(TextCapitalization.none));
|
||||
await tester.pump();
|
||||
textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.textCapitalization, TextCapitalization.none);
|
||||
});
|
||||
|
||||
testWidgets('SearchAnchor respects textCapitalization property', (WidgetTester tester) async {
|
||||
Widget buildSearchAnchor(TextCapitalization textCapitalization) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: Material(
|
||||
child: SearchAnchor(
|
||||
textCapitalization: textCapitalization,
|
||||
builder: (BuildContext context, SearchController controller) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
onPressed: () {
|
||||
controller.openView();
|
||||
},
|
||||
);
|
||||
},
|
||||
suggestionsBuilder: (BuildContext context, SearchController controller) {
|
||||
return <Widget>[];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await tester.pumpWidget(buildSearchAnchor(TextCapitalization.characters));
|
||||
await tester.pump();
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
|
||||
await tester.pumpAndSettle();
|
||||
TextField textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.textCapitalization, TextCapitalization.characters);
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
|
||||
await tester.pump();
|
||||
|
||||
await tester.pumpWidget(buildSearchAnchor(TextCapitalization.none));
|
||||
await tester.pump();
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
|
||||
await tester.pumpAndSettle();
|
||||
textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.textCapitalization, TextCapitalization.none);
|
||||
});
|
||||
|
||||
testWidgets('SearchAnchor.bar respects textCapitalization property', (WidgetTester tester) async {
|
||||
Widget buildSearchAnchor(TextCapitalization textCapitalization) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: Material(
|
||||
child: SearchAnchor.bar(
|
||||
textCapitalization: textCapitalization,
|
||||
suggestionsBuilder: (BuildContext context, SearchController controller) {
|
||||
return <Widget>[];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await tester.pumpWidget(buildSearchAnchor(TextCapitalization.characters));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byType(SearchBar)); // Open search view.
|
||||
await tester.pumpAndSettle();
|
||||
final Finder textFieldFinder = find.descendant(of: findViewContent(), matching: find.byType(TextField));
|
||||
final TextField textFieldInView = tester.widget<TextField>(textFieldFinder);
|
||||
expect(textFieldInView.textCapitalization, TextCapitalization.characters);
|
||||
// Close search view.
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
|
||||
await tester.pumpAndSettle();
|
||||
final TextField textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.textCapitalization, TextCapitalization.characters);
|
||||
});
|
||||
|
||||
testWidgets('hintStyle can override textStyle for hintText', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
@ -35,6 +35,7 @@ void main() {
|
||||
expect(themeData.textStyle, null);
|
||||
expect(themeData.hintStyle, null);
|
||||
expect(themeData.constraints, null);
|
||||
expect(themeData.textCapitalization, null);
|
||||
|
||||
const SearchBarTheme theme = SearchBarTheme(data: SearchBarThemeData(), child: SizedBox());
|
||||
expect(theme.data.elevation, null);
|
||||
@ -48,6 +49,7 @@ void main() {
|
||||
expect(theme.data.textStyle, null);
|
||||
expect(theme.data.hintStyle, null);
|
||||
expect(theme.data.constraints, null);
|
||||
expect(theme.data.textCapitalization, null);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Default SearchBarThemeData debugFillProperties', (WidgetTester tester) async {
|
||||
@ -77,6 +79,7 @@ void main() {
|
||||
textStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 24.0)),
|
||||
hintStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 16.0)),
|
||||
constraints: BoxConstraints(minWidth: 350, maxWidth: 850),
|
||||
textCapitalization: TextCapitalization.characters,
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
@ -95,6 +98,7 @@ void main() {
|
||||
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)');
|
||||
expect(description[11], 'textCapitalization: TextCapitalization.characters');
|
||||
});
|
||||
|
||||
group('[Theme, SearchBarTheme, SearchBar properties overrides]', () {
|
||||
@ -120,6 +124,7 @@ void main() {
|
||||
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 TextCapitalization textCapitalization = TextCapitalization.words;
|
||||
|
||||
const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
||||
elevation: elevation,
|
||||
@ -133,6 +138,7 @@ void main() {
|
||||
textStyle: textStyle,
|
||||
hintStyle: hintStyle,
|
||||
constraints: constraints,
|
||||
textCapitalization: textCapitalization,
|
||||
);
|
||||
|
||||
Widget buildFrame({
|
||||
@ -164,6 +170,7 @@ void main() {
|
||||
textStyle: textStyle,
|
||||
hintStyle: hintStyle,
|
||||
constraints: constraints,
|
||||
textCapitalization: textCapitalization,
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -223,6 +230,7 @@ void main() {
|
||||
final EditableText inputText = tester.widget(find.text('input'));
|
||||
expect(inputText.style.color, textStyleValue.color);
|
||||
expect(inputText.style.fontSize, textStyleValue.fontSize);
|
||||
expect(inputText.textCapitalization, textCapitalization);
|
||||
|
||||
final Rect barRect = tester.getRect(find.byType(SearchBar));
|
||||
final Rect leadingRect = tester.getRect(find.byIcon(Icons.search));
|
||||
|
Loading…
x
Reference in New Issue
Block a user