From e96aaab5e01159b3bfada6930f4a427842d8438e Mon Sep 17 00:00:00 2001 From: Rexios Date: Tue, 15 Oct 2024 16:24:48 -0400 Subject: [PATCH] Add padding options to `SearchAnchor` (#152508) Cherry-picked padding changes from https://github.com/flutter/flutter/pull/148856 Adds padding configuration options to `SearchAnchor`. This PR adds the following: - `viewBarPadding`: The padding for items inside the `SearchBar` in the `SearchAnchor` view - `viewPadding`: The padding to use around the entire `SearchAnchor` view Working towards https://github.com/flutter/flutter/issues/148852 --- .../lib/search_view_template.dart | 3 + .../lib/src/material/search_anchor.dart | 145 ++++++++++++------ .../lib/src/material/search_view_theme.dart | 20 +++ .../test/material/search_anchor_test.dart | 77 ++++++++++ .../test/material/search_view_theme_test.dart | 8 + 5 files changed, 202 insertions(+), 51 deletions(-) diff --git a/dev/tools/gen_defaults/lib/search_view_template.dart b/dev/tools/gen_defaults/lib/search_view_template.dart index 44f7b5c427..77406ffc28 100644 --- a/dev/tools/gen_defaults/lib/search_view_template.dart +++ b/dev/tools/gen_defaults/lib/search_view_template.dart @@ -47,6 +47,9 @@ class _${blockName}DefaultsM3 extends ${blockName}ThemeData { @override BoxConstraints get constraints => const BoxConstraints(minWidth: 360.0, minHeight: 240.0); + @override + EdgeInsetsGeometry? get barPadding => const EdgeInsets.symmetric(horizontal: 8.0); + @override Color? get dividerColor => ${componentColor('md.comp.search-view.divider')}; } diff --git a/packages/flutter/lib/src/material/search_anchor.dart b/packages/flutter/lib/src/material/search_anchor.dart index 0d7e3a4462..3cd1297fd4 100644 --- a/packages/flutter/lib/src/material/search_anchor.dart +++ b/packages/flutter/lib/src/material/search_anchor.dart @@ -126,11 +126,13 @@ class SearchAnchor extends StatefulWidget { this.viewSurfaceTintColor, this.viewSide, this.viewShape, + this.viewBarPadding, this.headerHeight, this.headerTextStyle, this.headerHintStyle, this.dividerColor, this.viewConstraints, + this.viewPadding, this.textCapitalization, this.viewOnChanged, this.viewOnSubmitted, @@ -165,6 +167,7 @@ class SearchAnchor extends StatefulWidget { MaterialStateProperty? barSide, MaterialStateProperty? barShape, MaterialStateProperty? barPadding, + EdgeInsetsGeometry? viewBarPadding, MaterialStateProperty? barTextStyle, MaterialStateProperty? barHintStyle, Widget? viewLeading, @@ -180,6 +183,7 @@ class SearchAnchor extends StatefulWidget { Color? dividerColor, BoxConstraints? constraints, BoxConstraints? viewConstraints, + EdgeInsetsGeometry? viewPadding, bool? isFullScreen, SearchController searchController, TextCapitalization textCapitalization, @@ -272,6 +276,11 @@ class SearchAnchor extends StatefulWidget { /// mode and a [RoundedRectangleBorder] shape with a 28.0 radius otherwise. final OutlinedBorder? viewShape; + /// The padding to use for the search view's search bar. + /// + /// If null, then the default value is 8.0 horizontally. + final EdgeInsetsGeometry? viewBarPadding; + /// The height of the search field on the search view. /// /// If null, the value of [SearchViewThemeData.headerHeight] will be used. If @@ -312,6 +321,13 @@ class SearchAnchor extends StatefulWidget { /// ``` final BoxConstraints? viewConstraints; + /// The padding to use for the search view. + /// + /// Has no effect if the search view is full-screen. + /// + /// If null, the value of [SearchViewThemeData.padding] will be used. + final EdgeInsetsGeometry? viewPadding; + /// {@macro flutter.widgets.editableText.textCapitalization} final TextCapitalization? textCapitalization; @@ -431,11 +447,13 @@ class _SearchAnchorState extends State { viewSurfaceTintColor: widget.viewSurfaceTintColor, viewSide: widget.viewSide, viewShape: widget.viewShape, + viewBarPadding: widget.viewBarPadding, viewHeaderHeight: widget.headerHeight, viewHeaderTextStyle: widget.headerTextStyle, viewHeaderHintStyle: widget.headerHintStyle, dividerColor: widget.dividerColor, viewConstraints: widget.viewConstraints, + viewPadding: widget.viewPadding, showFullScreenView: getShowFullScreenView(), toggleVisibility: toggleVisibility, textDirection: Directionality.of(context), @@ -511,11 +529,13 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { this.viewSurfaceTintColor, this.viewSide, this.viewShape, + this.viewBarPadding, this.viewHeaderHeight, this.viewHeaderTextStyle, this.viewHeaderHintStyle, this.dividerColor, this.viewConstraints, + this.viewPadding, this.textCapitalization, required this.showFullScreenView, required this.anchorKey, @@ -539,11 +559,13 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { final Color? viewSurfaceTintColor; final BorderSide? viewSide; final OutlinedBorder? viewShape; + final EdgeInsetsGeometry? viewBarPadding; final double? viewHeaderHeight; final TextStyle? viewHeaderTextStyle; final TextStyle? viewHeaderHintStyle; final Color? dividerColor; final BoxConstraints? viewConstraints; + final EdgeInsetsGeometry? viewPadding; final TextCapitalization? textCapitalization; final bool showFullScreenView; final GlobalKey anchorKey; @@ -705,10 +727,12 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { viewSurfaceTintColor: viewSurfaceTintColor, viewSide: viewSide, viewShape: viewShape, + viewBarPadding: viewBarPadding, viewHeaderHeight: viewHeaderHeight, viewHeaderTextStyle: viewHeaderTextStyle, viewHeaderHintStyle: viewHeaderHintStyle, dividerColor: dividerColor, + viewPadding: viewPadding, showFullScreenView: showFullScreenView, animation: curvedAnimation!, topPadding: topPadding, @@ -745,10 +769,12 @@ class _ViewContent extends StatefulWidget { this.viewSurfaceTintColor, this.viewSide, this.viewShape, + this.viewBarPadding, this.viewHeaderHeight, this.viewHeaderTextStyle, this.viewHeaderHintStyle, this.dividerColor, + this.viewPadding, this.textCapitalization, required this.showFullScreenView, required this.topPadding, @@ -772,10 +798,12 @@ class _ViewContent extends StatefulWidget { final Color? viewSurfaceTintColor; final BorderSide? viewSide; final OutlinedBorder? viewShape; + final EdgeInsetsGeometry? viewBarPadding; final double? viewHeaderHeight; final TextStyle? viewHeaderTextStyle; final TextStyle? viewHeaderHintStyle; final Color? dividerColor; + final EdgeInsetsGeometry? viewPadding; final TextCapitalization? textCapitalization; final bool showFullScreenView; final double topPadding; @@ -963,6 +991,12 @@ class _ViewContentState extends State<_ViewContent> { ?? widget.viewHeaderTextStyle ?? viewTheme.headerTextStyle ?? viewDefaults.headerHintStyle; + final EdgeInsetsGeometry? effectivePadding = widget.viewPadding + ?? viewTheme.padding + ?? viewDefaults.padding; + final EdgeInsetsGeometry? effectiveBarPadding = widget.viewBarPadding + ?? viewTheme.barPadding + ?? viewDefaults.barPadding; final Widget viewDivider = DividerTheme( data: dividerTheme.copyWith(color: effectiveDividerColor), @@ -976,61 +1010,65 @@ class _ViewContentState extends State<_ViewContent> { child: SizedBox( width: _viewRect.width, height: _viewRect.height, - child: Material( - clipBehavior: Clip.antiAlias, - shape: effectiveShape, - color: effectiveBackgroundColor, - surfaceTintColor: effectiveSurfaceTint, - elevation: effectiveElevation, - child: ClipRect( + child: Padding( + padding: widget.showFullScreenView ? EdgeInsets.zero : (effectivePadding ?? EdgeInsets.zero), + child: Material( clipBehavior: Clip.antiAlias, - child: OverflowBox( - alignment: Alignment.topLeft, - maxWidth: math.min(widget.viewMaxWidth, _screenSize!.width), - minWidth: 0, - child: FadeTransition( - opacity: viewIconsFadeCurve, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: EdgeInsets.only(top: widget.topPadding), - child: SafeArea( - top: false, - bottom: false, - child: SearchBar( - autoFocus: true, - constraints: headerConstraints ?? (widget.showFullScreenView ? BoxConstraints(minHeight: _SearchViewDefaultsM3.fullScreenBarHeight) : null), - leading: widget.viewLeading ?? defaultLeading, - trailing: widget.viewTrailing ?? defaultTrailing, - hintText: widget.viewHintText, - backgroundColor: const MaterialStatePropertyAll(Colors.transparent), - overlayColor: const MaterialStatePropertyAll(Colors.transparent), - elevation: const MaterialStatePropertyAll(0.0), - textStyle: MaterialStatePropertyAll(effectiveTextStyle), - hintStyle: MaterialStatePropertyAll(effectiveHintStyle), - controller: _controller, - onChanged: (String value) { - widget.viewOnChanged?.call(value); - updateSuggestions(); - }, - onSubmitted: widget.viewOnSubmitted, - textCapitalization: widget.textCapitalization, - textInputAction: widget.textInputAction, - keyboardType: widget.keyboardType, + shape: effectiveShape, + color: effectiveBackgroundColor, + surfaceTintColor: effectiveSurfaceTint, + elevation: effectiveElevation, + child: ClipRect( + clipBehavior: Clip.antiAlias, + child: OverflowBox( + alignment: Alignment.topLeft, + maxWidth: math.min(widget.viewMaxWidth, _screenSize!.width), + minWidth: 0, + child: FadeTransition( + opacity: viewIconsFadeCurve, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: EdgeInsets.only(top: widget.topPadding), + child: SafeArea( + top: false, + bottom: false, + child: SearchBar( + autoFocus: true, + constraints: headerConstraints ?? (widget.showFullScreenView ? BoxConstraints(minHeight: _SearchViewDefaultsM3.fullScreenBarHeight) : null), + padding: WidgetStatePropertyAll(effectiveBarPadding), + leading: widget.viewLeading ?? defaultLeading, + trailing: widget.viewTrailing ?? defaultTrailing, + hintText: widget.viewHintText, + backgroundColor: const MaterialStatePropertyAll(Colors.transparent), + overlayColor: const MaterialStatePropertyAll(Colors.transparent), + elevation: const MaterialStatePropertyAll(0.0), + textStyle: MaterialStatePropertyAll(effectiveTextStyle), + hintStyle: MaterialStatePropertyAll(effectiveHintStyle), + controller: _controller, + onChanged: (String value) { + widget.viewOnChanged?.call(value); + updateSuggestions(); + }, + onSubmitted: widget.viewOnSubmitted, + textCapitalization: widget.textCapitalization, + textInputAction: widget.textInputAction, + keyboardType: widget.keyboardType, + ), ), ), - ), - FadeTransition( - opacity: viewDividerFadeCurve, - child: viewDivider), - Expanded( - child: FadeTransition( - opacity: viewListFadeOnIntervalCurve, - child: viewBuilder(result), + FadeTransition( + opacity: viewDividerFadeCurve, + child: viewDivider), + Expanded( + child: FadeTransition( + opacity: viewListFadeOnIntervalCurve, + child: viewBuilder(result), + ), ), - ), - ], + ], + ), ), ), ), @@ -1054,6 +1092,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor { MaterialStateProperty? barSide, MaterialStateProperty? barShape, MaterialStateProperty? barPadding, + super.viewBarPadding, MaterialStateProperty? barTextStyle, MaterialStateProperty? barHintStyle, super.viewLeading, @@ -1069,6 +1108,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor { super.dividerColor, BoxConstraints? constraints, super.viewConstraints, + super.viewPadding, super.isFullScreen, super.searchController, super.textCapitalization, @@ -1673,6 +1713,9 @@ class _SearchViewDefaultsM3 extends SearchViewThemeData { @override BoxConstraints get constraints => const BoxConstraints(minWidth: 360.0, minHeight: 240.0); + @override + EdgeInsetsGeometry? get barPadding => const EdgeInsets.symmetric(horizontal: 8.0); + @override Color? get dividerColor => _colors.outline; } diff --git a/packages/flutter/lib/src/material/search_view_theme.dart b/packages/flutter/lib/src/material/search_view_theme.dart index 1f66c2d085..125e414b88 100644 --- a/packages/flutter/lib/src/material/search_view_theme.dart +++ b/packages/flutter/lib/src/material/search_view_theme.dart @@ -44,6 +44,8 @@ class SearchViewThemeData with Diagnosticable { this.elevation, this.surfaceTintColor, this.constraints, + this.padding, + this.barPadding, this.side, this.shape, this.headerHeight, @@ -79,6 +81,12 @@ class SearchViewThemeData with Diagnosticable { /// Overrides the value of size constraints for [SearchAnchor.viewConstraints]. final BoxConstraints? constraints; + /// Overrides the value of the padding for [SearchAnchor.viewPadding]. + final EdgeInsetsGeometry? padding; + + /// Overrides the value of the padding for [SearchAnchor.viewBarPadding] + final EdgeInsetsGeometry? barPadding; + /// Overrides the value of the divider color for [SearchAnchor.dividerColor]. final Color? dividerColor; @@ -94,6 +102,8 @@ class SearchViewThemeData with Diagnosticable { TextStyle? headerTextStyle, TextStyle? headerHintStyle, BoxConstraints? constraints, + EdgeInsetsGeometry? padding, + EdgeInsetsGeometry? barPadding, Color? dividerColor, }) { return SearchViewThemeData( @@ -106,6 +116,8 @@ class SearchViewThemeData with Diagnosticable { headerTextStyle: headerTextStyle ?? this.headerTextStyle, headerHintStyle: headerHintStyle ?? this.headerHintStyle, constraints: constraints ?? this.constraints, + padding: padding ?? this.padding, + barPadding: barPadding ?? this.barPadding, dividerColor: dividerColor ?? this.dividerColor, ); } @@ -125,6 +137,8 @@ class SearchViewThemeData with Diagnosticable { headerTextStyle: TextStyle.lerp(a?.headerTextStyle, b?.headerTextStyle, t), headerHintStyle: TextStyle.lerp(a?.headerTextStyle, b?.headerTextStyle, t), constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t), + padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), + barPadding: EdgeInsetsGeometry.lerp(a?.barPadding, b?.barPadding, t), dividerColor: Color.lerp(a?.dividerColor, b?.dividerColor, t), ); } @@ -140,6 +154,8 @@ class SearchViewThemeData with Diagnosticable { headerTextStyle, headerHintStyle, constraints, + padding, + barPadding, dividerColor, ); @@ -161,6 +177,8 @@ class SearchViewThemeData with Diagnosticable { && other.headerTextStyle == headerTextStyle && other.headerHintStyle == headerHintStyle && other.constraints == constraints + && other.padding == padding + && other.barPadding == barPadding && other.dividerColor == dividerColor; } @@ -176,6 +194,8 @@ class SearchViewThemeData with Diagnosticable { properties.add(DiagnosticsProperty('headerTextStyle', headerTextStyle, defaultValue: null)); properties.add(DiagnosticsProperty('headerHintStyle', headerHintStyle, defaultValue: null)); properties.add(DiagnosticsProperty('constraints', constraints, defaultValue: null)); + properties.add(DiagnosticsProperty('padding', padding, defaultValue: null)); + properties.add(DiagnosticsProperty('barPadding', barPadding, defaultValue: null)); properties.add(DiagnosticsProperty('dividerColor', dividerColor, defaultValue: null)); } diff --git a/packages/flutter/test/material/search_anchor_test.dart b/packages/flutter/test/material/search_anchor_test.dart index 7f1ef6fbc2..32dcbb4f51 100644 --- a/packages/flutter/test/material/search_anchor_test.dart +++ b/packages/flutter/test/material/search_anchor_test.dart @@ -1642,6 +1642,56 @@ void main() { expect(inputText.style?.color, Colors.orange); }); + testWidgets('SearchAnchor respects viewPadding property', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Material( + child: SearchAnchor( + isFullScreen: false, + viewPadding: const EdgeInsets.all(16.0), + builder: (BuildContext context, SearchController controller) { + return IconButton(icon: const Icon(Icons.search), onPressed: () { + controller.openView(); + },); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + )); + + await tester.tap(find.widgetWithIcon(IconButton, Icons.search)); + await tester.pumpAndSettle(); + + final Padding padding = tester.widget(find.descendant(of: findViewContent(), matching: find.byType(Padding)).first); + expect(padding.padding, const EdgeInsets.all(16.0)); + }); + + testWidgets('SearchAnchor ignores viewPadding property if full screen', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Material( + child: SearchAnchor( + isFullScreen: true, + viewPadding: const EdgeInsets.all(16.0), + builder: (BuildContext context, SearchController controller) { + return IconButton(icon: const Icon(Icons.search), onPressed: () { + controller.openView(); + },); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + )); + + await tester.tap(find.widgetWithIcon(IconButton, Icons.search)); + await tester.pumpAndSettle(); + + final Padding padding = tester.widget(find.descendant(of: findViewContent(), matching: find.byType(Padding)).first); + expect(padding.padding, EdgeInsets.zero); + }); + testWidgets('SearchAnchor respects dividerColor property', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Material( @@ -1696,6 +1746,33 @@ void main() { expect(sizedBox.height, 390.0); }); + testWidgets('SearchAnchor respects viewBarPadding property', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Material( + child: Center( + child: SearchAnchor( + viewBarPadding: const EdgeInsets.symmetric(horizontal: 16.0), + builder: (BuildContext context, SearchController controller) { + return IconButton(icon: const Icon(Icons.search), onPressed: () { + controller.openView(); + },); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + ), + )); + + await tester.tap(find.widgetWithIcon(IconButton, Icons.search)); + await tester.pumpAndSettle(); + + final Finder findSearchBar = find.descendant(of: findViewContent(), matching: find.byType(SearchBar)).first; + final Padding padding = tester.widget(find.descendant(of: findSearchBar, matching: find.byType(Padding)).first); + expect(padding.padding, const EdgeInsets.symmetric(horizontal: 16.0)); + }); + testWidgets('SearchAnchor respects builder property - LTR', (WidgetTester tester) async { Widget buildAnchor({required SearchAnchorChildBuilder builder}) { return MaterialApp( diff --git a/packages/flutter/test/material/search_view_theme_test.dart b/packages/flutter/test/material/search_view_theme_test.dart index 7041ef3ac3..4dfce1e3f2 100644 --- a/packages/flutter/test/material/search_view_theme_test.dart +++ b/packages/flutter/test/material/search_view_theme_test.dart @@ -31,6 +31,8 @@ void main() { expect(themeData.headerHeight, null); expect(themeData.headerTextStyle, null); expect(themeData.headerHintStyle, null); + expect(themeData.padding, null); + expect(themeData.barPadding, null); expect(themeData.dividerColor, null); const SearchViewTheme theme = SearchViewTheme(data: SearchViewThemeData(), child: SizedBox()); @@ -43,6 +45,8 @@ void main() { expect(theme.data.headerHeight, null); expect(theme.data.headerTextStyle, null); expect(theme.data.headerHintStyle, null); + expect(themeData.padding, null); + expect(themeData.barPadding, null); expect(theme.data.dividerColor, null); }); @@ -71,6 +75,8 @@ void main() { headerTextStyle: TextStyle(fontSize: 24.0), headerHintStyle: TextStyle(fontSize: 16.0), constraints: BoxConstraints(minWidth: 350, minHeight: 240), + padding: EdgeInsets.only(bottom: 32.0), + barPadding: EdgeInsets.zero, ).debugFillProperties(builder); final List description = builder.properties @@ -87,6 +93,8 @@ void main() { expect(description[6], 'headerTextStyle: TextStyle(inherit: true, size: 24.0)'); expect(description[7], 'headerHintStyle: TextStyle(inherit: true, size: 16.0)'); expect(description[8], 'constraints: BoxConstraints(350.0<=w<=Infinity, 240.0<=h<=Infinity)'); + expect(description[9], 'padding: EdgeInsets(0.0, 0.0, 0.0, 32.0)'); + expect(description[10], 'barPadding: EdgeInsets.zero'); }); group('[Theme, SearchViewTheme, SearchView properties overrides]', () {