diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index 9a8c16780e8..768434a49bb 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -883,12 +883,19 @@ class _CupertinoNavigationBarState extends State { /// It should be placed at top of the screen and automatically accounts for /// the iOS status bar. /// +/// This navigation bar is expanded only in portrait orientation. In landscape +/// mode, the navigation bar remains permanently collapsed. The navigation bar +/// also collapses when scrolling in portrait mode. +/// /// Minimally, a [largeTitle] widget will appear in the middle of the app bar /// when the sliver is collapsed and transfer to the area below in larger font -/// when the sliver is expanded. +/// when the sliver is expanded. This expanded view will only trigger in +/// portrait orientation, while in landscape mode the bar will stay in its +/// collapsed view. /// -/// For advanced uses, an optional [middle] widget can be supplied to show a -/// different widget in the middle of the navigation bar when the sliver is collapsed. +/// For advanced uses, an optional [middle] widget +/// can be supplied to show a different widget in the middle of the navigation +/// bar when the sliver is collapsed. /// /// Like [CupertinoNavigationBar], it also supports a [leading] and [trailing] /// widget on the static section on top that remains while scrolling. @@ -1085,10 +1092,12 @@ class CupertinoSliverNavigationBar extends StatefulWidget { /// A widget to place in the middle of the static navigation bar instead of /// the [largeTitle]. /// - /// This widget is visible in both collapsed and expanded states if - /// [alwaysShowMiddle] is true, otherwise just in collapsed state. The text - /// supplied in [largeTitle] will no longer appear in collapsed state if a - /// [middle] widget is provided. + /// If [alwaysShowMiddle] is true, this widget is visible in both the + /// collapsed and expanded states of the navigation bar. Else, it is visible + /// only in the collapsed state. + /// + /// If null, [largeTitle] will be displayed in the navigation bar's collapsed + /// state. final Widget? middle; /// {@macro flutter.cupertino.CupertinoNavigationBar.trailing} @@ -3060,7 +3069,6 @@ class _NavigationBarComponentsTransition { final KeyedSubtree? bottomLargeTitle = bottomComponents.largeTitleKey.currentWidget as KeyedSubtree?; final KeyedSubtree? topBackLabel = topComponents.backLabelKey.currentWidget as KeyedSubtree?; - final KeyedSubtree? topLeading = topComponents.leadingKey.currentWidget as KeyedSubtree?; if (bottomLargeTitle == null || !bottomLargeExpanded) { return null; @@ -3093,32 +3101,28 @@ class _NavigationBarComponentsTransition { ); } - if (topLeading != null) { - // Unlike bottom middle, the bottom large title moves when it can't - // transition to the top back label position. - final RelativeRect from = positionInTransitionBox( - bottomComponents.largeTitleKey, - from: bottomNavBarBox, - ); + // Unlike bottom middle, the bottom large title moves when it can't + // transition to the top back label position. + final RelativeRect from = positionInTransitionBox( + bottomComponents.largeTitleKey, + from: bottomNavBarBox, + ); - final RelativeRectTween positionTween = RelativeRectTween( - begin: from, - end: from.shift(Offset(forwardDirection * bottomNavBarBox.size.width / 4.0, 0.0)), - ); + final RelativeRectTween positionTween = RelativeRectTween( + begin: from, + end: from.shift(Offset(forwardDirection * bottomNavBarBox.size.width / 4.0, 0.0)), + ); - // Just shift slightly towards the trailing edge instead of moving to the - // back label position. - return PositionedTransition( - rect: animation.drive(positionTween), - child: FadeTransition( - opacity: fadeOutBy(0.4), - // Keep the font when transitioning into a non-back-label leading. - child: DefaultTextStyle(style: bottomLargeTitleTextStyle!, child: bottomLargeTitle.child), - ), - ); - } - - return null; + // Just shift slightly towards the trailing edge instead of moving to the + // back label position. + return PositionedTransition( + rect: animation.drive(positionTween), + child: FadeTransition( + opacity: fadeOutBy(0.4), + // Keep the font when transitioning into a non-back-label leading. + child: DefaultTextStyle(style: bottomLargeTitleTextStyle!, child: bottomLargeTitle.child), + ), + ); } Widget? get bottomTrailing { diff --git a/packages/flutter/lib/src/cupertino/search_field.dart b/packages/flutter/lib/src/cupertino/search_field.dart index d13524df0d7..473e7c4474f 100644 --- a/packages/flutter/lib/src/cupertino/search_field.dart +++ b/packages/flutter/lib/src/cupertino/search_field.dart @@ -483,10 +483,17 @@ class _CupertinoSearchTextFieldState extends State wit Widget build(BuildContext context) { final String placeholder = widget.placeholder ?? CupertinoLocalizations.of(context).searchTextFieldPlaceholderLabel; - + final Color defaultPlaceholderColor = CupertinoDynamicColor.resolve( + CupertinoColors.secondaryLabel, + context, + ); final TextStyle placeholderStyle = widget.placeholderStyle ?? - TextStyle(color: CupertinoColors.systemGrey.withOpacity(1.0 - _fadeExtent)); + TextStyle( + color: defaultPlaceholderColor.withAlpha( + (255 * (defaultPlaceholderColor.a * (1 - _fadeExtent))).round(), + ), + ); // The icon size will be scaled by a factor of the accessibility text scale, // to follow the behavior of `UISearchTextField`. diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index 8e705a9e535..c9209a565c2 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -1879,6 +1879,6 @@ class _RenderBaselineAlignedStack extends RenderBox width = math.max(width, editableTextSize.width); final Size size = Size(width, height); assert(size.isFinite); - return size; + return constraints.constrain(size); } } diff --git a/packages/flutter/test/cupertino/nav_bar_test.dart b/packages/flutter/test/cupertino/nav_bar_test.dart index cccfc71560c..2175d8edb2a 100644 --- a/packages/flutter/test/cupertino/nav_bar_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_test.dart @@ -2846,14 +2846,6 @@ void main() { // Tap the search field. await tester.tap(find.byType(CupertinoSearchTextField), warnIfMissed: false); await tester.pump(); - // Pump halfway through the animation. - await tester.pump(const Duration(milliseconds: 150)); - - await expectLater( - find.byType(CupertinoSliverNavigationBar), - matchesGoldenFile('nav_bar.search.transition_forward.png'), - ); - // Pump to the end of the animation. await tester.pump(const Duration(milliseconds: 300)); @@ -2865,14 +2857,6 @@ void main() { // Tap the 'Cancel' button to exit the search view. await tester.tap(find.widgetWithText(CupertinoButton, 'Cancel')); await tester.pump(); - // Pump halfway through the animation. - await tester.pump(const Duration(milliseconds: 150)); - - await expectLater( - find.byType(CupertinoSliverNavigationBar), - matchesGoldenFile('nav_bar.search.transition_backward.png'), - ); - // Pump for the duration of the search field animation. await tester.pump(const Duration(milliseconds: 300)); diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart index dec289f3f1b..12c5b38db51 100644 --- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart @@ -1850,6 +1850,33 @@ void main() { expect(find.text('Page 2'), findsOneWidget); }); + testWidgets('Bottom large title is shown mid-transition when top has no leading', ( + WidgetTester tester, + ) async { + setWindowToPortrait(tester); + await startTransitionBetween( + tester, + from: const CupertinoSliverNavigationBar(largeTitle: Text('Page 1')), + to: const CupertinoSliverNavigationBar( + largeTitle: Text('Page 2'), + automaticallyImplyLeading: false, + ), + ); + + // Go to the next page. + await tester.pump(const Duration(milliseconds: 600)); + + // Start the gesture at the edge of the screen. + final TestGesture gesture = await tester.startGesture(const Offset(5.0, 200.0)); + // Trigger the swipe. + await gesture.moveBy(const Offset(200.0, 0.0)); + + // Back gestures should trigger and draw the hero transition in the very same + // frame (since the "from" route has already moved to reveal the "to" route). + await tester.pump(); + expect(flying(tester, find.text('Page 1')), findsOneWidget); + }); + testWidgets('Back label is not clipped mid-transition', (WidgetTester tester) async { const String label = 'backbackback'; await startTransitionBetween( diff --git a/packages/flutter/test/cupertino/search_field_test.dart b/packages/flutter/test/cupertino/search_field_test.dart index 03666fd9042..237c9841ff2 100644 --- a/packages/flutter/test/cupertino/search_field_test.dart +++ b/packages/flutter/test/cupertino/search_field_test.dart @@ -129,7 +129,7 @@ void main() { ); Text placeholder = tester.widget(find.text('Search')); - expect(placeholder.style!.color!.value, CupertinoColors.systemGrey.darkColor.value); + expect(placeholder.style!.color!.value, CupertinoColors.secondaryLabel.darkColor.value); await tester.pumpAndSettle(); @@ -141,7 +141,7 @@ void main() { ); placeholder = tester.widget(find.text('Search')); - expect(placeholder.style!.color!.value, CupertinoColors.systemGrey.color.value); + expect(placeholder.style!.color!.value, CupertinoColors.secondaryLabel.color.value); }); testWidgets("placeholderStyle modifies placeholder's style and doesn't affect text's style", ( @@ -623,7 +623,7 @@ void main() { expect(suffixIconFinder, findsOneWidget); expect(placeholderFinder, findsOneWidget); - // Initially, the icons and placeholder text are fully opaque. + // Initially, the icons are fully opaque. expect( tester .widget(find.ancestor(of: prefixIconFinder, matching: find.byType(Opacity))) @@ -636,7 +636,8 @@ void main() { .opacity, equals(1.0), ); - expect(tester.widget(placeholderFinder).style?.color?.a, equals(1.0)); + // The default placeholder color is semi-transparent. + expect(tester.widget(placeholderFinder).style?.color?.a, equals(0.6)); final double searchTextFieldHeight = tester.getSize(searchTextFieldFinder).height;