Skip to content

Commit b05da52

Browse files
authored
CupertinoSearchTextField and CupertinoSliverNavigationBar.search more fidelity updates (flutter#169708)
This PR does the following: ## Show large title mid-transition if automaticallyImplyLeading is false (see flutter#169708 (comment)) ## Correct color for the CupertinoSearchTextField placeholder (see flutter#163020 (comment)) | Dark mode | Light mode | | --- | --- | | <img width="381" alt="placeholder color dark mode" src="https://github.com/user-attachments/assets/e37e23fa-9f4f-495e-8f02-b9c38a4faffb" /> | <img width="381" alt="placeholder color light mode" src="https://github.com/user-attachments/assets/16e24a61-2528-44e0-9afa-8431487cf5ff" /> | And also: - Removes flaky mid-transition goldens - Fixes a CupertinoTextField crash caused by flutter#166952 where size > constraints Fixes [Improve fidelity of CupertinoSliverNavigationBar.search and CupertinoSearchTextField](flutter#163020)
1 parent 134ca1c commit b05da52

File tree

6 files changed

+78
-55
lines changed

6 files changed

+78
-55
lines changed

packages/flutter/lib/src/cupertino/nav_bar.dart

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -883,12 +883,19 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
883883
/// It should be placed at top of the screen and automatically accounts for
884884
/// the iOS status bar.
885885
///
886+
/// This navigation bar is expanded only in portrait orientation. In landscape
887+
/// mode, the navigation bar remains permanently collapsed. The navigation bar
888+
/// also collapses when scrolling in portrait mode.
889+
///
886890
/// Minimally, a [largeTitle] widget will appear in the middle of the app bar
887891
/// when the sliver is collapsed and transfer to the area below in larger font
888-
/// when the sliver is expanded.
892+
/// when the sliver is expanded. This expanded view will only trigger in
893+
/// portrait orientation, while in landscape mode the bar will stay in its
894+
/// collapsed view.
889895
///
890-
/// For advanced uses, an optional [middle] widget can be supplied to show a
891-
/// different widget in the middle of the navigation bar when the sliver is collapsed.
896+
/// For advanced uses, an optional [middle] widget
897+
/// can be supplied to show a different widget in the middle of the navigation
898+
/// bar when the sliver is collapsed.
892899
///
893900
/// Like [CupertinoNavigationBar], it also supports a [leading] and [trailing]
894901
/// widget on the static section on top that remains while scrolling.
@@ -1085,10 +1092,12 @@ class CupertinoSliverNavigationBar extends StatefulWidget {
10851092
/// A widget to place in the middle of the static navigation bar instead of
10861093
/// the [largeTitle].
10871094
///
1088-
/// This widget is visible in both collapsed and expanded states if
1089-
/// [alwaysShowMiddle] is true, otherwise just in collapsed state. The text
1090-
/// supplied in [largeTitle] will no longer appear in collapsed state if a
1091-
/// [middle] widget is provided.
1095+
/// If [alwaysShowMiddle] is true, this widget is visible in both the
1096+
/// collapsed and expanded states of the navigation bar. Else, it is visible
1097+
/// only in the collapsed state.
1098+
///
1099+
/// If null, [largeTitle] will be displayed in the navigation bar's collapsed
1100+
/// state.
10921101
final Widget? middle;
10931102

10941103
/// {@macro flutter.cupertino.CupertinoNavigationBar.trailing}
@@ -3060,7 +3069,6 @@ class _NavigationBarComponentsTransition {
30603069
final KeyedSubtree? bottomLargeTitle =
30613070
bottomComponents.largeTitleKey.currentWidget as KeyedSubtree?;
30623071
final KeyedSubtree? topBackLabel = topComponents.backLabelKey.currentWidget as KeyedSubtree?;
3063-
final KeyedSubtree? topLeading = topComponents.leadingKey.currentWidget as KeyedSubtree?;
30643072

30653073
if (bottomLargeTitle == null || !bottomLargeExpanded) {
30663074
return null;
@@ -3093,32 +3101,28 @@ class _NavigationBarComponentsTransition {
30933101
);
30943102
}
30953103

3096-
if (topLeading != null) {
3097-
// Unlike bottom middle, the bottom large title moves when it can't
3098-
// transition to the top back label position.
3099-
final RelativeRect from = positionInTransitionBox(
3100-
bottomComponents.largeTitleKey,
3101-
from: bottomNavBarBox,
3102-
);
3103-
3104-
final RelativeRectTween positionTween = RelativeRectTween(
3105-
begin: from,
3106-
end: from.shift(Offset(forwardDirection * bottomNavBarBox.size.width / 4.0, 0.0)),
3107-
);
3104+
// Unlike bottom middle, the bottom large title moves when it can't
3105+
// transition to the top back label position.
3106+
final RelativeRect from = positionInTransitionBox(
3107+
bottomComponents.largeTitleKey,
3108+
from: bottomNavBarBox,
3109+
);
31083110

3109-
// Just shift slightly towards the trailing edge instead of moving to the
3110-
// back label position.
3111-
return PositionedTransition(
3112-
rect: animation.drive(positionTween),
3113-
child: FadeTransition(
3114-
opacity: fadeOutBy(0.4),
3115-
// Keep the font when transitioning into a non-back-label leading.
3116-
child: DefaultTextStyle(style: bottomLargeTitleTextStyle!, child: bottomLargeTitle.child),
3117-
),
3118-
);
3119-
}
3111+
final RelativeRectTween positionTween = RelativeRectTween(
3112+
begin: from,
3113+
end: from.shift(Offset(forwardDirection * bottomNavBarBox.size.width / 4.0, 0.0)),
3114+
);
31203115

3121-
return null;
3116+
// Just shift slightly towards the trailing edge instead of moving to the
3117+
// back label position.
3118+
return PositionedTransition(
3119+
rect: animation.drive(positionTween),
3120+
child: FadeTransition(
3121+
opacity: fadeOutBy(0.4),
3122+
// Keep the font when transitioning into a non-back-label leading.
3123+
child: DefaultTextStyle(style: bottomLargeTitleTextStyle!, child: bottomLargeTitle.child),
3124+
),
3125+
);
31223126
}
31233127

31243128
Widget? get bottomTrailing {

packages/flutter/lib/src/cupertino/search_field.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,10 +483,17 @@ class _CupertinoSearchTextFieldState extends State<CupertinoSearchTextField> wit
483483
Widget build(BuildContext context) {
484484
final String placeholder =
485485
widget.placeholder ?? CupertinoLocalizations.of(context).searchTextFieldPlaceholderLabel;
486-
486+
final Color defaultPlaceholderColor = CupertinoDynamicColor.resolve(
487+
CupertinoColors.secondaryLabel,
488+
context,
489+
);
487490
final TextStyle placeholderStyle =
488491
widget.placeholderStyle ??
489-
TextStyle(color: CupertinoColors.systemGrey.withOpacity(1.0 - _fadeExtent));
492+
TextStyle(
493+
color: defaultPlaceholderColor.withAlpha(
494+
(255 * (defaultPlaceholderColor.a * (1 - _fadeExtent))).round(),
495+
),
496+
);
490497

491498
// The icon size will be scaled by a factor of the accessibility text scale,
492499
// to follow the behavior of `UISearchTextField`.

packages/flutter/lib/src/cupertino/text_field.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1879,6 +1879,6 @@ class _RenderBaselineAlignedStack extends RenderBox
18791879
width = math.max(width, editableTextSize.width);
18801880
final Size size = Size(width, height);
18811881
assert(size.isFinite);
1882-
return size;
1882+
return constraints.constrain(size);
18831883
}
18841884
}

packages/flutter/test/cupertino/nav_bar_test.dart

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2846,14 +2846,6 @@ void main() {
28462846
// Tap the search field.
28472847
await tester.tap(find.byType(CupertinoSearchTextField), warnIfMissed: false);
28482848
await tester.pump();
2849-
// Pump halfway through the animation.
2850-
await tester.pump(const Duration(milliseconds: 150));
2851-
2852-
await expectLater(
2853-
find.byType(CupertinoSliverNavigationBar),
2854-
matchesGoldenFile('nav_bar.search.transition_forward.png'),
2855-
);
2856-
28572849
// Pump to the end of the animation.
28582850
await tester.pump(const Duration(milliseconds: 300));
28592851

@@ -2865,14 +2857,6 @@ void main() {
28652857
// Tap the 'Cancel' button to exit the search view.
28662858
await tester.tap(find.widgetWithText(CupertinoButton, 'Cancel'));
28672859
await tester.pump();
2868-
// Pump halfway through the animation.
2869-
await tester.pump(const Duration(milliseconds: 150));
2870-
2871-
await expectLater(
2872-
find.byType(CupertinoSliverNavigationBar),
2873-
matchesGoldenFile('nav_bar.search.transition_backward.png'),
2874-
);
2875-
28762860
// Pump for the duration of the search field animation.
28772861
await tester.pump(const Duration(milliseconds: 300));
28782862

packages/flutter/test/cupertino/nav_bar_transition_test.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,33 @@ void main() {
18501850
expect(find.text('Page 2'), findsOneWidget);
18511851
});
18521852

1853+
testWidgets('Bottom large title is shown mid-transition when top has no leading', (
1854+
WidgetTester tester,
1855+
) async {
1856+
setWindowToPortrait(tester);
1857+
await startTransitionBetween(
1858+
tester,
1859+
from: const CupertinoSliverNavigationBar(largeTitle: Text('Page 1')),
1860+
to: const CupertinoSliverNavigationBar(
1861+
largeTitle: Text('Page 2'),
1862+
automaticallyImplyLeading: false,
1863+
),
1864+
);
1865+
1866+
// Go to the next page.
1867+
await tester.pump(const Duration(milliseconds: 600));
1868+
1869+
// Start the gesture at the edge of the screen.
1870+
final TestGesture gesture = await tester.startGesture(const Offset(5.0, 200.0));
1871+
// Trigger the swipe.
1872+
await gesture.moveBy(const Offset(200.0, 0.0));
1873+
1874+
// Back gestures should trigger and draw the hero transition in the very same
1875+
// frame (since the "from" route has already moved to reveal the "to" route).
1876+
await tester.pump();
1877+
expect(flying(tester, find.text('Page 1')), findsOneWidget);
1878+
});
1879+
18531880
testWidgets('Back label is not clipped mid-transition', (WidgetTester tester) async {
18541881
const String label = 'backbackback';
18551882
await startTransitionBetween(

packages/flutter/test/cupertino/search_field_test.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ void main() {
129129
);
130130

131131
Text placeholder = tester.widget(find.text('Search'));
132-
expect(placeholder.style!.color!.value, CupertinoColors.systemGrey.darkColor.value);
132+
expect(placeholder.style!.color!.value, CupertinoColors.secondaryLabel.darkColor.value);
133133

134134
await tester.pumpAndSettle();
135135

@@ -141,7 +141,7 @@ void main() {
141141
);
142142

143143
placeholder = tester.widget(find.text('Search'));
144-
expect(placeholder.style!.color!.value, CupertinoColors.systemGrey.color.value);
144+
expect(placeholder.style!.color!.value, CupertinoColors.secondaryLabel.color.value);
145145
});
146146

147147
testWidgets("placeholderStyle modifies placeholder's style and doesn't affect text's style", (
@@ -623,7 +623,7 @@ void main() {
623623
expect(suffixIconFinder, findsOneWidget);
624624
expect(placeholderFinder, findsOneWidget);
625625

626-
// Initially, the icons and placeholder text are fully opaque.
626+
// Initially, the icons are fully opaque.
627627
expect(
628628
tester
629629
.widget<Opacity>(find.ancestor(of: prefixIconFinder, matching: find.byType(Opacity)))
@@ -636,7 +636,8 @@ void main() {
636636
.opacity,
637637
equals(1.0),
638638
);
639-
expect(tester.widget<Text>(placeholderFinder).style?.color?.a, equals(1.0));
639+
// The default placeholder color is semi-transparent.
640+
expect(tester.widget<Text>(placeholderFinder).style?.color?.a, equals(0.6));
640641

641642
final double searchTextFieldHeight = tester.getSize(searchTextFieldFinder).height;
642643

0 commit comments

Comments
 (0)